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

fix(browser): Don't assume window.document is available #11602

Merged
merged 2 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/browser-utils/src/metrics/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { GLOBAL_OBJ } from '@sentry/utils';

export const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
export const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ &
// document is not available in all browser environments (webworkers). We make it optional so you have to explicitly check for it
Omit<Window, 'document'> &
Partial<Pick<Window, 'document'>>;
11 changes: 7 additions & 4 deletions packages/browser-utils/src/metrics/web-vitals/getLCP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import { WINDOW } from '../types';
import { bindReporter } from './lib/bindReporter';
import { getActivationStart } from './lib/getActivationStart';
import { getVisibilityWatcher } from './lib/getVisibilityWatcher';
Expand Down Expand Up @@ -82,10 +83,12 @@ export const onLCP = (onReport: LCPReportCallback, opts: ReportOpts = {}) => {
// stops LCP observation, it's unreliable since it can be programmatically
// generated. See: https://github.com/GoogleChrome/web-vitals/issues/75
['keydown', 'click'].forEach(type => {
// Wrap in a setTimeout so the callback is run in a separate task
// to avoid extending the keyboard/click handler to reduce INP impact
// https://github.com/GoogleChrome/web-vitals/issues/383
addEventListener(type, () => setTimeout(stopListening, 0), true);
if (WINDOW.document) {
// Wrap in a setTimeout so the callback is run in a separate task
// to avoid extending the keyboard/click handler to reduce INP impact
// https://github.com/GoogleChrome/web-vitals/issues/383
addEventListener(type, () => setTimeout(stopListening, 0), true);
}
});

onHidden(stopListening);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ const initHiddenTime = () => {
// that visibility state is always 'hidden' during prerendering, so we have
// to ignore that case until prerendering finishes (see: `prerenderingchange`
// event logic below).
return WINDOW.document.visibilityState === 'hidden' && !WINDOW.document.prerendering ? 0 : Infinity;
firstHiddenTime = WINDOW.document!.visibilityState === 'hidden' && !WINDOW.document!.prerendering ? 0 : Infinity;
};

const onVisibilityUpdate = (event: Event) => {
// If the document is 'hidden' and no previous hidden timestamp has been
// set, update it based on the current event data.
if (WINDOW.document.visibilityState === 'hidden' && firstHiddenTime > -1) {
if (WINDOW.document!.visibilityState === 'hidden' && firstHiddenTime > -1) {
// If the event is a 'visibilitychange' event, it means the page was
// visible prior to this change, so the event timestamp is the first
// hidden time.
Expand All @@ -41,7 +41,8 @@ const onVisibilityUpdate = (event: Event) => {
firstHiddenTime = event.type === 'visibilitychange' ? event.timeStamp : 0;

// Remove all listeners now that a `firstHiddenTime` value has been set.
removeChangeListeners();
removeEventListener('visibilitychange', onVisibilityUpdate, true);
removeEventListener('prerenderingchange', onVisibilityUpdate, true);
}
};

Expand All @@ -54,18 +55,13 @@ const addChangeListeners = () => {
addEventListener('prerenderingchange', onVisibilityUpdate, true);
};

const removeChangeListeners = () => {
removeEventListener('visibilitychange', onVisibilityUpdate, true);
removeEventListener('prerenderingchange', onVisibilityUpdate, true);
};

export const getVisibilityWatcher = () => {
if (firstHiddenTime < 0) {
if (WINDOW.document && firstHiddenTime < 0) {
// If the document is hidden when this code runs, assume it was hidden
// since navigation start. This isn't a perfect heuristic, but it's the
// best we can do until an API is available to support querying past
// visibilityState.
firstHiddenTime = initHiddenTime();
initHiddenTime();
addChangeListeners();
}
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export const initMetric = <MetricName extends MetricType['name']>(name: MetricNa
let navigationType: MetricType['navigationType'] = 'navigate';

if (navEntry) {
if (WINDOW.document.prerendering || getActivationStart() > 0) {
if ((WINDOW.document && WINDOW.document.prerendering) || getActivationStart() > 0) {
navigationType = 'prerender';
} else if (WINDOW.document.wasDiscarded) {
} else if (WINDOW.document && WINDOW.document.wasDiscarded) {
navigationType = 'restore';
} else if (navEntry.type) {
navigationType = navEntry.type.replace(/_/g, '-') as MetricType['navigationType'];
Expand Down
13 changes: 8 additions & 5 deletions packages/browser-utils/src/metrics/web-vitals/lib/onHidden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ export interface OnHiddenCallback {

export const onHidden = (cb: OnHiddenCallback) => {
const onHiddenOrPageHide = (event: Event) => {
if (event.type === 'pagehide' || WINDOW.document.visibilityState === 'hidden') {
if (event.type === 'pagehide' || (WINDOW.document && WINDOW.document.visibilityState === 'hidden')) {
cb(event);
}
};
addEventListener('visibilitychange', onHiddenOrPageHide, true);
// Some browsers have buggy implementations of visibilitychange,
// so we use pagehide in addition, just to be safe.
addEventListener('pagehide', onHiddenOrPageHide, true);

if (WINDOW.document) {
addEventListener('visibilitychange', onHiddenOrPageHide, true);
// Some browsers have buggy implementations of visibilitychange,
// so we use pagehide in addition, just to be safe.
addEventListener('pagehide', onHiddenOrPageHide, true);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import { WINDOW } from '../../types';

export const whenActivated = (callback: () => void) => {
if (WINDOW.document.prerendering) {
if (WINDOW.document && WINDOW.document.prerendering) {
addEventListener('prerenderingchange', () => callback(), true);
} else {
callback();
Expand Down
4 changes: 2 additions & 2 deletions packages/browser-utils/src/metrics/web-vitals/onTTFB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export const TTFBThresholds: MetricRatingThresholds = [800, 1800];
* @param callback
*/
const whenReady = (callback: () => void) => {
if (WINDOW.document.prerendering) {
if (WINDOW.document && WINDOW.document.prerendering) {
whenActivated(() => whenReady(callback));
} else if (WINDOW.document.readyState !== 'complete') {
} else if (WINDOW.document && WINDOW.document.readyState !== 'complete') {
addEventListener('load', () => whenReady(callback), true);
} else {
// Queue a task so the callback runs after `loadEventEnd`.
Expand Down
4 changes: 3 additions & 1 deletion packages/browser/src/tracing/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,8 @@ function registerInteractionListener(
};

['click'].forEach(type => {
addEventListener(type, registerInteractionTransaction, { once: false, capture: true });
if (WINDOW.document) {
addEventListener(type, registerInteractionTransaction, { once: false, capture: true });
}
});
}
Loading