Skip to content

Instantly share code, notes, and snippets.

@domenic
Forked from esprehn/custom-element-super-swap.js
Last active October 23, 2015 19:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save domenic/8d4ad3436548de88b899 to your computer and use it in GitHub Desktop.
Save domenic/8d4ad3436548de88b899 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 tempPimpl = new HTMLElementPimpl(element.tagName, element.ownerDocument);
// pimplMap associates actual elements with their pimpls.
// You could also implement this with private properties or symbols, e.g.
// `let originalPimpl = element[pimplSymbol]`.
let originalPimpl = pimplMap.get(element);
pimplMap.set(element, tempPmpl);
// 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 originalPimpl.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 originalPimpl, that puts element back into the
// document. The element also now has the original "parser set" attributes.
pimplMap.set(element, originalPimpl);
// Swap the "merged" attributes from tempPimpl with the ones from
// originalPimpl. element.attributes now has both. This just swaps storage;
// no callbacks are run.
swapPimplAttributes(tempPimpl, originalPimpl);
// If the constructor added a shadow root, it replaces any existing one.
// This just swaps storage, no callbacks are run.
if (tempPimpl.shadowRoot)
swapPimplShadowRoot(tempPimpl, originalPimpl);
// Move over any kids the constructor may have added.
element.appendChild(fragment);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment