summaryrefslogtreecommitdiffstats
path: root/browser/components/places/InteractionsChild.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/places/InteractionsChild.sys.mjs')
-rw-r--r--browser/components/places/InteractionsChild.sys.mjs149
1 files changed, 149 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..e57ff1f0ff
--- /dev/null
+++ b/browser/components/places/InteractionsChild.sys.mjs
@@ -0,0 +1,149 @@
+/* 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,
+ };
+ }
+}