diff options
Diffstat (limited to '')
-rw-r--r-- | browser/components/places/InteractionsChild.sys.mjs | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/browser/components/places/InteractionsChild.sys.mjs b/browser/components/places/InteractionsChild.sys.mjs new file mode 100644 index 0000000000..ff7dd3bd8c --- /dev/null +++ b/browser/components/places/InteractionsChild.sys.mjs @@ -0,0 +1,148 @@ +/* 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/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", +}); + +/** + * Listens for interactions in the child process and passes information to the + * parent. + */ +export class InteractionsChild extends JSWindowActorChild { + #progressListener; + #currentURL; + + actorCreated() { + this.isContentWindowPrivate = + lazy.PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow); + + if (this.isContentWindowPrivate) { + return; + } + + this.#progressListener = { + onLocationChange: (webProgress, request, location, flags) => { + this.onLocationChange(webProgress, request, location, flags); + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener2", + "nsIWebProgressListener", + "nsISupportsWeakReference", + ]), + }; + + let webProgress = this.docShell + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + webProgress.addProgressListener( + this.#progressListener, + Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT | + Ci.nsIWebProgress.NOTIFY_LOCATION + ); + } + + didDestroy() { + // If the tab is closed then the docshell is no longer available. + if (!this.#progressListener || !this.docShell) { + return; + } + + let webProgress = this.docShell + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + webProgress.removeProgressListener(this.#progressListener); + } + + onLocationChange(webProgress, request, location, flags) { + // We don't care about inner-frame navigations. + if (!webProgress.isTopLevel) { + return; + } + + // If this is a new document then the DOMContentLoaded event will trigger + // the new interaction instead. + if (!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) { + return; + } + + this.#recordNewPage(); + } + + #recordNewPage() { + if (!this.docShell.currentDocumentChannel) { + // If there is no document channel, then it is something we're not + // interested in, but we do need to know that the previous interaction + // has ended. + this.sendAsyncMessage("Interactions:PageHide"); + return; + } + + let docInfo = this.#getDocumentInfo(); + + // This may happen when the page calls replaceState or pushState with the + // same URL. We'll just consider this to not be a new page. + if (docInfo.url == this.#currentURL) { + return; + } + + this.#currentURL = docInfo.url; + + if ( + this.docShell.currentDocumentChannel instanceof Ci.nsIHttpChannel && + !this.docShell.currentDocumentChannel.requestSucceeded + ) { + return; + } + + this.sendAsyncMessage("Interactions:PageLoaded", docInfo); + } + + async handleEvent(event) { + if (this.isContentWindowPrivate) { + // No recording in private browsing mode. + return; + } + switch (event.type) { + case "DOMContentLoaded": { + this.#recordNewPage(); + break; + } + case "pagehide": { + if (!this.docShell.currentDocumentChannel) { + return; + } + + if (!this.docShell.currentDocumentChannel.requestSucceeded) { + return; + } + + this.sendAsyncMessage("Interactions:PageHide"); + break; + } + } + } + + /** + * Returns the current document information for sending to the parent process. + * + * @returns {{ isActive: boolean, url: string, referrer: * }?} + */ + #getDocumentInfo() { + let doc = this.document; + + let referrer; + if (doc.referrer) { + referrer = Services.io.newURI(doc.referrer); + } + return { + isActive: this.manager.browsingContext.isActive, + url: doc.documentURIObject.specIgnoringRef, + referrer: referrer?.specIgnoringRef, + }; + } +} |