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

Make the getHeaderHeightWithoutNav() function more defensive. #6672

Merged
merged 19 commits into from
Mar 24, 2023

Conversation

techanvil
Copy link
Collaborator

@techanvil techanvil commented Mar 2, 2023

Summary

Addresses issue:

Relevant technical choices

Having seen some related error reports from production sites, this PR adds some defensive programming to the getHeaderHeightWithoutNav() function.

PR Author Checklist

  • My code is tested and passes existing unit tests.
  • My code has an appropriate set of unit tests which all pass.
  • My code is backward-compatible with WordPress 5.2 and PHP 5.6.
  • My code follows the WordPress coding standards.
  • My code has proper inline documentation.
  • I have added a QA Brief on the issue linked above.
  • I have signed the Contributor License Agreement (see https://cla.developers.google.com/).

Do not alter or remove anything below. The following sections will be managed by moderators only.

Code Reviewer Checklist

  • Run the code.
  • Ensure the acceptance criteria are satisfied.
  • Reassess the implementation with the IB.
  • Ensure no unrelated changes are included.
  • Ensure CI checks pass.
  • Check Storybook where applicable.
  • Ensure there is a QA Brief.

Merge Reviewer Checklist

  • Ensure the PR has the correct target branch.
  • Double-check that the PR is okay to be merged.
  • Ensure the corresponding issue has a ZenHub release assigned.
  • Add a changelog message to the issue.

@techanvil techanvil changed the base branch from develop to main March 2, 2023 10:34
@github-actions
Copy link

github-actions bot commented Mar 2, 2023

Size Change: +681 B (0%)

Total Size: 1.24 MB

Filename Size Change
./dist/assets/js/googlesitekit-activation-********************.js 27.3 kB +81 B (0%)
./dist/assets/js/googlesitekit-adminbar-********************.js 36.1 kB +83 B (0%)
./dist/assets/js/googlesitekit-api-********************.js 9.56 kB -5 B (0%)
./dist/assets/js/googlesitekit-components-gm2-********************.js 5.98 kB -1 B (0%)
./dist/assets/js/googlesitekit-components-gm3-********************.js 9.73 kB -2 B (0%)
./dist/assets/js/googlesitekit-data-********************.js 2.15 kB -1 B (0%)
./dist/assets/js/googlesitekit-datastore-forms-********************.js 9.23 kB -1 B (0%)
./dist/assets/js/googlesitekit-datastore-location-********************.js 2.09 kB +1 B (0%)
./dist/assets/js/googlesitekit-datastore-site-********************.js 16.3 kB +2 B (0%)
./dist/assets/js/googlesitekit-datastore-user-********************.js 22.2 kB -2 B (0%)
./dist/assets/js/googlesitekit-entity-dashboard-********************.js 63.8 kB +74 B (0%)
./dist/assets/js/googlesitekit-main-dashboard-********************.js 69.6 kB +100 B (0%)
./dist/assets/js/googlesitekit-modules-adsense-********************.js 68.5 kB +4 B (0%)
./dist/assets/js/googlesitekit-modules-analytics-4-********************.js 26.4 kB -3 B (0%)
./dist/assets/js/googlesitekit-modules-analytics-********************.js 79 kB +15 B (0%)
./dist/assets/js/googlesitekit-modules-********************.js 20.5 kB -1 B (0%)
./dist/assets/js/googlesitekit-modules-optimize-********************.js 18.7 kB +21 B (0%)
./dist/assets/js/googlesitekit-modules-pagespeed-insights-********************.js 21.2 kB +4 B (0%)
./dist/assets/js/googlesitekit-modules-search-console-********************.js 44.7 kB -11 B (0%)
./dist/assets/js/googlesitekit-modules-tagmanager-********************.js 31.8 kB -17 B (0%)
./dist/assets/js/googlesitekit-settings-********************.js 52.5 kB +75 B (0%)
./dist/assets/js/googlesitekit-splash-********************.js 70 kB +88 B (0%)
./dist/assets/js/googlesitekit-user-input-********************.js 44.8 kB +76 B (0%)
./dist/assets/js/googlesitekit-vendor-********************.js 319 kB +26 B (0%)
./dist/assets/js/googlesitekit-widgets-********************.js 16.4 kB +2 B (0%)
./dist/assets/js/googlesitekit-wp-dashboard-********************.js 61 kB +71 B (0%)
./dist/assets/js/runtime-********************.js 1.26 kB +2 B (0%)
ℹ️ View Unchanged
Filename Size
./dist/assets/css/googlesitekit-admin-css-********************.min.css 48.2 kB
./dist/assets/css/googlesitekit-adminbar-css-********************.min.css 11.1 kB
./dist/assets/css/googlesitekit-wp-dashboard-css-********************.min.css 7.31 kB
./dist/assets/js/29-********************.js 2.8 kB
./dist/assets/js/30-********************.js 2.28 kB
./dist/assets/js/31-********************.js 3.72 kB
./dist/assets/js/32-********************.js 3.12 kB
./dist/assets/js/analytics-advanced-tracking-********************.js 769 B
./dist/assets/js/googlesitekit-datastore-ui-********************.js 9.32 kB
./dist/assets/js/googlesitekit-i18n-********************.js 3.92 kB
./dist/assets/js/googlesitekit-polyfills-********************.js 377 B

compressed-size-action

@techanvil
Copy link
Collaborator Author

Closing for now as we'll need to create a separate issue to resolve this.

@techanvil techanvil closed this Mar 2, 2023
@techanvil techanvil deleted the bug/6109-incorrect-view-notifications-fix branch March 2, 2023 15:04
@aaemnnosttv aaemnnosttv restored the bug/6109-incorrect-view-notifications-fix branch March 3, 2023 20:58
@aaemnnosttv aaemnnosttv reopened this Mar 3, 2023
@aaemnnosttv aaemnnosttv changed the base branch from main to develop March 3, 2023 20:58
@aaemnnosttv aaemnnosttv marked this pull request as draft March 3, 2023 23:43
Copy link
Collaborator

@aaemnnosttv aaemnnosttv left a comment

Choose a reason for hiding this comment

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

Thanks @techanvil – one more thing I wanted to mention is that we might also want to consider adding something where its used in case the function were to change there where this could be reintroduced. Perhaps just adding a test or two to guard against a regression here would be a good idea.

rootMargin: `-${ getHeaderHeightWithoutNav(
breakpoint
) }px 0px 0px 0px`,

assets/js/util/scroll.js Outdated Show resolved Hide resolved
assets/js/util/scroll.js Outdated Show resolved Hide resolved
* @param {string} breakpoint The current breakpoint.
* @return {number} The height of the sticky WordPress admin bar, if present.
*/
function getWordPressAdminBarHeight( breakpoint ) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Regarding the use of a named breakpoint, are we sure our references in JS are aligned with the same in CSS? I realize the use here dates back quite a few releases but it seems a bit strange to rely on that when we're querying for elements and all. It could be that the current errors may only happen on the exact boundary of a breakpoint (we've seen some odd things happen at 600px before specifically for this reason).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I can confirm the BREAKPOINT_SMALL / BREAKPOINT_TABLET crossover does correlate precisely to the breakpoint at which the WordPress admin bar changes its stickiness.

However, it's a valid concern anyway, I think, considering that useBreakpoint() uses a throttled value from useWindowWidth(), which means it could be using a stale value when determining the header height...

I think the additional defensive measures being proposed here would be enough to mitigate this edge case, though.

// Otherwise, we use the value of the bottom of the Site Kit header's bounding box as this will take into account the sticky WordPress admin bar.
return breakpoint === BREAKPOINT_SMALL
? header.offsetHeight
: header.getBoundingClientRect().bottom;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since .bottom could be negative here, should we have an additional condition to return 0 in that case to avoid returning a negative number here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm - the way this way originally written, there was a check for a negative result in the caller, to catch unexpected cases where the header is non-sticky. However, as you've pointed out we can make a more reliable check for stickiness in the caller, I think it would be a good idea to simply assume the header is sticky within this function - but, do what you've suggested just to handle the unexpected case here, i.e. do return 0 if .bottom is negative.

@@ -81,24 +122,26 @@ export function getHeaderHeightWithoutNav( breakpoint ) {
'.react-joyride__overlay'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Another potential enhancement would be to use body.googlesitekit-showing-feature-tour here since this is where the actual style to make it not sticky is, in addition to being our own class :)

body.googlesitekit-showing-feature-tour & {
position: static;
}

Alternatively, perhaps we can use other queries to better determine whether the header is sticky or not? i.e. the page 0 < scrollY and the header's top == 0 ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Aha - thanks for pointing that out, I hadn't noticed we're explicitly setting the position to be non-sticky, and had assumed it was some DOM-related aspect, although hadn't dug into it (obviously!).

Given this is the case, I'd suggest we simply get the calculated style for the header and check whether the position property is sticky. I'm interested in your idea of using scrollY and top but that could give some indeterminate results (say both scrollY and top are 0, we wouldn't know if the header is sticky or not).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess my thought was to make the functions more about the position of the header in the viewport rather than specific positioning rules. It's also worth noting that the WP admin bar does not use position:sticky but fixed so there is more than one way to achieve that kind of position. No problem in checking for sticky on our own component since we own that style, but it might be simpler to think about it independent of that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@aaemnnosttv, I do take your point, but I can't think of how we can determine the stickiness of the header purely based on its position in the viewport, or otherwise change the logic to avoid needing to make this determination.

Unless you've got any suggestions on how to achieve this, we may have no better alternative to checking the CSS position property...

assets/js/util/scroll.js Outdated Show resolved Hide resolved
* @param {string} breakpoint The current breakpoint.
* @return {number} The height of the sticky Site Kit header including the sticky WordPress admin bar when it's present.
*/
function getGoogleSiteKitHeaderHeight( breakpoint ) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This function could be misinterpreted to be the same as getHeaderHeight above if using by name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, good point. I would suggest changing the exported functions to getStickyHeaderHeight() and getStickyHeaderHeightWithoutNav() to be a bit more explicit, while these non-exported functions can remain as they are...

@techanvil
Copy link
Collaborator Author

Thanks @techanvil – one more thing I wanted to mention is that we might also want to consider adding something where its used in case the function were to change there where this could be reintroduced. Perhaps just adding a test or two to guard against a regression here would be a good idea.

rootMargin: `-${ getHeaderHeightWithoutNav(
breakpoint
) }px 0px 0px 0px`,

Yes, I think it's worth adding a guard at the point of use, as a failsafe. I'd suggest calling Math.abs() on the result, as that's what we are already doing in a similar situation in the WidgetAreaRenderer:

const top = Math.abs( getStickyHeaderHeight( breakpoint ) + gap );

We could add some tests, too, I think we should be able to test this one using JSDom... :)

@aaemnnosttv aaemnnosttv changed the base branch from develop to main March 10, 2023 20:04
@aaemnnosttv
Copy link
Collaborator

Yes, I think it's worth adding a guard at the point of use, as a failsafe. I'd suggest calling Math.abs() on the result, as that's what we are already doing in a similar situation in the WidgetAreaRenderer:

const top = Math.abs( getStickyHeaderHeight( breakpoint ) + gap );

We could add some tests, too, I think we should be able to test this one using JSDom... :)

Math.abs would prevent the error we've reproduced from the double negative, but it wouldn't protect against the more exotic values like NaN or non-finite values. Let's not get too carried away here but it does seem problematic to construct a negative number using a template string 😄

@techanvil
Copy link
Collaborator Author

techanvil commented Mar 13, 2023

Yes, I think it's worth adding a guard at the point of use, as a failsafe. I'd suggest calling Math.abs() on the result, as that's what we are already doing in a similar situation in the WidgetAreaRenderer:

const top = Math.abs( getStickyHeaderHeight( breakpoint ) + gap );

We could add some tests, too, I think we should be able to test this one using JSDom... :)

Math.abs would prevent the error we've reproduced from the double negative, but it wouldn't protect against the more exotic values like NaN or non-finite values. Let's not get too carried away here but it does seem problematic to construct a negative number using a template string 😄

I do agree, it's really just for consistency I suggested that approach, given we already do that in a similar scenario in WidgetAreaRenderer.

Hmm. Well, arguably it is going a bit overboard to check the value in two places, but I've created a utility function, finiteNumberOrZero(), to be used both in getStickyHeaderHeightWithoutNav() and where the rootMargin is constructed (i.e. on the return value of getStickyHeaderHeightWithoutNav() which is where it feels a little excessive). And, I've moved the negative sign out of the string template.

Obviously there's still room for manoeuvre here, please see what you think.

@techanvil techanvil force-pushed the bug/6109-incorrect-view-notifications-fix branch from 282ee05 to f764e5e Compare March 13, 2023 12:04
@techanvil techanvil changed the base branch from main to develop March 13, 2023 12:45
@techanvil techanvil marked this pull request as ready for review March 13, 2023 16:39
Copy link
Collaborator

@aaemnnosttv aaemnnosttv left a comment

Choose a reason for hiding this comment

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

LGTM, thanks @techanvil !

@aaemnnosttv aaemnnosttv merged commit 019189d into develop Mar 24, 2023
@aaemnnosttv aaemnnosttv deleted the bug/6109-incorrect-view-notifications-fix branch March 24, 2023 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants