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.
That includes the '.' (dot). Is it prudent to have it there given selectors below?
There was a problem hiding this comment.
Good catch, changed to use Date.now
| * @param {string} selector | ||
| * @return {?Element} | ||
| */ | ||
| export function scopedQuerySelector(root, selector) { |
There was a problem hiding this comment.
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.
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.
| // 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.
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.
I'll split out of the PR.
| 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.
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?
src/custom-element.js
Outdated
| getRealChildren() { | ||
| return dom.childElements(this, element => | ||
| !isInternalOrServiceNode(element)); | ||
| return dom.childElementByTag(this, '*').filter(element => { |
There was a problem hiding this comment.
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.
I was after code size here, not speed. I'll split out the childElements changes.
5d9a2af to
dcf0a09
Compare
| foundEl = closestByTag(analyticsEl, selector); | ||
| } else if (selectionMethod == 'scope') { | ||
| foundEl = analyticsEl.parentElement.querySelector(selector); | ||
| foundEl = scopedQuerySelector(analyticsEl.parentElement, selector); |
src/dom.js
Outdated
| }); | ||
|
|
||
| // Only IE. | ||
| const unique = `i-amphtml-scoped-${Date.now()}`; |
There was a problem hiding this comment.
better to increment an integer. This can very much be invoked twice in the same milli
There was a problem hiding this comment.
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.
If they call #filter, etc.
There was a problem hiding this comment.
Are there existing consumers that do that?
There was a problem hiding this comment.
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
#querySelectorcalls. 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
:scopeCSS 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