Skip to content

Commit

Permalink
fix(browser): Don't assume window.document is available (#11602)
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst authored Apr 16, 2024
1 parent 39c8290 commit ee4091e
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 26 deletions.
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 @@ -448,6 +448,8 @@ function registerInteractionListener(
};

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

0 comments on commit ee4091e

Please sign in to comment.