InteractionsChild.sys.mjs (4260B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", 9 }); 10 11 /** 12 * Listens for interactions in the child process and passes information to the 13 * parent. 14 */ 15 export class InteractionsChild extends JSWindowActorChild { 16 #progressListener; 17 #currentURL; 18 19 actorCreated() { 20 this.isContentWindowPrivate = 21 lazy.PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow); 22 23 if (this.isContentWindowPrivate) { 24 return; 25 } 26 27 this.#progressListener = { 28 onLocationChange: (webProgress, request, location, flags) => { 29 this.onLocationChange(webProgress, request, location, flags); 30 }, 31 32 QueryInterface: ChromeUtils.generateQI([ 33 "nsIWebProgressListener2", 34 "nsIWebProgressListener", 35 "nsISupportsWeakReference", 36 ]), 37 }; 38 39 let webProgress = this.docShell 40 .QueryInterface(Ci.nsIInterfaceRequestor) 41 .getInterface(Ci.nsIWebProgress); 42 webProgress.addProgressListener( 43 this.#progressListener, 44 Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT | 45 Ci.nsIWebProgress.NOTIFY_LOCATION 46 ); 47 } 48 49 didDestroy() { 50 // If the tab is closed then the docshell is no longer available. 51 if (!this.#progressListener || !this.docShell) { 52 return; 53 } 54 55 let webProgress = this.docShell 56 .QueryInterface(Ci.nsIInterfaceRequestor) 57 .getInterface(Ci.nsIWebProgress); 58 webProgress.removeProgressListener(this.#progressListener); 59 } 60 61 onLocationChange(webProgress, request, location, flags) { 62 // We don't care about inner-frame navigations. 63 if (!webProgress.isTopLevel) { 64 return; 65 } 66 67 // If this is a new document then the DOMContentLoaded event will trigger 68 // the new interaction instead. 69 if (!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) { 70 return; 71 } 72 73 this.#recordNewPage(); 74 } 75 76 #recordNewPage() { 77 if (!this.docShell.currentDocumentChannel) { 78 // If there is no document channel, then it is something we're not 79 // interested in, but we do need to know that the previous interaction 80 // has ended. 81 this.sendAsyncMessage("Interactions:PageHide"); 82 return; 83 } 84 85 let docInfo = this.#getDocumentInfo(); 86 87 // This may happen when the page calls replaceState or pushState with the 88 // same URL. We'll just consider this to not be a new page. 89 if (docInfo.url == this.#currentURL) { 90 return; 91 } 92 93 this.#currentURL = docInfo.url; 94 95 if ( 96 this.docShell.currentDocumentChannel instanceof Ci.nsIHttpChannel && 97 !this.docShell.currentDocumentChannel.requestSucceeded 98 ) { 99 return; 100 } 101 102 this.sendAsyncMessage("Interactions:PageLoaded", docInfo); 103 } 104 105 async handleEvent(event) { 106 if (this.isContentWindowPrivate) { 107 // No recording in private browsing mode. 108 return; 109 } 110 switch (event.type) { 111 case "DOMContentLoaded": { 112 this.#recordNewPage(); 113 break; 114 } 115 case "pagehide": { 116 // We generally expect this to be an nsIHttpChannel, if it isn't 117 // then the if statement below will return early anyway. 118 let currentDocumentChannel = /** @type {nsIHttpChannel} */ ( 119 this.docShell.currentDocumentChannel 120 ); 121 if (!currentDocumentChannel) { 122 return; 123 } 124 125 if (!currentDocumentChannel.requestSucceeded) { 126 return; 127 } 128 129 this.sendAsyncMessage("Interactions:PageHide"); 130 break; 131 } 132 } 133 } 134 135 /** 136 * Returns the current document information for sending to the parent process. 137 * 138 * @returns {{ isActive: boolean, url: string, referrer: * }?} 139 */ 140 #getDocumentInfo() { 141 let doc = this.document; 142 143 let referrer; 144 if (doc.referrer) { 145 referrer = Services.io.newURI(doc.referrer); 146 } 147 return { 148 isActive: this.manager.browsingContext.isActive, 149 url: doc.documentURIObject.specIgnoringRef, 150 referrer: referrer?.specIgnoringRef, 151 }; 152 } 153 }