forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			756 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			756 lines
		
	
	
	
		
			24 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/. */
 | 
						|
 | 
						|
// This file is loaded into the browser window scope.
 | 
						|
/* eslint-env mozilla/browser-window */
 | 
						|
 | 
						|
/**
 | 
						|
 * Controls the "full zoom" setting and its site-specific preferences.
 | 
						|
 */
 | 
						|
var FullZoom = {
 | 
						|
  // Identifies the setting in the content prefs database.
 | 
						|
  name: "browser.content.full-zoom",
 | 
						|
 | 
						|
  // browser.zoom.siteSpecific preference cache
 | 
						|
  _siteSpecificPref: undefined,
 | 
						|
 | 
						|
  // browser.zoom.updateBackgroundTabs preference cache
 | 
						|
  updateBackgroundTabs: undefined,
 | 
						|
 | 
						|
  // This maps the browser to monotonically increasing integer
 | 
						|
  // tokens. _browserTokenMap[browser] is increased each time the zoom is
 | 
						|
  // changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses.
 | 
						|
  _browserTokenMap: new WeakMap(),
 | 
						|
 | 
						|
  // Stores initial locations if we receive onLocationChange
 | 
						|
  // events before we're initialized.
 | 
						|
  _initialLocations: new WeakMap(),
 | 
						|
 | 
						|
  get siteSpecific() {
 | 
						|
    if (this._siteSpecificPref === undefined) {
 | 
						|
      this._siteSpecificPref = Services.prefs.getBoolPref(
 | 
						|
        "browser.zoom.siteSpecific"
 | 
						|
      );
 | 
						|
    }
 | 
						|
    return this._siteSpecificPref;
 | 
						|
  },
 | 
						|
 | 
						|
  // nsISupports
 | 
						|
 | 
						|
  QueryInterface: ChromeUtils.generateQI([
 | 
						|
    "nsIObserver",
 | 
						|
    "nsIContentPrefObserver",
 | 
						|
    "nsISupportsWeakReference",
 | 
						|
  ]),
 | 
						|
 | 
						|
  // Initialization & Destruction
 | 
						|
 | 
						|
  init: function FullZoom_init() {
 | 
						|
    gBrowser.addEventListener("DoZoomEnlargeBy10", this);
 | 
						|
    gBrowser.addEventListener("DoZoomReduceBy10", this);
 | 
						|
    window.addEventListener("MozScaleGestureComplete", this);
 | 
						|
 | 
						|
    // Register ourselves with the service so we know when our pref changes.
 | 
						|
    this._cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(
 | 
						|
      Ci.nsIContentPrefService2
 | 
						|
    );
 | 
						|
    this._cps2.addObserverForName(this.name, this);
 | 
						|
 | 
						|
    this.updateBackgroundTabs = Services.prefs.getBoolPref(
 | 
						|
      "browser.zoom.updateBackgroundTabs"
 | 
						|
    );
 | 
						|
 | 
						|
    // Listen for changes to the browser.zoom branch so we can enable/disable
 | 
						|
    // updating background tabs and per-site saving and restoring of zoom levels.
 | 
						|
    Services.prefs.addObserver("browser.zoom.", this, true);
 | 
						|
 | 
						|
    // If we received onLocationChange events for any of the current browsers
 | 
						|
    // before we were initialized we want to replay those upon initialization.
 | 
						|
    for (let browser of gBrowser.browsers) {
 | 
						|
      if (this._initialLocations.has(browser)) {
 | 
						|
        this.onLocationChange(...this._initialLocations.get(browser), browser);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // This should be nulled after initialization.
 | 
						|
    this._initialLocations = null;
 | 
						|
  },
 | 
						|
 | 
						|
  destroy: function FullZoom_destroy() {
 | 
						|
    Services.prefs.removeObserver("browser.zoom.", this);
 | 
						|
    this._cps2.removeObserverForName(this.name, this);
 | 
						|
    gBrowser.removeEventListener("DoZoomEnlargeBy10", this);
 | 
						|
    gBrowser.removeEventListener("DoZoomReduceBy10", this);
 | 
						|
    window.removeEventListener("MozScaleGestureComplete", this);
 | 
						|
  },
 | 
						|
 | 
						|
  // Event Handlers
 | 
						|
 | 
						|
  // EventListener
 | 
						|
 | 
						|
  handleEvent: function FullZoom_handleEvent(event) {
 | 
						|
    switch (event.type) {
 | 
						|
      case "DoZoomEnlargeBy10":
 | 
						|
        this.changeZoomBy(this._getTargetedBrowser(event), 0.1);
 | 
						|
        break;
 | 
						|
      case "DoZoomReduceBy10":
 | 
						|
        this.changeZoomBy(this._getTargetedBrowser(event), -0.1);
 | 
						|
        break;
 | 
						|
      case "MozScaleGestureComplete": {
 | 
						|
        let nonDefaultScalingZoom = event.detail != 1.0;
 | 
						|
        this.updateCommands(nonDefaultScalingZoom);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  // nsIObserver
 | 
						|
 | 
						|
  observe(aSubject, aTopic, aData) {
 | 
						|
    switch (aTopic) {
 | 
						|
      case "nsPref:changed":
 | 
						|
        switch (aData) {
 | 
						|
          case "browser.zoom.siteSpecific":
 | 
						|
            // Invalidate pref cache.
 | 
						|
            this._siteSpecificPref = undefined;
 | 
						|
            break;
 | 
						|
          case "browser.zoom.updateBackgroundTabs":
 | 
						|
            this.updateBackgroundTabs = Services.prefs.getBoolPref(
 | 
						|
              "browser.zoom.updateBackgroundTabs"
 | 
						|
            );
 | 
						|
            break;
 | 
						|
          case "browser.zoom.full": {
 | 
						|
            this.updateCommands();
 | 
						|
            break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  // nsIContentPrefObserver
 | 
						|
 | 
						|
  onContentPrefSet: function FullZoom_onContentPrefSet(
 | 
						|
    aGroup,
 | 
						|
    aName,
 | 
						|
    aValue,
 | 
						|
    aIsPrivate
 | 
						|
  ) {
 | 
						|
    this._onContentPrefChanged(aGroup, aValue, aIsPrivate);
 | 
						|
  },
 | 
						|
 | 
						|
  onContentPrefRemoved: function FullZoom_onContentPrefRemoved(
 | 
						|
    aGroup,
 | 
						|
    aName,
 | 
						|
    aIsPrivate
 | 
						|
  ) {
 | 
						|
    this._onContentPrefChanged(aGroup, undefined, aIsPrivate);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Appropriately updates the zoom level after a content preference has
 | 
						|
   * changed.
 | 
						|
   *
 | 
						|
   * @param aGroup  The group of the changed preference.
 | 
						|
   * @param aValue  The new value of the changed preference.  Pass undefined to
 | 
						|
   *                indicate the preference's removal.
 | 
						|
   */
 | 
						|
  _onContentPrefChanged: function FullZoom__onContentPrefChanged(
 | 
						|
    aGroup,
 | 
						|
    aValue,
 | 
						|
    aIsPrivate
 | 
						|
  ) {
 | 
						|
    if (this._isNextContentPrefChangeInternal) {
 | 
						|
      // Ignore changes that FullZoom itself makes.  This works because the
 | 
						|
      // content pref service calls callbacks before notifying observers, and it
 | 
						|
      // does both in the same turn of the event loop.
 | 
						|
      delete this._isNextContentPrefChangeInternal;
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let browser = gBrowser.selectedBrowser;
 | 
						|
    if (!browser.currentURI) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (this._isPDFViewer(browser)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let ctxt = this._loadContextFromBrowser(browser);
 | 
						|
    let domain = this._cps2.extractDomain(browser.currentURI.spec);
 | 
						|
    if (aGroup) {
 | 
						|
      if (aGroup == domain && ctxt.usePrivateBrowsing == aIsPrivate) {
 | 
						|
        this._applyPrefToZoom(aValue, browser);
 | 
						|
      }
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // If the current page doesn't have a site-specific preference, then its
 | 
						|
    // zoom should be set to the new global preference now that the global
 | 
						|
    // preference has changed.
 | 
						|
    let hasPref = false;
 | 
						|
    let token = this._getBrowserToken(browser);
 | 
						|
    this._cps2.getByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
 | 
						|
      handleResult() {
 | 
						|
        hasPref = true;
 | 
						|
      },
 | 
						|
      handleCompletion: () => {
 | 
						|
        if (!hasPref && token.isCurrent) {
 | 
						|
          this._applyPrefToZoom(undefined, browser);
 | 
						|
        }
 | 
						|
      },
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  // location change observer
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called when the location of a tab changes.
 | 
						|
   * When that happens, we need to update the current zoom level if appropriate.
 | 
						|
   *
 | 
						|
   * @param aURI
 | 
						|
   *        A URI object representing the new location.
 | 
						|
   * @param aIsTabSwitch
 | 
						|
   *        Whether this location change has happened because of a tab switch.
 | 
						|
   * @param aBrowser
 | 
						|
   *        (optional) browser object displaying the document
 | 
						|
   */
 | 
						|
  onLocationChange: function FullZoom_onLocationChange(
 | 
						|
    aURI,
 | 
						|
    aIsTabSwitch,
 | 
						|
    aBrowser
 | 
						|
  ) {
 | 
						|
    let browser = aBrowser || gBrowser.selectedBrowser;
 | 
						|
 | 
						|
    // If we haven't been initialized yet but receive an onLocationChange
 | 
						|
    // notification then let's store and replay it upon initialization.
 | 
						|
    if (this._initialLocations) {
 | 
						|
      this._initialLocations.set(browser, [aURI, aIsTabSwitch]);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Ignore all pending async zoom accesses in the browser.  Pending accesses
 | 
						|
    // that started before the location change will be prevented from applying
 | 
						|
    // to the new location.
 | 
						|
    this._ignorePendingZoomAccesses(browser);
 | 
						|
 | 
						|
    if (!aURI || (aIsTabSwitch && !this._isSiteSpecific(browser))) {
 | 
						|
      this._notifyOnLocationChange(browser);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aURI.spec == "about:blank") {
 | 
						|
      if (
 | 
						|
        !browser.contentPrincipal ||
 | 
						|
        browser.contentPrincipal.isNullPrincipal
 | 
						|
      ) {
 | 
						|
        // For an about:blank with a null principal, zooming any amount does not
 | 
						|
        // make any sense - so simply do 100%.
 | 
						|
        this._applyPrefToZoom(
 | 
						|
          1,
 | 
						|
          browser,
 | 
						|
          this._notifyOnLocationChange.bind(this, browser)
 | 
						|
        );
 | 
						|
      } else {
 | 
						|
        // If it's not a null principal, there may be content loaded into it,
 | 
						|
        // so use the global pref. This will avoid a cps2 roundtrip if we've
 | 
						|
        // already loaded the global pref once. Really, this should probably
 | 
						|
        // use the contentPrincipal's origin if it's an http(s) principal.
 | 
						|
        // (See bug 1457597)
 | 
						|
        this._applyPrefToZoom(
 | 
						|
          undefined,
 | 
						|
          browser,
 | 
						|
          this._notifyOnLocationChange.bind(this, browser)
 | 
						|
        );
 | 
						|
      }
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Media documents should always start at 1, and are not affected by prefs.
 | 
						|
    if (!aIsTabSwitch && browser.isSyntheticDocument) {
 | 
						|
      ZoomManager.setZoomForBrowser(browser, 1);
 | 
						|
      // _ignorePendingZoomAccesses already called above, so no need here.
 | 
						|
      this._notifyOnLocationChange(browser);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // The PDF viewer zooming isn't handled by `ZoomManager`, ensure that the
 | 
						|
    // browser zoom level always gets reset to 100% on load (to prevent the
 | 
						|
    // UI elements of the PDF viewer from being zoomed in/out on load).
 | 
						|
    if (this._isPDFViewer(browser)) {
 | 
						|
      this._applyPrefToZoom(
 | 
						|
        1,
 | 
						|
        browser,
 | 
						|
        this._notifyOnLocationChange.bind(this, browser)
 | 
						|
      );
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // See if the zoom pref is cached.
 | 
						|
    let ctxt = this._loadContextFromBrowser(browser);
 | 
						|
    let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
 | 
						|
    if (pref) {
 | 
						|
      this._applyPrefToZoom(
 | 
						|
        pref.value,
 | 
						|
        browser,
 | 
						|
        this._notifyOnLocationChange.bind(this, browser)
 | 
						|
      );
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // It's not cached, so we have to asynchronously fetch it.
 | 
						|
    let value = undefined;
 | 
						|
    let token = this._getBrowserToken(browser);
 | 
						|
    this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
 | 
						|
      handleResult(resultPref) {
 | 
						|
        value = resultPref.value;
 | 
						|
      },
 | 
						|
      handleCompletion: () => {
 | 
						|
        if (!token.isCurrent) {
 | 
						|
          this._notifyOnLocationChange(browser);
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        this._applyPrefToZoom(
 | 
						|
          value,
 | 
						|
          browser,
 | 
						|
          this._notifyOnLocationChange.bind(this, browser)
 | 
						|
        );
 | 
						|
      },
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  // update state of zoom menu items
 | 
						|
 | 
						|
  /**
 | 
						|
   * Updates the current windows Zoom commands for zooming in, zooming out
 | 
						|
   * and resetting the zoom level.
 | 
						|
   *
 | 
						|
   * @param {boolean} [forceResetEnabled=false]
 | 
						|
   *   Set to true if the zoom reset command should be enabled regardless of
 | 
						|
   *   whether or not the ZoomManager.zoom level is at 1.0. This is specifically
 | 
						|
   *   for when using scaling zoom via the pinch gesture which doesn't cause
 | 
						|
   *   the ZoomManager.zoom level to change.
 | 
						|
   * @returns Promise
 | 
						|
   * @resolves undefined
 | 
						|
   */
 | 
						|
  updateCommands: async function FullZoom_updateCommands(
 | 
						|
    forceResetEnabled = false
 | 
						|
  ) {
 | 
						|
    let zoomLevel = ZoomManager.zoom;
 | 
						|
    let defaultZoomLevel = await ZoomUI.getGlobalValue();
 | 
						|
    let reduceCmd = document.getElementById("cmd_fullZoomReduce");
 | 
						|
    if (zoomLevel == ZoomManager.MIN) {
 | 
						|
      reduceCmd.setAttribute("disabled", "true");
 | 
						|
    } else {
 | 
						|
      reduceCmd.removeAttribute("disabled");
 | 
						|
    }
 | 
						|
 | 
						|
    let enlargeCmd = document.getElementById("cmd_fullZoomEnlarge");
 | 
						|
    if (zoomLevel == ZoomManager.MAX) {
 | 
						|
      enlargeCmd.setAttribute("disabled", "true");
 | 
						|
    } else {
 | 
						|
      enlargeCmd.removeAttribute("disabled");
 | 
						|
    }
 | 
						|
 | 
						|
    let resetCmd = document.getElementById("cmd_fullZoomReset");
 | 
						|
    if (zoomLevel == defaultZoomLevel && !forceResetEnabled) {
 | 
						|
      resetCmd.setAttribute("disabled", "true");
 | 
						|
    } else {
 | 
						|
      resetCmd.removeAttribute("disabled");
 | 
						|
    }
 | 
						|
 | 
						|
    let fullZoomCmd = document.getElementById("cmd_fullZoomToggle");
 | 
						|
    if (!ZoomManager.useFullZoom) {
 | 
						|
      fullZoomCmd.setAttribute("checked", "true");
 | 
						|
    } else {
 | 
						|
      fullZoomCmd.setAttribute("checked", "false");
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  // Setting & Pref Manipulation
 | 
						|
 | 
						|
  sendMessageToPDFViewer(browser, name) {
 | 
						|
    try {
 | 
						|
      browser.sendMessageToActor(name, {}, "Pdfjs");
 | 
						|
    } catch (ex) {
 | 
						|
      console.error(ex);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * If browser in reader mode sends message to reader in order to decrease font size,
 | 
						|
   * Otherwise reduces the zoom level of the page in the current browser.
 | 
						|
   */
 | 
						|
  async reduce() {
 | 
						|
    let browser = gBrowser.selectedBrowser;
 | 
						|
    if (browser.currentURI.spec.startsWith("about:reader")) {
 | 
						|
      browser.sendMessageToActor("Reader:ZoomOut", {}, "AboutReader");
 | 
						|
    } else if (this._isPDFViewer(browser)) {
 | 
						|
      this.sendMessageToPDFViewer(browser, "PDFJS:ZoomOut");
 | 
						|
    } else {
 | 
						|
      ZoomManager.reduce();
 | 
						|
      this._ignorePendingZoomAccesses(browser);
 | 
						|
      await this._applyZoomToPref(browser);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * If browser in reader mode sends message to reader in order to increase font size,
 | 
						|
   * Otherwise enlarges the zoom level of the page in the current browser.
 | 
						|
   */
 | 
						|
  async enlarge() {
 | 
						|
    let browser = gBrowser.selectedBrowser;
 | 
						|
    if (browser.currentURI.spec.startsWith("about:reader")) {
 | 
						|
      browser.sendMessageToActor("Reader:ZoomIn", {}, "AboutReader");
 | 
						|
    } else if (this._isPDFViewer(browser)) {
 | 
						|
      this.sendMessageToPDFViewer(browser, "PDFJS:ZoomIn");
 | 
						|
    } else {
 | 
						|
      ZoomManager.enlarge();
 | 
						|
      this._ignorePendingZoomAccesses(browser);
 | 
						|
      await this._applyZoomToPref(browser);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * If browser in reader mode sends message to reader in order to increase font size,
 | 
						|
   * Otherwise enlarges the zoom level of the page in the current browser.
 | 
						|
   * This function is not async like reduce/enlarge, because it is invoked by our
 | 
						|
   * event handler. This means that the call to _applyZoomToPref is not awaited and
 | 
						|
   * will happen asynchronously.
 | 
						|
   */
 | 
						|
  changeZoomBy(aBrowser, aValue) {
 | 
						|
    if (aBrowser.currentURI.spec.startsWith("about:reader")) {
 | 
						|
      const message = aValue > 0 ? "Reader::ZoomIn" : "Reader:ZoomOut";
 | 
						|
      aBrowser.sendMessageToActor(message, {}, "AboutReader");
 | 
						|
      return;
 | 
						|
    } else if (this._isPDFViewer(aBrowser)) {
 | 
						|
      const message = aValue > 0 ? "PDFJS::ZoomIn" : "PDFJS:ZoomOut";
 | 
						|
      this.sendMessageToPDFViewer(aBrowser, message);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    let zoom = ZoomManager.getZoomForBrowser(aBrowser);
 | 
						|
    zoom += aValue;
 | 
						|
    if (zoom < ZoomManager.MIN) {
 | 
						|
      zoom = ZoomManager.MIN;
 | 
						|
    } else if (zoom > ZoomManager.MAX) {
 | 
						|
      zoom = ZoomManager.MAX;
 | 
						|
    }
 | 
						|
    ZoomManager.setZoomForBrowser(aBrowser, zoom);
 | 
						|
    this._ignorePendingZoomAccesses(aBrowser);
 | 
						|
    this._applyZoomToPref(aBrowser);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the zoom level for the given browser to the given floating
 | 
						|
   * point value, where 1 is the default zoom level.
 | 
						|
   */
 | 
						|
  setZoom(value, browser = gBrowser.selectedBrowser) {
 | 
						|
    if (this._isPDFViewer(browser)) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    ZoomManager.setZoomForBrowser(browser, value);
 | 
						|
    this._ignorePendingZoomAccesses(browser);
 | 
						|
    this._applyZoomToPref(browser);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sets the zoom level of the page in the given browser to the global zoom
 | 
						|
   * level.
 | 
						|
   *
 | 
						|
   * @return A promise which resolves when the zoom reset has been applied.
 | 
						|
   */
 | 
						|
  reset: function FullZoom_reset(browser = gBrowser.selectedBrowser) {
 | 
						|
    let forceValue;
 | 
						|
    if (browser.currentURI.spec.startsWith("about:reader")) {
 | 
						|
      browser.sendMessageToActor("Reader:ResetZoom", {}, "AboutReader");
 | 
						|
    } else if (this._isPDFViewer(browser)) {
 | 
						|
      this.sendMessageToPDFViewer(browser, "PDFJS:ZoomReset");
 | 
						|
      // Ensure that the UI elements of the PDF viewer won't be zoomed in/out
 | 
						|
      // on reset, even if/when browser default zoom value is not set to 100%.
 | 
						|
      forceValue = 1;
 | 
						|
    }
 | 
						|
    let token = this._getBrowserToken(browser);
 | 
						|
    let result = ZoomUI.getGlobalValue().then(value => {
 | 
						|
      if (token.isCurrent) {
 | 
						|
        ZoomManager.setZoomForBrowser(browser, forceValue || value);
 | 
						|
        this._ignorePendingZoomAccesses(browser);
 | 
						|
      }
 | 
						|
    });
 | 
						|
    this._removePref(browser);
 | 
						|
    return result;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Called from the URL bar's inline zoom reset indicator button.
 | 
						|
   *
 | 
						|
   * @param {Event} event the click/keyboard event that triggered the call.
 | 
						|
   */
 | 
						|
  resetFromURLBar(event) {
 | 
						|
    if (event.button > 0) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this.reset();
 | 
						|
    this.resetScalingZoom();
 | 
						|
  },
 | 
						|
 | 
						|
  resetScalingZoom: function FullZoom_resetScaling(
 | 
						|
    browser = gBrowser.selectedBrowser
 | 
						|
  ) {
 | 
						|
    browser.browsingContext?.resetScalingZoom();
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Set the zoom level for a given browser.
 | 
						|
   *
 | 
						|
   * Per nsPresContext::setFullZoom, we can set the zoom to its current value
 | 
						|
   * without significant impact on performance, as the setting is only applied
 | 
						|
   * if it differs from the current setting.  In fact getting the zoom and then
 | 
						|
   * checking ourselves if it differs costs more.
 | 
						|
   *
 | 
						|
   * And perhaps we should always set the zoom even if it was more expensive,
 | 
						|
   * since nsDocumentViewer::SetTextZoom claims that child documents can have
 | 
						|
   * a different text zoom (although it would be unusual), and it implies that
 | 
						|
   * those child text zooms should get updated when the parent zoom gets set,
 | 
						|
   * and perhaps the same is true for full zoom
 | 
						|
   * (although nsDocumentViewer::SetFullZoom doesn't mention it).
 | 
						|
   *
 | 
						|
   * So when we apply new zoom values to the browser, we simply set the zoom.
 | 
						|
   * We don't check first to see if the new value is the same as the current
 | 
						|
   * one.
 | 
						|
   *
 | 
						|
   * @param aValue     The zoom level value.
 | 
						|
   * @param aBrowser   The zoom is set in this browser.  Required.
 | 
						|
   * @param aCallback  If given, it's asynchronously called when complete.
 | 
						|
   */
 | 
						|
  _applyPrefToZoom: function FullZoom__applyPrefToZoom(
 | 
						|
    aValue,
 | 
						|
    aBrowser,
 | 
						|
    aCallback
 | 
						|
  ) {
 | 
						|
    // The browser is sometimes half-destroyed because this method is called
 | 
						|
    // by content pref service callbacks, which themselves can be called at any
 | 
						|
    // time, even after browsers are closed.
 | 
						|
    if (
 | 
						|
      !aBrowser.mInitialized ||
 | 
						|
      aBrowser.isSyntheticDocument ||
 | 
						|
      (!this._isSiteSpecific(aBrowser) && aBrowser.tabHasCustomZoom)
 | 
						|
    ) {
 | 
						|
      this._executeSoon(aCallback);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aValue !== undefined && this._isSiteSpecific(aBrowser)) {
 | 
						|
      ZoomManager.setZoomForBrowser(aBrowser, this._ensureValid(aValue));
 | 
						|
      this._ignorePendingZoomAccesses(aBrowser);
 | 
						|
      this._executeSoon(aCallback);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Above, we check if site-specific zoom is enabled before setting
 | 
						|
    // the tab browser zoom, however global zoom should work independent
 | 
						|
    // of the site-specific pref, so we do no checks here.
 | 
						|
    let token = this._getBrowserToken(aBrowser);
 | 
						|
    ZoomUI.getGlobalValue().then(value => {
 | 
						|
      if (token.isCurrent) {
 | 
						|
        ZoomManager.setZoomForBrowser(aBrowser, value);
 | 
						|
        this._ignorePendingZoomAccesses(aBrowser);
 | 
						|
      }
 | 
						|
      this._executeSoon(aCallback);
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Saves the zoom level of the page in the given browser to the content
 | 
						|
   * prefs store.
 | 
						|
   *
 | 
						|
   * @param browser  The zoom of this browser will be saved.  Required.
 | 
						|
   */
 | 
						|
  _applyZoomToPref: function FullZoom__applyZoomToPref(browser) {
 | 
						|
    if (!this._isSiteSpecific(browser) || browser.isSyntheticDocument) {
 | 
						|
      // If site-specific zoom is disabled, we have called this function
 | 
						|
      // to adjust our tab's zoom level. It is now considered "custom"
 | 
						|
      // and we mark that here.
 | 
						|
      browser.tabHasCustomZoom = !this._isSiteSpecific(browser);
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    return new Promise(resolve => {
 | 
						|
      this._cps2.set(
 | 
						|
        browser.currentURI.spec,
 | 
						|
        this.name,
 | 
						|
        ZoomManager.getZoomForBrowser(browser),
 | 
						|
        this._loadContextFromBrowser(browser),
 | 
						|
        {
 | 
						|
          handleCompletion: () => {
 | 
						|
            this._isNextContentPrefChangeInternal = true;
 | 
						|
            resolve();
 | 
						|
          },
 | 
						|
        }
 | 
						|
      );
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes from the content prefs store the zoom level of the given browser.
 | 
						|
   *
 | 
						|
   * @param browser  The zoom of this browser will be removed.  Required.
 | 
						|
   */
 | 
						|
  _removePref: function FullZoom__removePref(browser) {
 | 
						|
    if (browser.isSyntheticDocument) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    let ctxt = this._loadContextFromBrowser(browser);
 | 
						|
    this._cps2.removeByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
 | 
						|
      handleCompletion: () => {
 | 
						|
        this._isNextContentPrefChangeInternal = true;
 | 
						|
      },
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  // Utilities
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the zoom change token of the given browser.  Asynchronous
 | 
						|
   * operations that access the given browser's zoom should use this method to
 | 
						|
   * capture the token before starting and use token.isCurrent to determine if
 | 
						|
   * it's safe to access the zoom when done.  If token.isCurrent is false, then
 | 
						|
   * after the async operation started, either the browser's zoom was changed or
 | 
						|
   * the browser was destroyed, and depending on what the operation is doing, it
 | 
						|
   * may no longer be safe to set and get its zoom.
 | 
						|
   *
 | 
						|
   * @param browser  The token of this browser will be returned.
 | 
						|
   * @return  An object with an "isCurrent" getter.
 | 
						|
   */
 | 
						|
  _getBrowserToken: function FullZoom__getBrowserToken(browser) {
 | 
						|
    let map = this._browserTokenMap;
 | 
						|
    if (!map.has(browser)) {
 | 
						|
      map.set(browser, 0);
 | 
						|
    }
 | 
						|
    return {
 | 
						|
      token: map.get(browser),
 | 
						|
      get isCurrent() {
 | 
						|
        // At this point, the browser may have been destructed and unbound but
 | 
						|
        // its outer ID not removed from the map because outer-window-destroyed
 | 
						|
        // hasn't been received yet.  In that case, the browser is unusable, it
 | 
						|
        // has no properties, so return false.  Check for this case by getting a
 | 
						|
        // property, say, docShell.
 | 
						|
        return map.get(browser) === this.token && browser.mInitialized;
 | 
						|
      },
 | 
						|
    };
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the browser that the supplied zoom event is associated with.
 | 
						|
   * @param event  The zoom event.
 | 
						|
   * @return  The associated browser element, if one exists, otherwise null.
 | 
						|
   */
 | 
						|
  _getTargetedBrowser: function FullZoom__getTargetedBrowser(event) {
 | 
						|
    let target = event.originalTarget;
 | 
						|
 | 
						|
    // With remote content browsers, the event's target is the browser
 | 
						|
    // we're looking for.
 | 
						|
    const XUL_NS =
 | 
						|
      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 | 
						|
    if (
 | 
						|
      window.XULElement.isInstance(target) &&
 | 
						|
      target.localName == "browser" &&
 | 
						|
      target.namespaceURI == XUL_NS
 | 
						|
    ) {
 | 
						|
      return target;
 | 
						|
    }
 | 
						|
 | 
						|
    // With in-process content browsers, the event's target is the content
 | 
						|
    // document.
 | 
						|
    if (target.nodeType == Node.DOCUMENT_NODE) {
 | 
						|
      return target.ownerGlobal.docShell.chromeEventHandler;
 | 
						|
    }
 | 
						|
 | 
						|
    throw new Error("Unexpected zoom event source");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Increments the zoom change token for the given browser so that pending
 | 
						|
   * async operations know that it may be unsafe to access they zoom when they
 | 
						|
   * finish.
 | 
						|
   *
 | 
						|
   * @param browser  Pending accesses in this browser will be ignored.
 | 
						|
   */
 | 
						|
  _ignorePendingZoomAccesses: function FullZoom__ignorePendingZoomAccesses(
 | 
						|
    browser
 | 
						|
  ) {
 | 
						|
    let map = this._browserTokenMap;
 | 
						|
    map.set(browser, (map.get(browser) || 0) + 1);
 | 
						|
  },
 | 
						|
 | 
						|
  _ensureValid: function FullZoom__ensureValid(aValue) {
 | 
						|
    // Note that undefined is a valid value for aValue that indicates a known-
 | 
						|
    // not-to-exist value.
 | 
						|
    if (isNaN(aValue)) {
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aValue < ZoomManager.MIN) {
 | 
						|
      return ZoomManager.MIN;
 | 
						|
    }
 | 
						|
 | 
						|
    if (aValue > ZoomManager.MAX) {
 | 
						|
      return ZoomManager.MAX;
 | 
						|
    }
 | 
						|
 | 
						|
    return aValue;
 | 
						|
  },
 | 
						|
 | 
						|
  // Whether to remember the site specific zoom level for this browser.
 | 
						|
  // This returns false when `browser.zoom.siteSpecific` is false or
 | 
						|
  // the browser has content loaded that should resist fingerprinting.
 | 
						|
  _isSiteSpecific(aBrowser) {
 | 
						|
    if (!this.siteSpecific) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    return (
 | 
						|
      !aBrowser?.browsingContext?.topWindowContext.shouldResistFingerprinting ||
 | 
						|
      !ChromeUtils.shouldResistFingerprinting(
 | 
						|
        "SiteSpecificZoom",
 | 
						|
        aBrowser?.browsingContext?.topWindowContext
 | 
						|
          .overriddenFingerprintingSettings
 | 
						|
      )
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the load context from the given Browser.
 | 
						|
   *
 | 
						|
   * @param Browser  The Browser whose load context will be returned.
 | 
						|
   * @return        The nsILoadContext of the given Browser.
 | 
						|
   */
 | 
						|
  _loadContextFromBrowser: function FullZoom__loadContextFromBrowser(browser) {
 | 
						|
    return browser.loadContext;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Asynchronously broadcasts "browser-fullZoom:location-change" so that
 | 
						|
   * listeners can be notified when the zoom levels on those pages change.
 | 
						|
   * The notification is always asynchronous so that observers are guaranteed a
 | 
						|
   * consistent behavior.
 | 
						|
   */
 | 
						|
  _notifyOnLocationChange: function FullZoom__notifyOnLocationChange(browser) {
 | 
						|
    this._executeSoon(function () {
 | 
						|
      Services.obs.notifyObservers(browser, "browser-fullZoom:location-change");
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  _executeSoon: function FullZoom__executeSoon(callback) {
 | 
						|
    if (!callback) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    Services.tm.dispatchToMainThread(callback);
 | 
						|
  },
 | 
						|
 | 
						|
  _isPDFViewer(browser) {
 | 
						|
    return !!(
 | 
						|
      browser.contentPrincipal.spec == "resource://pdf.js/web/viewer.html"
 | 
						|
    );
 | 
						|
  },
 | 
						|
};
 |