-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Add scopedQuerySelector helper #7260
Conversation
d13d181
to
f05d61e
Compare
src/dom.js
Outdated
}); | ||
|
||
// Only IE. | ||
const unique = `i-amphtml-scoped-${Math.random()}`; |
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.
That includes the '.' (dot). Is it prudent to have it there given selectors below?
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.
Good catch, changed to use Date.now
* @param {string} selector | ||
* @return {?Element} | ||
*/ | ||
export function scopedQuerySelector(root, selector) { |
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.
We initially decided to hold this off because of https://bugs.chromium.org/p/chromium/issues/detail?id=222028
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.
This isn't due to :scope
, but that there are now two selectors to match against (.selector1
vs .selector1 .selector2
).
I'm not requiring scopedQuerySelector
where we can avoid it (it's fine if you're doing scope.querySelector('selector')
). The only time it's necessary is when there's a descendant selector included (scope.querySelector('parent descendant')
), which is already slow.
@@ -136,7 +136,7 @@ export class AmpAdCustom extends AMP.BaseElement { | |||
|
|||
// Get the parent body of this amp-ad element. It could be the body of | |||
// the main document, or it could be an enclosing iframe. | |||
const body = ancestorElementsByTag(this.element, 'BODY')[0]; | |||
const body = closestByTag(this.element, 'BODY'); |
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.
Hmm. This doesn't look right. Though I guess not the subject to your PR. Would you mind leaving a TODO
here for me to investigate?
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.
I'll split out of the PR.
@@ -177,7 +181,7 @@ export function getElement(ampdoc, selector, analyticsEl, selectionMethod) { | |||
// Only tag names are supported currently. | |||
foundEl = closestByTag(analyticsEl, selector); | |||
} else if (selectionMethod == 'scope') { | |||
foundEl = analyticsEl.parentElement.querySelector(selector); | |||
foundEl = scopedQuerySelector(analyticsEl.parentElement, selector); |
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.
There's the same thing in the analytics-root
. This code is actually waiting to be killed in preference of that. Can you change there as well?
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.
Done.
src/custom-element.js
Outdated
@@ -1362,8 +1362,9 @@ function createBaseCustomElementClass(win) { | |||
* @package @final @this {!Element} | |||
*/ | |||
getRealChildren() { | |||
return dom.childElements(this, element => | |||
!isInternalOrServiceNode(element)); | |||
return dom.childElementByTag(this, '*').filter(element => { |
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.
This is relatively unclear that ByTag
can do wildcards... E.g. does ELement.getElementsByTagName
do wildcards?
Another question: is querying here really faster than first-child -> siblings walk?
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.
I was after code size here, not speed. I'll split out the childElements
changes.
5d9a2af
to
dcf0a09
Compare
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.
PTAL.
@@ -177,7 +181,7 @@ export function getElement(ampdoc, selector, analyticsEl, selectionMethod) { | |||
// Only tag names are supported currently. | |||
foundEl = closestByTag(analyticsEl, selector); | |||
} else if (selectionMethod == 'scope') { | |||
foundEl = analyticsEl.parentElement.querySelector(selector); | |||
foundEl = scopedQuerySelector(analyticsEl.parentElement, selector); |
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.
Done.
src/dom.js
Outdated
}); | ||
|
||
// Only IE. | ||
const unique = `i-amphtml-scoped-${Date.now()}`; |
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.
better to increment an integer. This can very much be invoked twice in the same milli
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.
It's removed immediately, so it'll still be unique. Really, all the class name needs is "i-amphtml-scoped".
src/dom.js
Outdated
scopeSelectorSupported = isScopeSelectorSupported(root); | ||
} | ||
if (scopeSelectorSupported) { | ||
return toArray(root./*OK*/querySelectorAll(`:scope ${selector}`)); |
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.
Do we really need to toArray
?
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.
If they call #filter
, etc.
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.
Are there existing consumers that do that?
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.
Only one. Updated it to a for loop.
Anytime you select a descendant of a descendant (`div div`, or, really, `* *`), your matching against the entire document then filtering to elements inside the scope. This is **not** what you'd expect. Fixes a selector bug in `<amp-analytics>`.
9c0989f
to
bc6c26e
Compare
f4e6699
to
2ac19e9
Compare
2ac19e9
to
14990b7
Compare
Ping @dvoytenko |
* Eliminate uses of childElement and childElements * Forbid uses of buggy querySelector Anytime you select a descendant of a descendant (`div div`, or, really, `* *`), your matching against the entire document then filtering to elements inside the scope. This is **not** what you'd expect. Fixes a selector bug in `<amp-analytics>`. * Add tests * Use `Date.now` to avoid `.` class selector conflict * Merge latest master * Linting * Fix types * Fix merge conflict * Return NodeList * Fix tests
* Eliminate uses of childElement and childElements * Forbid uses of buggy querySelector Anytime you select a descendant of a descendant (`div div`, or, really, `* *`), your matching against the entire document then filtering to elements inside the scope. This is **not** what you'd expect. Fixes a selector bug in `<amp-analytics>`. * Add tests * Use `Date.now` to avoid `.` class selector conflict * Merge latest master * Linting * Fix types * Fix merge conflict * Return NodeList * Fix tests
This fixes (and forbids future) element scoped
#querySelector
calls. Anytime you select a descendant of a descendant (div div
, or, really,* *
), your matching against the entire document then filtering to elements inside the scope. This is not what you'd expect.Instead, we'll use the
:scope
CSS selector where supported (everywhere but IE), and will quickly mutate and un-mutate the element in IE to accomplish a global, unique scope selector.Fixes a selector bug in
<amp-analytics>
. /cc @avimehta/cc @tlong2