forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			148 lines
		
	
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			148 lines
		
	
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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,
 | |
|     };
 | |
|   }
 | |
| }
 | 
