Web Navigation Decorators
The Web Navigation decorators allow you to easily respond to browser navigation events in your extension services. These decorators provide a clean way to monitor and respond to page navigation, loading states, and errors.
Method Decorators
onBeforeNavigate
This decorator handles events that fire before a navigation event begins.
import { onBeforeNavigate, InjectableService } from 'deco-ext';
@InjectableService()
class NavigationTracker {
@onBeforeNavigate()
handleNavigation(details: browser.WebNavigation.OnBeforeNavigateDetailsType) {
console.log(`Navigation starting for tab ${details.tabId}`);
console.log(`Navigating to: ${details.url}`);
// Track or respond to the new navigation
this.logNavigation(details.url, details.tabId);
}
private logNavigation(url: string, tabId: number) {
// Log navigation or perform other actions
}
}
With parameter decorator:
import { onBeforeNavigate, navigationDetails, InjectableService } from 'deco-ext';
@InjectableService()
class NavigationMonitor {
@onBeforeNavigate()
trackNavigation(
@navigationDetails('tabId') tabId: number,
@navigationDetails('url') url: string,
@navigationDetails('frameId') frameId: number
) {
if (frameId === 0) {
console.log(`Main frame navigation in tab ${tabId}`);
console.log(`Navigating to: ${url}`);
this.analyzeNavigation(url);
} else {
console.log(`Subframe navigation to ${url}`);
}
}
private analyzeNavigation(url: string) {
// Analyze the navigation target
}
}
onNavigationCommitted
This decorator handles events that fire when a navigation is committed.
import { onNavigationCommitted, InjectableService } from 'deco-ext';
@InjectableService()
class NavigationMonitor {
@onNavigationCommitted()
handleCommitted(details: browser.WebNavigation.OnCommittedDetailsType) {
console.log(`Navigation committed in tab ${details.tabId}`);
console.log(`URL: ${details.url}`);
console.log(`Transition type: ${details.transitionType}`);
// Respond to committed navigation
if (details.transitionType === 'typed') {
console.log('User typed this URL directly');
}
}
}
With parameter decorator:
import { onNavigationCommitted, navigationCommittedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class NavigationMonitor {
@onNavigationCommitted()
trackCommitted(
@navigationCommittedDetails('tabId') tabId: number,
@navigationCommittedDetails('url') url: string,
@navigationCommittedDetails('transitionType') transitionType: string,
@navigationCommittedDetails('transitionQualifiers') qualifiers: string[]
) {
console.log(`Tab ${tabId} navigation committed to ${url}`);
console.log(`Transition: ${transitionType}`);
if (qualifiers.includes('from_address_bar')) {
console.log('Navigation originated from address bar');
}
}
}
onNavigationCompleted
This decorator handles events that fire when a navigation is completed.
import { onNavigationCompleted, InjectableService } from 'deco-ext';
@InjectableService()
class PageLoadTracker {
@onNavigationCompleted()
handleComplete(details: browser.WebNavigation.OnCompletedDetailsType) {
console.log(`Navigation completed in tab ${details.tabId}`);
console.log(`Loaded: ${details.url}`);
// Perform actions after navigation completes
this.analyzePage(details.tabId, details.url);
}
private analyzePage(tabId: number, url: string) {
// Analyze the loaded page
}
}
With parameter decorator:
import { onNavigationCompleted, navigationCompletedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class PageLoadTracker {
@onNavigationCompleted()
trackCompletion(
@navigationCompletedDetails('tabId') tabId: number,
@navigationCompletedDetails('url') url: string,
@navigationCompletedDetails('frameId') frameId: number,
@navigationCompletedDetails('timeStamp') timestamp: number
) {
const loadTime = new Date(timestamp).toLocaleTimeString();
console.log(`Tab ${tabId} frame ${frameId} completed loading ${url} at ${loadTime}`);
}
}
onDOMContentLoaded
This decorator handles events that fire when the DOM content of a page is loaded.
import { onDOMContentLoaded, InjectableService } from 'deco-ext';
@InjectableService()
class DOMLoadMonitor {
@onDOMContentLoaded()
handleDOMLoaded(details: browser.WebNavigation.OnDOMContentLoadedDetailsType) {
console.log(`DOM loaded in tab ${details.tabId}`);
console.log(`URL: ${details.url}`);
console.log(`Time: ${new Date(details.timeStamp).toLocaleTimeString()}`);
// Respond to DOM content loaded event
this.injectContentIfNeeded(details.tabId, details.url);
}
private injectContentIfNeeded(tabId: number, url: string) {
// Potentially inject content scripts or perform other actions
}
}
With parameter decorator:
import { onDOMContentLoaded, domContentLoadedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class DOMLoadMonitor {
@onDOMContentLoaded()
trackDOMLoad(
@domContentLoadedDetails('tabId') tabId: number,
@domContentLoadedDetails('url') url: string,
@domContentLoadedDetails('frameId') frameId: number
) {
if (frameId === 0) {
console.log(`Main document DOM loaded in tab ${tabId}`);
console.log(`URL: ${url}`);
if (url.includes('example.com')) {
this.enhancePage(tabId);
}
}
}
private enhancePage(tabId: number) {
// Enhance the page after DOM is loaded
}
}
onNavigationError
This decorator handles events that fire when a navigation fails due to an error.
import { onNavigationError, InjectableService } from 'deco-ext';
@InjectableService()
class NavigationErrorHandler {
@onNavigationError()
handleError(details: browser.WebNavigation.OnErrorOccurredDetailsType) {
console.log(`Navigation error in tab ${details.tabId}`);
console.log(`Failed URL: ${details.url}`);
console.log(`Error: ${details.error}`);
// Handle the navigation error
this.logNavigationError(details.tabId, details.url, details.error);
}
private logNavigationError(tabId: number, url: string, error: string) {
// Log error or notify user
}
}
With parameter decorator:
import { onNavigationError, navigationErrorDetails, InjectableService } from 'deco-ext';
@InjectableService()
class NavigationErrorHandler {
@onNavigationError()
trackErrors(
@navigationErrorDetails('tabId') tabId: number,
@navigationErrorDetails('url') url: string,
@navigationErrorDetails('error') error: string,
@navigationErrorDetails('frameId') frameId: number
) {
if (frameId === 0) {
console.log(`Main frame navigation error in tab ${tabId}`);
console.log(`Failed URL: ${url}`);
console.log(`Error: ${error}`);
// Provide fallback content or suggest alternatives
this.suggestAlternatives(tabId, url, error);
}
}
private suggestAlternatives(tabId: number, url: string, error: string) {
// Suggest alternative actions based on the error
}
}
onHistoryStateUpdated
This decorator handles events that fire when the page's history state is updated (pushState/replaceState).
import { onHistoryStateUpdated, InjectableService } from 'deco-ext';
@InjectableService()
class SPANavigationTracker {
@onHistoryStateUpdated()
handleHistoryUpdate(details: browser.WebNavigation.OnHistoryStateUpdatedDetailsType) {
console.log(`History state updated in tab ${details.tabId}`);
console.log(`New URL: ${details.url}`);
// Track single-page app navigation
this.trackSPANavigation(details.tabId, details.url);
}
private trackSPANavigation(tabId: number, url: string) {
// Track single-page application navigation changes
}
}
With parameter decorator:
import { onHistoryStateUpdated, historyStateUpdatedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class SPANavigationTracker {
@onHistoryStateUpdated()
trackHistoryChanges(
@historyStateUpdatedDetails('tabId') tabId: number,
@historyStateUpdatedDetails('url') url: string,
@historyStateUpdatedDetails('transitionType') transitionType: string
) {
console.log(`SPA navigation in tab ${tabId}`);
console.log(`New virtual page: ${url}`);
console.log(`Transition type: ${transitionType}`);
// Update analytics or other tracking
this.recordVirtualPageview(url);
}
private recordVirtualPageview(url: string) {
// Record SPA virtual pageview
}
}
onReferenceFragmentUpdated
This decorator handles events that fire when the URL fragment (part after #) changes.
import { onReferenceFragmentUpdated, InjectableService } from 'deco-ext';
@InjectableService()
class FragmentTracker {
@onReferenceFragmentUpdated()
handleFragmentChange(details: browser.WebNavigation.OnReferenceFragmentUpdatedDetailsType) {
console.log(`URL fragment changed in tab ${details.tabId}`);
console.log(`URL with new fragment: ${details.url}`);
// Extract and use the fragment
const fragment = new URL(details.url).hash;
console.log(`Fragment: ${fragment}`);
this.respondToFragmentChange(details.tabId, fragment);
}
private respondToFragmentChange(tabId: number, fragment: string) {
// Respond to fragment change (e.g., for in-page navigation)
}
}
With parameter decorator:
import { onReferenceFragmentUpdated, referenceFragmentUpdatedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class FragmentTracker {
@onReferenceFragmentUpdated()
trackFragmentChanges(
@referenceFragmentUpdatedDetails('tabId') tabId: number,
@referenceFragmentUpdatedDetails('url') url: string,
@referenceFragmentUpdatedDetails('timeStamp') time: number
) {
const fragment = new URL(url).hash.substring(1); // Remove the # character
console.log(`Tab ${tabId} changed fragment to: ${fragment}`);
console.log(`At time: ${new Date(time).toLocaleTimeString()}`);
// Track fragment-based navigation
this.logFragmentNavigation(fragment);
}
private logFragmentNavigation(fragment: string) {
// Log or respond to fragment-based navigation
}
}
onTabReplaced
This decorator handles events that fire when a tab is replaced by another tab.
import { onTabReplaced, InjectableService } from 'deco-ext';
@InjectableService()
class TabReplacementTracker {
@onTabReplaced()
handleTabReplacement(details: browser.WebNavigation.OnTabReplacedDetailsType) {
console.log(`Tab replacement occurred`);
console.log(`Replaced tab ID: ${details.replacedTabId}`);
console.log(`New tab ID: ${details.tabId}`);
// Update any tab tracking
this.updateTabReferences(details.replacedTabId, details.tabId);
}
private updateTabReferences(oldTabId: number, newTabId: number) {
// Update any data structures that reference the old tab ID
}
}
With parameter decorator:
import { onTabReplaced, tabReplacedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class TabReplacementTracker {
@onTabReplaced()
trackTabReplacement(
@tabReplacedDetails('replacedTabId') oldTabId: number,
@tabReplacedDetails('tabId') newTabId: number,
@tabReplacedDetails('timeStamp') time: number
) {
console.log(`Tab ${oldTabId} was replaced by tab ${newTabId}`);
console.log(`At time: ${new Date(time).toLocaleTimeString()}`);
// Transfer any tab-specific state
this.transferTabState(oldTabId, newTabId);
}
private transferTabState(oldTabId: number, newTabId: number) {
// Transfer any state or tracking from old tab to new tab
}
}
Parameter Decorators
navigationDetails
Used with onBeforeNavigate
to extract specific properties from the navigation details:
import { onBeforeNavigate, navigationDetails, InjectableService } from 'deco-ext';
@InjectableService()
class NavigationTracker {
@onBeforeNavigate()
trackNavigation(
@navigationDetails('tabId') tabId: number,
@navigationDetails('url') url: string,
@navigationDetails('frameId') frameId: number,
@navigationDetails('timeStamp') timestamp: number
) {
const navTime = new Date(timestamp).toLocaleTimeString();
console.log(`Tab ${tabId} frame ${frameId} navigating to ${url} at ${navTime}`);
}
}
navigationCommittedDetails
Used with onNavigationCommitted
to extract specific properties from the committed navigation details:
import { onNavigationCommitted, navigationCommittedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class NavigationTracker {
@onNavigationCommitted()
trackCommit(
@navigationCommittedDetails('tabId') tabId: number,
@navigationCommittedDetails('url') url: string,
@navigationCommittedDetails('transitionType') transitionType: string,
@navigationCommittedDetails('transitionQualifiers') qualifiers: string[]
) {
console.log(`Tab ${tabId} committed navigation to ${url}`);
console.log(`Transition: ${transitionType}`);
console.log(`Qualifiers: ${qualifiers.join(', ')}`);
}
}
navigationCompletedDetails
Used with onNavigationCompleted
to extract specific properties from the completed navigation details:
import { onNavigationCompleted, navigationCompletedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class NavigationTracker {
@onNavigationCompleted()
trackCompletion(
@navigationCompletedDetails('tabId') tabId: number,
@navigationCompletedDetails('url') url: string,
@navigationCompletedDetails('frameId') frameId: number,
@navigationCompletedDetails('timeStamp') timestamp: number
) {
const loadTime = new Date(timestamp).toLocaleTimeString();
console.log(`Tab ${tabId} frame ${frameId} completed loading ${url} at ${loadTime}`);
}
}
domContentLoadedDetails
Used with onDOMContentLoaded
to extract specific properties from the DOM loaded details:
import { onDOMContentLoaded, domContentLoadedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class DOMTracker {
@onDOMContentLoaded()
trackDOMLoad(
@domContentLoadedDetails('tabId') tabId: number,
@domContentLoadedDetails('url') url: string,
@domContentLoadedDetails('frameId') frameId: number,
@domContentLoadedDetails('timeStamp') timestamp: number
) {
const loadTime = new Date(timestamp).toLocaleTimeString();
console.log(`Tab ${tabId} frame ${frameId} DOM loaded for ${url} at ${loadTime}`);
}
}
navigationErrorDetails
Used with onNavigationError
to extract specific properties from the navigation error details:
import { onNavigationError, navigationErrorDetails, InjectableService } from 'deco-ext';
@InjectableService()
class ErrorTracker {
@onNavigationError()
trackError(
@navigationErrorDetails('tabId') tabId: number,
@navigationErrorDetails('url') url: string,
@navigationErrorDetails('error') errorCode: string,
@navigationErrorDetails('timeStamp') timestamp: number
) {
const errorTime = new Date(timestamp).toLocaleTimeString();
console.log(`Tab ${tabId} encountered error '${errorCode}' loading ${url} at ${errorTime}`);
}
}
historyStateUpdatedDetails
Used with onHistoryStateUpdated
to extract specific properties from the history state updated details:
import { onHistoryStateUpdated, historyStateUpdatedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class SPATracker {
@onHistoryStateUpdated()
trackStateChange(
@historyStateUpdatedDetails('tabId') tabId: number,
@historyStateUpdatedDetails('url') url: string,
@historyStateUpdatedDetails('frameId') frameId: number,
@historyStateUpdatedDetails('transitionType') transitionType: string
) {
console.log(`Tab ${tabId} frame ${frameId} history state updated to ${url}`);
console.log(`Transition type: ${transitionType}`);
}
}
referenceFragmentUpdatedDetails
Used with onReferenceFragmentUpdated
to extract specific properties from the fragment updated details:
import { onReferenceFragmentUpdated, referenceFragmentUpdatedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class FragmentTracker {
@onReferenceFragmentUpdated()
trackFragmentChange(
@referenceFragmentUpdatedDetails('tabId') tabId: number,
@referenceFragmentUpdatedDetails('url') url: string,
@referenceFragmentUpdatedDetails('frameId') frameId: number,
@referenceFragmentUpdatedDetails('transitionType') transitionType: string
) {
const fragment = new URL(url).hash;
console.log(`Tab ${tabId} frame ${frameId} fragment changed to ${fragment}`);
console.log(`Transition type: ${transitionType}`);
}
}
tabReplacedDetails
Used with onTabReplaced
to extract specific properties from the tab replaced details:
import { onTabReplaced, tabReplacedDetails, InjectableService } from 'deco-ext';
@InjectableService()
class TabTracker {
@onTabReplaced()
trackReplacement(
@tabReplacedDetails('replacedTabId') oldTabId: number,
@tabReplacedDetails('tabId') newTabId: number,
@tabReplacedDetails('timeStamp') timestamp: number
) {
const replaceTime = new Date(timestamp).toLocaleTimeString();
console.log(`Tab ${oldTabId} was replaced by tab ${newTabId} at ${replaceTime}`);
}
}
Implementation Details
These decorators use a singleton pattern to ensure only one event listener is registered per event type, and then route events to all decorated methods. When a web navigation event occurs:
- The event is received by the single registered browser API listener
- The event data is passed to all registered method handlers
- For each handler:
- The class instance is resolved from the dependency injection container
- If the class has an
init
method, it's called before handling the event - If parameter decorators are used, the event data is transformed accordingly
- The method is called with the appropriate parameters
The decorators can only be used on methods within classes that have been decorated with the InjectableService
decorator from deco-ext.
Future Enhancements
The implementation includes TODO comments indicating planned filtering options for several navigation events, which would allow you to filter events based on URL patterns or other criteria.