Skip to content

Instantly share code, notes, and snippets.

@esprehn
Last active October 23, 2015 19:19
Show Gist options
  • Save esprehn/3996b01712bd9002c15c to your computer and use it in GitHub Desktop.
Save esprehn/3996b01712bd9002c15c to your computer and use it in GitHub Desktop.
Custom element super swap algorithm
/*
This is a rough approximation of what the algorithm woud do. In an
implementation you might make the JS Wrapper point to a different
C++ Element for the duration of the constructor, then make it
point back at the original C++ element, and move data between them.
This means that the C++ side remains consistent, if you querySelector
from the document you can still find your element where the constructor
is running, but if you look at the parentNode/firstChild or attributes
properties of the element they all appear empty. This means in the
common (and well behaved) case where your constructor is not looking
around the document your view over the element is consistent and you
won't observe any weirdness. This also means all things creating elements
like cloneNode, innerHTML, document parser can use the upgrade process
which is async, but appears to be sync from inside your constructor.
*/
function runCreatedCallback(element)
{
let tempSlots = new InternalSlots(element.tagName, element.ownerDocument);
let originalSlots = InternalSlots.get(element);
InternalSlots.set(element, tempSlots);
// at this point element.firstChild is null, element.attributes is empty
// because the internal storage for all of those has been swapped out.
// Actually run the constructor. Any attempt to appendChild, cloneNode
// or importNode the element from inside the constructor will throw a
// HierarchyRequestError.
doElementUpgrade(element);
// Run all attribute changed callbacks, this merges the attributes
// from the original set, and the ones the constructor may have set.
for (let [name, value] of originalSlots.attributes)
setAttribute(element, name, value);
// Remove all kids of the element the constructor may have added.
let fragment = new DocumentFragment()
takeAllChildren(fragment, element);
// Reassociate with the originalSlots, that puts element back into the
// document. The element also now has the original "parser set" attributes.
InternalSlots.set(element, originalSlots);
// Swap the "merged" attributes from tempSlots with the ones from
// originalSlots. element.attributes now has both. This just swaps storage
// no callbacks are run.
InternalSlots.swapAttributes(tempSlots, originalSlots);
// If the constructor added a shadow root, it replaces any existing one.
// This just swaps storage, no callbacks are run.
if (tempSlots.shadowRoot)
InternalSlots.swapShadowRoot(tempSlots, originalSlots);
// Move over any kids the constructor may have added.
appendChild(element, fragment);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment