Skip to content
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

✨ Visibility trigger support for querySelectorAll #26886

Merged
merged 20 commits into from Apr 20, 2020

Conversation

micajuine-ho
Copy link
Contributor

@micajuine-ho micajuine-ho commented Feb 20, 2020

Task 2 of #26823

  • getAmpElement() => getAmpElementOrElements
    • If experiment is on, returns unique elements of scopedQuerySelectorAll() for given selector
      • Error if selectionMethod is provided
    • Otherwise, return getElement() of selector
  • Also changed what promise gets pushed into the unlistenPromises

Flame Charts:
Before for processConfigs and addTriggers:
Screen Shot 2020-04-28 at 2 11 53 PM
Screen Shot 2020-04-28 at 2 09 12 PM

After for processConfigs and addTriggers:
Screen Shot 2020-04-28 at 12 57 26 PM
Screen Shot 2020-04-28 at 12 58 17 PM

Copy link
Contributor Author

@micajuine-ho micajuine-ho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhouyx Should the tests in test-analytics-root.js also be applied to theEmbedAnalyticsRoot within that file?

Comment on lines 1511 to 1521
this.root
.getAmpElementOrElements(
context.parentElement || context,
selectors[i],
selectionMethod,
multiSelectorVisibilityOn
)
.then(elements => {
for (let j = 0; j < elements.length; j++) {
unlistenPromises.push(
visibilityManagerPromise.then(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this seem correct?

return this.ampdoc.whenReady().then(() => {
const results = [];
const foundElements = scopedQuerySelectorAll(
dev().assertElement(this.ampdoc.getBody()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you confirm that if embed would get its own ampdoc? If not I think we still need to pass in the context

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current implantation uses this.getRoot().querySelector(selector), which I think is a better approach.

extensions/amp-analytics/0.1/analytics-root.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/analytics-root.js Outdated Show resolved Hide resolved
@micajuine-ho
Copy link
Contributor Author

PTAL @zhouyx

}
const elements = [];
for (let i = 0; i < foundElements.length; i++) {
if (this.contains(foundElements[i])) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.contains?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used in the original getAmpElements(), to make sure that the element is a descendant of the analytics-root: defined here

extensions/amp-analytics/0.1/analytics-root.js Outdated Show resolved Hide resolved
@micajuine-ho
Copy link
Contributor Author

@zhouyx Ready for another round of review

for (let i = 0; i < elementsLists.length; i++) {
this.verifyAmpElements_(elementsLists[i], selectors[i]);
for (let j = 0; j < elementsLists[i].length; j++) {
if (uniqueElements.indexOf(elementsLists[i][j]) === -1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little concern about the performance here. Would it be faster to assign a unique attribute to element instead? so that we don't need to iterate the array everytime. Could you please verify?

cc @jridgewell who might know : ) What's the best way to find unique element out of an array?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For small lists, this will be quick enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhouyx I think the list will be relatively small. What do you think?

extensions/amp-analytics/0.1/analytics-root.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/events.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/events.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/test/test-analytics-root.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/test/test-analytics-root.js Outdated Show resolved Hide resolved
@micajuine-ho
Copy link
Contributor Author

micajuine-ho commented Mar 31, 2020

@zhouyx Ready for another round of review

  • Changed getReadyPromise()
  • Condensed getAmpElements()'s work into getElementsByQuerySelectorAll_()
  • Fixed tests (including tests for getReadyPromise())

Changes in the next PR:

  • Add additional restriction to getElementsByQuerySelectorAll_() to only return elements with data-vars-*
  • Change intersectionObserver polyfill to support non-amp elements (add asyncGetLayoutRect()), remove verifyAmpElements() from getElementsByQuerySelectorAll_()

These changes will not change the code flow/structure seen in this PR.

extensions/amp-analytics/0.1/events.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/events.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/analytics-root.js Outdated Show resolved Hide resolved
'Cannot have selectionMethod defined with an array selector.'
);
return this.getElementsByQuerySelectorAll_(selectors).then((elements) =>
elements.filter((element, index) => elements.indexOf(element) === index)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this goes into getElementsByQuerySelectorAll_ as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't make a much of a difference when the elements list is small, but it does take O(n^2) time... I'm a bit concerned when one try to select every single <p> of a document. could you please bench mark the performance with a reasonable element count? Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a webpage with 30 amp-ad elements and no throttle:

  • 0.024ms avg with O(n^2)
 elements.filter((element, index) => elements.indexOf(element) === index)
  • 0.058ms avg with O(n)
for (let i = 0; i < elements.length; i++) {
   if (!elements[i].hasAttribute('special')) {
      elements[i].setAttribute('special', 'yes');
      returnElements.push(elements[i]);
   }
}

Not sure if there's. better way to do O(n)

extensions/amp-analytics/0.1/test/test-analytics-root.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/test/test-events.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/test/test-events.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/test/test-events.js Outdated Show resolved Hide resolved
@micajuine-ho
Copy link
Contributor Author

@zhouyx Ready for review

Copy link
Contributor

@zhouyx zhouyx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't reply to the original comment thread, but re https://github.com/ampproject/amphtml/pull/26886/files#discussion_r402566861
Thanks for running the benchmark, I can only think about apply special attribute and remove it afterwards as the way to achieve O(n).
SG, let's go with the your approach.

extensions/amp-analytics/0.1/analytics-root.js Outdated Show resolved Hide resolved
expect(error).to.match(/required to be an AMP element/);
});
});

describe('get amp elements', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we deprecate usage of analyticsRoot.getElement() afterwards? If so is the plan to fix related test in the following PR?

extensions/amp-analytics/0.1/test/test-analytics-root.js Outdated Show resolved Hide resolved
child2.classList.add('i-amphtml-element');
});

it('should find all elements by selector', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping on this comment : )

extensions/amp-analytics/0.1/test/test-events.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/test/test-events.js Outdated Show resolved Hide resolved
extensions/amp-analytics/0.1/test/test-events.js Outdated Show resolved Hide resolved
@micajuine-ho micajuine-ho requested review from zhouyx and removed request for zhouyx April 20, 2020 19:25
@micajuine-ho
Copy link
Contributor Author

@zhouyx Can you approve again for the bundle size check?

@micajuine-ho micajuine-ho merged commit e1233a7 into ampproject:master Apr 20, 2020
@micajuine-ho micajuine-ho added this to In progress in Performance Improvements via automation Apr 28, 2020
@micajuine-ho micajuine-ho moved this from In progress to Done in Performance Improvements Apr 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging this pull request may close these issues.

None yet

5 participants