-
Notifications
You must be signed in to change notification settings - Fork 166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ShadyDOM] Add option for on-demand patching #189
Changes from 10 commits
ea9331d
8591a8b
9eb4b9e
afa5c47
0b5af5c
281ada5
140b5b4
1622012
6a4bea0
0369924
518643f
8b0e854
fd14262
770b354
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ import {EventTargetPatches} from './patches/EventTarget.js'; | |
import {NodePatches} from './patches/Node.js'; | ||
import {SlotablePatches} from './patches/Slotable.js'; | ||
import {ParentNodePatches, ParentNodeDocumentOrFragmentPatches} from './patches/ParentNode.js'; | ||
import {ElementPatches} from './patches/Element.js'; | ||
import {ElementPatches, ElementShadowPatches} from './patches/Element.js'; | ||
import {ElementOrShadowRootPatches} from './patches/ElementOrShadowRoot.js'; | ||
import {HTMLElementPatches} from './patches/HTMLElement.js'; | ||
import {SlotPatches} from './patches/Slot.js'; | ||
|
@@ -71,16 +71,66 @@ const getPatchPrototype = (name) => window[name] && window[name].prototype; | |
// CustomElements polyfill *only* cares about these accessors. | ||
const disallowedNativePatches = utils.settings.hasDescriptors ? null : ['innerHTML', 'textContent']; | ||
|
||
/** | ||
* Patch a group of accessors on an object only if it exists or if the `force` | ||
* argument is true. | ||
* @param {!Object} proto | ||
* @param {!Array<Object>} list | ||
* @param {string=} prefix | ||
* @param {Array=} disallowed | ||
*/ | ||
function applyPatchList(proto, list, prefix, disallowed) { | ||
list.forEach(patch => proto && patch && | ||
utils.patchProperties(proto, patch, prefix, disallowed)); | ||
} | ||
|
||
/** @param {string=} prefix */ | ||
export const applyPatches = (prefix) => { | ||
const disallowed = prefix ? null : disallowedNativePatches; | ||
for (let p in patchMap) { | ||
const proto = getPatchPrototype(p); | ||
patchMap[p].forEach(patch => proto && patch && | ||
utils.patchProperties(proto, patch, prefix, disallowed)); | ||
applyPatchList(proto, patchMap[p], prefix, disallowed); | ||
} | ||
} | ||
|
||
const PROTO_IS_PATCHED = utils.SHADY_PREFIX + 'patchedProto'; | ||
|
||
const patchedProtos = new Map(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add comment that this is making a pre-patched proto for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, let's add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
const TextPatchedProto = Object.create(Text.prototype); | ||
TextPatchedProto[PROTO_IS_PATCHED] = true; | ||
applyPatchList(TextPatchedProto, patchMap.EventTarget); | ||
applyPatchList(TextPatchedProto, patchMap.Node); | ||
applyPatchList(TextPatchedProto, patchMap.Text); | ||
patchedProtos.set(Text.prototype, TextPatchedProto); | ||
|
||
export const patchElementProto = (proto) => { | ||
proto[PROTO_IS_PATCHED] = true; | ||
applyPatchList(proto, patchMap.EventTarget); | ||
applyPatchList(proto, patchMap.Node); | ||
applyPatchList(proto, patchMap.Element); | ||
applyPatchList(proto, patchMap.HTMLElement); | ||
applyPatchList(proto, patchMap.HTMLSlotElement); | ||
return proto; | ||
} | ||
|
||
export const patchNodeProto = (node) => { | ||
if (node[PROTO_IS_PATCHED] || utils.isShadyRoot(node)) { | ||
return; | ||
} | ||
const nativeProto = Object.getPrototypeOf(node); | ||
let proto = patchedProtos.get(nativeProto); | ||
if (!proto) { | ||
proto = Object.create(nativeProto); | ||
patchElementProto(proto); | ||
patchedProtos.set(nativeProto, proto); | ||
} | ||
Object.setPrototypeOf(node, proto); | ||
} | ||
|
||
export const patchShadowOnElement = () => { | ||
utils.patchProperties(Element.prototype, ElementShadowPatches); | ||
} | ||
|
||
export const addShadyPrefixedProperties = () => { | ||
// perform shady patches | ||
applyPatches(utils.SHADY_PREFIX); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,15 +77,6 @@ export const ElementPatches = utils.getOwnPropertyDescriptors({ | |
this[utils.SHADY_PREFIX + 'setAttribute']('slot', value); | ||
}, | ||
|
||
// Note: Can be patched on element prototype on all browsers. | ||
// Must be patched on instance on browsers that support native Shadow DOM | ||
// but do not have builtin accessors (old Chrome). | ||
/** @this {Element} */ | ||
get shadowRoot() { | ||
const nodeData = shadyDataForNode(this); | ||
return nodeData && nodeData.publicRoot || null; | ||
}, | ||
|
||
/** @this {Element} */ | ||
get className() { | ||
return this.getAttribute('class') || ''; | ||
|
@@ -127,14 +118,33 @@ export const ElementPatches = utils.getOwnPropertyDescriptors({ | |
// ensure that "class" attribute is fully removed if ShadyCSS does not keep scoping | ||
this[utils.NATIVE_PREFIX + 'removeAttribute'](attr); | ||
} | ||
}, | ||
} | ||
|
||
}); | ||
|
||
export const ElementShadowPatches = utils.getOwnPropertyDescriptors({ | ||
/** | ||
* @this {Element} | ||
* @param {!{mode: string}} options | ||
*/ | ||
attachShadow(options) { | ||
return attachShadow(this, options); | ||
} | ||
const root = attachShadow(this, options); | ||
// TODO(sorvell): Workaround for CE not seeing shadowRoot in augmented | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "augmented" means "on-demand"? Why wasn't this a problem in regular noPatch mode? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only in on-demand patching mode since we're only patching here. Added better comment. |
||
// noPatch mode. CE's attachShadow patch is overwritten by this patch | ||
// and cannot set its own special tracking for shadowRoot. It does this | ||
// to be able to see closed shadowRoots. | ||
this['__CE_shadowRoot'] = root; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CE polyfill also does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's right. |
||
return root; | ||
}, | ||
|
||
// Note: Can be patched on element prototype on all browsers. | ||
// Must be patched on instance on browsers that support native Shadow DOM | ||
// but do not have builtin accessors (old Chrome). | ||
/** @this {Element} */ | ||
get shadowRoot() { | ||
const nodeData = shadyDataForNode(this); | ||
return nodeData && nodeData.publicRoot || null; | ||
}, | ||
}); | ||
|
||
Object.assign(ElementPatches, ElementShadowPatches); |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -26,11 +26,17 @@ import {patchInsideElementAccessors, patchOutsideElementAccessors} from './patch | |||||||
import {patchEvents, patchClick, composedPath} from './patch-events.js'; | ||||||||
import {ShadyRoot} from './attach-shadow.js'; | ||||||||
import {wrap, Wrapper} from './wrapper.js'; | ||||||||
import {addShadyPrefixedProperties, applyPatches} from './patch-prototypes.js'; | ||||||||
import {addShadyPrefixedProperties, applyPatches, patchShadowOnElement, patchElementProto} from './patch-prototypes.js'; | ||||||||
|
||||||||
|
||||||||
if (utils.settings.inUse) { | ||||||||
|
||||||||
const patch = utils.settings.hasDescriptors ? n => n : (node) => { | ||||||||
patchInsideElementAccessors(node); | ||||||||
patchOutsideElementAccessors(node); | ||||||||
return node; | ||||||||
}; | ||||||||
|
||||||||
let ShadyDOM = { | ||||||||
// TODO(sorvell): remove when Polymer does not depend on this. | ||||||||
'inUse': utils.settings.inUse, | ||||||||
|
@@ -41,11 +47,7 @@ if (utils.settings.inUse) { | |||||||
// (2) does not have special (slot) children itself | ||||||||
// (3) and setting the property needs to provoke distribution (because | ||||||||
// a nested slot is added/removed) | ||||||||
'patch': (node) => { | ||||||||
patchInsideElementAccessors(node); | ||||||||
patchOutsideElementAccessors(node); | ||||||||
return node; | ||||||||
}, | ||||||||
'patch': patch, | ||||||||
'isShadyRoot': utils.isShadyRoot, | ||||||||
'enqueue': enqueue, | ||||||||
'flush': flush, | ||||||||
|
@@ -74,7 +76,20 @@ if (utils.settings.inUse) { | |||||||
// Integration point with ShadyCSS to disable styling MutationObserver, | ||||||||
// as ShadyDOM will now handle dynamic scoping. | ||||||||
'handlesDynamicScoping': true, | ||||||||
'wrap': utils.settings.noPatch ? wrap : (n) => n, | ||||||||
// Ensure the node is wrapped. This should be used when `noPatch` is set | ||||||||
// to ensure Shadow DOM compatible DOM operation. | ||||||||
'wrap': utils.settings.noPatch ? wrap : patch, | ||||||||
// When code should be compatible with `noPatch` `true` and `on-demand` | ||||||||
// settings, `wrapIfNeeded` can be used for optimal performance (v. `wrap`) | ||||||||
// for all DOM operations except the following: `appendChild` and | ||||||||
// `insertBefore` (when the node is being moved from a location where it | ||||||||
// was logically positioned in the DOM); when setting `className`/`class`; | ||||||||
// when calling `querySelector|All`; when setting `textContent` or | ||||||||
// `innerHTML`; `addEventListener`; and all scope specific API's like | ||||||||
// `getRootNode`, `isConnected`, `slot`, `assignedSlot`, `assignedNodes`. | ||||||||
'wrapIfNeeded': utils.settings.noPatch === true || | ||||||||
(utils.settings.noPatch === 'on-demand' && !utils.settings.hasDescriptors) ? | ||||||||
wrap : patch, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's review the logic for what |
||||||||
'Wrapper': Wrapper, | ||||||||
'composedPath': composedPath, | ||||||||
// Set to true to avoid patching regular platform property names. When set, | ||||||||
|
@@ -83,8 +98,10 @@ if (utils.settings.inUse) { | |||||||
// This setting provides a small performance boost, but requires all DOM API | ||||||||
// access that requires Shadow DOM behavior to be proxied via `ShadyDOM.wrap`. | ||||||||
'noPatch': utils.settings.noPatch, | ||||||||
'patchOnDemand': utils.settings.patchOnDemand, | ||||||||
'nativeMethods': nativeMethods, | ||||||||
'nativeTree': nativeTree | ||||||||
'nativeTree': nativeTree, | ||||||||
'patchElementProto': patchElementProto | ||||||||
}; | ||||||||
|
||||||||
window['ShadyDOM'] = ShadyDOM; | ||||||||
|
@@ -121,6 +138,9 @@ if (utils.settings.inUse) { | |||||||
applyPatches(); | ||||||||
// Patch click event behavior only if we're patching | ||||||||
patchClick() | ||||||||
} else if (utils.settings.patchOnDemand) { | ||||||||
// in noPatch, do patch `attachShadow` and `shadowRoot` | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarified. |
||||||||
patchShadowOnElement(); | ||||||||
} | ||||||||
|
||||||||
// For simplicity, patch events unconditionally. | ||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
force
argument removed?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed comment.