/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; // This file expects tabTracker to be defined in the global scope (e.g. // by ext-browser.js or ext-android.js). /* global tabTracker */ ChromeUtils.defineESModuleGetters(this, { MatchURLFilters: "resource://gre/modules/MatchURLFilters.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", WebNavigation: "resource://gre/modules/WebNavigation.sys.mjs", WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs", }); var { ExtensionError } = ExtensionUtils; const defaultTransitionTypes = { topFrame: "link", subFrame: "auto_subframe", }; const frameTransitions = { anyFrame: { qualifiers: ["server_redirect", "client_redirect", "forward_back"], }, topFrame: { types: ["reload", "form_submit"], }, }; const tabTransitions = { topFrame: { qualifiers: ["from_address_bar"], types: ["auto_bookmark", "typed", "keyword", "generated", "link"], }, subFrame: { types: ["manual_subframe"], }, }; const isTopLevelFrame = ({ frameId, parentFrameId }) => { return frameId == 0 && parentFrameId == -1; }; const fillTransitionProperties = (eventName, src, dst) => { if ( eventName == "onCommitted" || eventName == "onHistoryStateUpdated" || eventName == "onReferenceFragmentUpdated" ) { let frameTransitionData = src.frameTransitionData || {}; let tabTransitionData = src.tabTransitionData || {}; let transitionType, transitionQualifiers = []; // Fill transition properties for any frame. for (let qualifier of frameTransitions.anyFrame.qualifiers) { if (frameTransitionData[qualifier]) { transitionQualifiers.push(qualifier); } } if (isTopLevelFrame(dst)) { for (let type of frameTransitions.topFrame.types) { if (frameTransitionData[type]) { transitionType = type; } } for (let qualifier of tabTransitions.topFrame.qualifiers) { if (tabTransitionData[qualifier]) { transitionQualifiers.push(qualifier); } } for (let type of tabTransitions.topFrame.types) { if (tabTransitionData[type]) { transitionType = type; } } // If transitionType is not defined, defaults it to "link". if (!transitionType) { transitionType = defaultTransitionTypes.topFrame; } } else { // If it is sub-frame, transitionType defaults it to "auto_subframe", // "manual_subframe" is set only in case of a recent user interaction. transitionType = tabTransitionData.link ? "manual_subframe" : defaultTransitionTypes.subFrame; } // Fill the transition properties in the webNavigation event object. dst.transitionType = transitionType; dst.transitionQualifiers = transitionQualifiers; } }; this.webNavigation = class extends ExtensionAPIPersistent { makeEventHandler(event) { let { extension } = this; let { tabManager } = extension; return ({ fire }, params) => { // Don't create a MatchURLFilters instance if the listener does not include any filter. let [urlFilters] = params; let filters = urlFilters ? new MatchURLFilters(urlFilters.url) : null; let listener = data => { if (!data.browser) { return; } if ( !extension.privateBrowsingAllowed && PrivateBrowsingUtils.isBrowserPrivate(data.browser) ) { return; } if (filters && !filters.matches(data.url)) { return; } let data2 = { url: data.url, timeStamp: Date.now(), }; if (event == "onErrorOccurred") { data2.error = data.error; } if (data.frameId != undefined) { data2.frameId = data.frameId; data2.parentFrameId = data.parentFrameId; } if (data.sourceFrameId != undefined) { data2.sourceFrameId = data.sourceFrameId; } // Do not send a webNavigation event when the data.browser is related to a tab from a // new window opened to adopt an existent tab (See Bug 1443221 for a rationale). const chromeWin = data.browser.ownerGlobal; if ( chromeWin && chromeWin.gBrowser && chromeWin.gBrowserInit && chromeWin.gBrowserInit.isAdoptingTab() && chromeWin.gBrowser.selectedBrowser === data.browser ) { return; } // Fills in tabId typically. Object.assign(data2, tabTracker.getBrowserData(data.browser)); if (data2.tabId < 0) { return; } let tab = tabTracker.getTab(data2.tabId); if (!tabManager.canAccessTab(tab)) { return; } if (data.sourceTabBrowser) { data2.sourceTabId = tabTracker.getBrowserData( data.sourceTabBrowser ).tabId; } fillTransitionProperties(event, data, data2); fire.async(data2); }; WebNavigation[event].addListener(listener); return { unregister() { WebNavigation[event].removeListener(listener); }, convert(_fire) { fire = _fire; }, }; }; } makeEventManagerAPI(event, context) { let self = this; return new EventManager({ context, module: "webNavigation", event, register(fire, ...params) { let fn = self.makeEventHandler(event); return fn({ fire }, params).unregister; }, }).api(); } PERSISTENT_EVENTS = { onBeforeNavigate: this.makeEventHandler("onBeforeNavigate"), onCommitted: this.makeEventHandler("onCommitted"), onDOMContentLoaded: this.makeEventHandler("onDOMContentLoaded"), onCompleted: this.makeEventHandler("onCompleted"), onErrorOccurred: this.makeEventHandler("onErrorOccurred"), onReferenceFragmentUpdated: this.makeEventHandler( "onReferenceFragmentUpdated" ), onHistoryStateUpdated: this.makeEventHandler("onHistoryStateUpdated"), onCreatedNavigationTarget: this.makeEventHandler( "onCreatedNavigationTarget" ), }; getAPI(context) { let { extension } = context; let { tabManager } = extension; return { webNavigation: { // onTabReplaced does nothing, it exists for compat. onTabReplaced: new EventManager({ context, name: "webNavigation.onTabReplaced", register: () => { return () => {}; }, }).api(), onBeforeNavigate: this.makeEventManagerAPI("onBeforeNavigate", context), onCommitted: this.makeEventManagerAPI("onCommitted", context), onDOMContentLoaded: this.makeEventManagerAPI( "onDOMContentLoaded", context ), onCompleted: this.makeEventManagerAPI("onCompleted", context), onErrorOccurred: this.makeEventManagerAPI("onErrorOccurred", context), onReferenceFragmentUpdated: this.makeEventManagerAPI( "onReferenceFragmentUpdated", context ), onHistoryStateUpdated: this.makeEventManagerAPI( "onHistoryStateUpdated", context ), onCreatedNavigationTarget: this.makeEventManagerAPI( "onCreatedNavigationTarget", context ), getAllFrames({ tabId }) { let tab = tabManager.get(tabId); if (tab.discarded) { return null; } let frames = WebNavigationFrames.getAllFrames(tab.browsingContext); return frames.map(fd => ({ tabId, ...fd })); }, getFrame({ tabId, frameId }) { let tab = tabManager.get(tabId); if (tab.discarded) { return null; } let fd = WebNavigationFrames.getFrame(tab.browsingContext, frameId); if (!fd) { throw new ExtensionError(`No frame found with frameId: ${frameId}`); } return { tabId, ...fd }; }, }, }; } };