Forked from esprehn/custom-element-super-swap.js
Last active
October 23, 2015 19:29
-
-
Save domenic/8d4ad3436548de88b899 to your computer and use it in GitHub Desktop.
Custom element super swap algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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