tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }