diff --git a/packages/browser-utils/src/instrument/history.ts b/packages/browser-utils/src/instrument/history.ts index 0a166c6fe111..ad5ecb75f2ed 100644 --- a/packages/browser-utils/src/instrument/history.ts +++ b/packages/browser-utils/src/instrument/history.ts @@ -1,5 +1,5 @@ -import { addHandler, fill, maybeInstrument, supportsHistory, triggerHandlers } from '@sentry/core'; import type { HandlerDataHistory } from '@sentry/core'; +import { addHandler, fill, maybeInstrument, supportsHistory, triggerHandlers } from '@sentry/core'; import { WINDOW } from '../types'; let lastHref: string | undefined; @@ -19,29 +19,26 @@ export function addHistoryInstrumentationHandler(handler: (data: HandlerDataHist } function instrumentHistory(): void { - if (!supportsHistory()) { - return; - } - - const oldOnPopState = WINDOW.onpopstate; - WINDOW.onpopstate = function (this: WindowEventHandlers, ...args: unknown[]) { + // The `popstate` event may also be triggered on `pushState`, but it may not always reliably be emitted by the browser + // Which is why we also monkey-patch methods below, in addition to this + WINDOW.addEventListener('popstate', () => { const to = WINDOW.location.href; // keep track of the current URL state, as we always receive only the updated state const from = lastHref; lastHref = to; - const handlerData: HandlerDataHistory = { from, to }; - triggerHandlers('history', handlerData); - if (oldOnPopState) { - // Apparently this can throw in Firefox when incorrectly implemented plugin is installed. - // https://github.com/getsentry/sentry-javascript/issues/3344 - // https://github.com/bugsnag/bugsnag-js/issues/469 - try { - return oldOnPopState.apply(this, args); - } catch (_oO) { - // no-empty - } + + if (from === to) { + return; } - }; + + const handlerData = { from, to } satisfies HandlerDataHistory; + triggerHandlers('history', handlerData); + }); + + // Just guard against this not being available, in weird environments + if (!supportsHistory()) { + return; + } function historyReplacementFunction(originalHistoryFunction: () => void): () => void { return function (this: History, ...args: unknown[]): void { @@ -52,7 +49,12 @@ function instrumentHistory(): void { const to = String(url); // keep track of the current URL state, as we always receive only the updated state lastHref = to; - const handlerData: HandlerDataHistory = { from, to }; + + if (from === to) { + return; + } + + const handlerData = { from, to } satisfies HandlerDataHistory; triggerHandlers('history', handlerData); } return originalHistoryFunction.apply(this, args); diff --git a/packages/core/src/utils-hoist/index.ts b/packages/core/src/utils-hoist/index.ts index befb934905a2..b643f2e46d84 100644 --- a/packages/core/src/utils-hoist/index.ts +++ b/packages/core/src/utils-hoist/index.ts @@ -108,6 +108,7 @@ export { supportsDOMException, supportsErrorEvent, supportsFetch, + supportsHistory, supportsNativeFetch, supportsReferrerPolicy, supportsReportingObserver, @@ -178,4 +179,3 @@ export { vercelWaitUntil } from './vercelWaitUntil'; export { SDK_VERSION } from './version'; export { getDebugImagesForResources, getFilenameToDebugIdMap } from './debug-ids'; export { escapeStringForRegex } from './vendor/escapeStringForRegex'; -export { supportsHistory } from './vendor/supportsHistory'; diff --git a/packages/core/src/utils-hoist/supports.ts b/packages/core/src/utils-hoist/supports.ts index d1ae4b5b96d4..031402bb78b1 100644 --- a/packages/core/src/utils-hoist/supports.ts +++ b/packages/core/src/utils-hoist/supports.ts @@ -6,8 +6,6 @@ const WINDOW = GLOBAL_OBJ as unknown as Window; declare const EdgeRuntime: string | undefined; -export { supportsHistory } from './vendor/supportsHistory'; - /** * Tells whether current environment supports ErrorEvent objects * {@link supportsErrorEvent}. @@ -56,6 +54,16 @@ export function supportsDOMException(): boolean { } } +/** + * Tells whether current environment supports History API + * {@link supportsHistory}. + * + * @returns Answer to the given question. + */ +export function supportsHistory(): boolean { + return 'history' in WINDOW; +} + /** * Tells whether current environment supports Fetch API * {@link supportsFetch}. diff --git a/packages/core/src/utils-hoist/vendor/supportsHistory.ts b/packages/core/src/utils-hoist/vendor/supportsHistory.ts deleted file mode 100644 index bd0fbddec35f..000000000000 --- a/packages/core/src/utils-hoist/vendor/supportsHistory.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Based on https://github.com/angular/angular.js/pull/13945/files -// The MIT License - -// Copyright (c) 2010-2016 Google, Inc. http://angularjs.org - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import { GLOBAL_OBJ } from '../worldwide'; - -const WINDOW = GLOBAL_OBJ as unknown as Window; - -/** - * Tells whether current environment supports History API - * {@link supportsHistory}. - * - * @returns Answer to the given question. - */ -export function supportsHistory(): boolean { - // NOTE: in Chrome App environment, touching history.pushState, *even inside - // a try/catch block*, will cause Chrome to output an error to console.error - // borrowed from: https://github.com/angular/angular.js/pull/13945/files - // TODO(v9): Remove this custom check, it is pretty old and likely not needed anymore - const chromeVar = (WINDOW as { chrome?: { app?: { runtime?: unknown } } }).chrome; - const isChromePackagedApp = chromeVar && chromeVar.app && chromeVar.app.runtime; - const hasHistoryApi = 'history' in WINDOW && !!WINDOW.history.pushState && !!WINDOW.history.replaceState; - - return !isChromePackagedApp && hasHistoryApi; -}