Skip to content

Commit b6b8833

Browse files
committed
handle multiple events fired by multiple triggers, prioritize navigationApi
1 parent bc40ce5 commit b6b8833

File tree

2 files changed

+102
-53
lines changed

2 files changed

+102
-53
lines changed

examples/web/examples/navigation/index.js

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,28 @@ function testNavigationApiHash() {
156156

157157
function testNavigationApiRoute(route, navigationType) {
158158
if ('navigation' in window) {
159-
try {
160-
159+
try {
160+
// Log current navigation.activation.navigationType before navigation
161+
console.log('🧭 Before Navigation API navigate():', {
162+
currentNavigationType: window.navigation.activation?.navigationType,
163+
targetRoute: route,
164+
method: navigationType
165+
});
161166

162167
// Use Navigation API navigate method
163168
window.navigation.navigate(route);
164169

170+
// Log navigation.activation.navigationType after navigation
171+
setTimeout(() => {
172+
console.log('🧭 After Navigation API navigate():', {
173+
newNavigationType: window.navigation.activation?.navigationType,
174+
currentUrl: location.href,
175+
expectedInstrumentation: {
176+
'browser.navigation.type': window.navigation.activation?.navigationType || 'push'
177+
}
178+
});
179+
}, 50);
180+
165181
// Update content after navigation
166182
setTimeout(() => {
167183
document.getElementById('nav-api-content').innerHTML =
@@ -170,6 +186,7 @@ function testNavigationApiRoute(route, navigationType) {
170186
<p><strong>Target:</strong> <code>${route}</code></p>
171187
<p><strong>Current URL:</strong> <code>${window.location.href}</code></p>
172188
<p><strong>Navigation API:</strong> Supported ✓</p>
189+
<button id="navApiHashBtn" style="display: none;">Nav API: Hash</button>
173190
<p class="console-note">📊 Check console for detailed navigation events and instrumentation data!</p>
174191
</div>`;
175192
}, 100);
@@ -201,13 +218,27 @@ function testNavigationApiRoute(route, navigationType) {
201218
}
202219

203220
window.addEventListener('popstate', handleRouteChange);
204-
const loadTimeSetup = () => {
205-
handleRouteChange();
221+
222+
// Add event listeners when DOM is loaded
223+
document.addEventListener('DOMContentLoaded', () => {
206224
attachEventListenersToLinks();
207225

208-
// Add event listeners
226+
// Hash change button
209227
document.getElementById('hashChangeBtn').addEventListener('click', testHashChange);
228+
229+
// Back button
210230
document.getElementById('backBtn').addEventListener('click', goBack);
211-
document.getElementById('navApiHashBtn').addEventListener('click', testNavigationApiHash);
231+
232+
// Navigation API hash button - only show if Navigation API is available
233+
const navApiHashBtn = document.getElementById('navApiHashBtn');
234+
if ('navigation' in window) {
235+
navApiHashBtn.addEventListener('click', testNavigationApiHash);
236+
} else {
237+
navApiHashBtn.style.display = 'none';
238+
}
239+
});
240+
241+
const loadTimeSetup = () => {
242+
handleRouteChange();
212243
};
213244
window.addEventListener('load', loadTimeSetup);

packages/instrumentation-browser-navigation/src/instrumentation.ts

Lines changed: 65 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -75,29 +75,30 @@ export class BrowserNavigationInstrumentation extends InstrumentationBase<Browse
7575
*/
7676
private _onSoftNavigation(changeState: string | null | undefined, navigationEvent?: any) {
7777
const referrer = this.oldUrl;
78-
// For Navigation API events, always process as they may contain important state changes
79-
// For other events, skip only if URL hasn't changed AND it's not a hash-only change
80-
if (changeState !== 'navigate' && referrer === location.href) {
81-
// Still allow hash changes even if URLs appear the same (normalization issues)
82-
const isHashChange = this._isHashChange(referrer, location.href) || changeState === 'hashchange';
83-
if (!isHashChange) {
84-
return;
85-
}
78+
const currentUrl = (changeState === 'navigate' && navigationEvent?.destination?.url)
79+
? navigationEvent.destination.url
80+
: location.href;
81+
82+
if (referrer === currentUrl) {
83+
return;
8684
}
87-
const navType = this._mapChangeStateToType(changeState);
88-
const sameDocument = this._determineSameDocument(changeState, navigationEvent, referrer, window.location.href);
89-
const hashChange = this._determineHashChange(changeState, navigationEvent, referrer, window.location.href);
85+
86+
const navType = this._mapChangeStateToType(changeState, navigationEvent);
87+
const sameDocument = this._determineSameDocument(changeState, navigationEvent, referrer, currentUrl);
88+
const hashChange = this._determineHashChange(changeState, navigationEvent, referrer, currentUrl);
9089
const navLogRecord: LogRecord = {
9190
eventName: EVENT_NAME,
9291
attributes: {
93-
[URL_FULL_ATTRIBUTE]: this._sanitizeUrl(window.location.href),
92+
[URL_FULL_ATTRIBUTE]: this._sanitizeUrl(currentUrl),
9493
[NAV_SAME_DOCUMENT]: sameDocument,
9594
[NAV_HASH_CHANGE]: hashChange,
9695
...(navType ? { [NAV_TYPE]: navType } : {}),
9796
},
9897
};
9998
this._applyCustomLogRecordData(navLogRecord, this.applyCustomLogRecordData);
10099
this.eventLogger?.emit(navLogRecord);
100+
101+
this.oldUrl = currentUrl;
101102
}
102103

103104
public _setLogger(eventLogger: Logger) {
@@ -122,52 +123,41 @@ export class BrowserNavigationInstrumentation extends InstrumentationBase<Browse
122123
override enable() {
123124
const cfg = this.getConfig() as BrowserNavigationInstrumentationConfig;
124125
const useNavigationApiIfAvailable = !!cfg.useNavigationApiIfAvailable;
126+
const hasNavigationApi = useNavigationApiIfAvailable && (window as any).navigation;
125127

126-
// Always patch history API
127-
this._patchHistoryApi();
128+
// Only patch history API if Navigation API is not available
129+
if (!hasNavigationApi) {
130+
this._patchHistoryApi();
131+
}
128132

129133
// Always listen for page load
130134
this._waitForPageLoad();
131135

132-
// Always listen for popstate (back/forward)
133-
if (this._onPopStateHandler) {
134-
window.removeEventListener('popstate', this._onPopStateHandler);
135-
this._onPopStateHandler = undefined;
136-
}
137-
this._onPopStateHandler = () => {
138-
this._onSoftNavigation('popstate');
139-
this.oldUrl = location.href;
140-
};
141-
window.addEventListener('popstate', this._onPopStateHandler);
142-
143-
// Always listen for hashchange
144-
if (this._onHashChangeHandler) {
145-
window.removeEventListener('hashchange', this._onHashChangeHandler);
146-
this._onHashChangeHandler = undefined;
147-
}
148-
this._onHashChangeHandler = () => {
149-
this._onSoftNavigation('hashchange');
150-
this.oldUrl = location.href;
151-
};
152-
window.addEventListener('hashchange', this._onHashChangeHandler);
153-
154-
// Navigation API listener (experimental)
155-
if (this._onNavigateHandler) {
156-
const nav = (window as any).navigation;
157-
if (nav) {
158-
nav.removeEventListener('navigate', this._onNavigateHandler);
136+
if (hasNavigationApi) {
137+
if (this._onNavigateHandler) {
138+
const nav = (window as any).navigation;
139+
if (nav) {
140+
nav.removeEventListener('navigate', this._onNavigateHandler);
141+
}
142+
this._onNavigateHandler = undefined;
159143
}
160-
this._onNavigateHandler = undefined;
161-
}
162-
if (useNavigationApiIfAvailable && (window as any).navigation) {
144+
163145
this._onNavigateHandler = (event: any) => {
164146
this._onSoftNavigation('navigate', event);
165-
this.oldUrl = location.href;
166147
};
167148
(window as any).navigation.addEventListener(
168149
'navigate',
169150
this._onNavigateHandler
170151
);
152+
} else {
153+
if (this._onPopStateHandler) {
154+
window.removeEventListener('popstate', this._onPopStateHandler);
155+
this._onPopStateHandler = undefined;
156+
}
157+
this._onPopStateHandler = () => {
158+
this._onSoftNavigation('popstate');
159+
};
160+
window.addEventListener('popstate', this._onPopStateHandler);
171161
}
172162
}
173163

@@ -211,7 +201,6 @@ export class BrowserNavigationInstrumentation extends InstrumentationBase<Browse
211201
const oldUrl = plugin.oldUrl;
212202
if (url !== oldUrl) {
213203
plugin._onSoftNavigation(changeState);
214-
plugin.oldUrl = location.href;
215204
}
216205
return result;
217206
};
@@ -255,6 +244,8 @@ export class BrowserNavigationInstrumentation extends InstrumentationBase<Browse
255244
try {
256245
const a = new URL(fromUrl, window.location.origin);
257246
const b = new URL(toUrl, window.location.origin);
247+
// Only consider it a hash change if the base URL (origin + pathname + search) is identical
248+
// and only the hash portion differs
258249
return (
259250
a.origin === b.origin &&
260251
a.pathname === b.pathname &&
@@ -367,7 +358,34 @@ export class BrowserNavigationInstrumentation extends InstrumentationBase<Browse
367358
}
368359
}
369360

370-
private _mapChangeStateToType(changeState?: string | null): NavigationType | undefined {
361+
private _mapChangeStateToType(changeState?: string | null, navigationEvent?: any): NavigationType | undefined {
362+
// For Navigation API events, check if it's a hash change first
363+
if (changeState === 'navigate' && navigationEvent?.hashChange) {
364+
// Hash changes are always considered 'push' operations semantically
365+
return 'push';
366+
}
367+
368+
// For Navigation API events, determine type based on event properties
369+
if (changeState === 'navigate') {
370+
// Check if this is a back/forward navigation (traverse)
371+
if (navigationEvent?.navigationType === 'traverse') {
372+
return 'traverse';
373+
}
374+
375+
// Check if this is a replace operation
376+
if (navigationEvent?.navigationType === 'replace') {
377+
return 'replace';
378+
}
379+
380+
// Check if this is a reload
381+
if (navigationEvent?.navigationType === 'reload') {
382+
return 'reload';
383+
}
384+
385+
// Default to 'push' for new navigations (link clicks, programmatic navigation)
386+
return 'push';
387+
}
388+
371389
switch (changeState) {
372390
case 'pushState':
373391
return 'push';

0 commit comments

Comments
 (0)