mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D36041 --HG-- extra : source : 96b3895a3b2aa2fcb064c85ec5857b7216884556
		
			
				
	
	
		
			193 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
	
		
			6.3 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/. */
 | 
						|
 | 
						|
/* eslint-env mozilla/frame-script */
 | 
						|
 | 
						|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | 
						|
const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
 | 
						|
 | 
						|
var RefreshBlocker = {
 | 
						|
  PREF: "accessibility.blockautorefresh",
 | 
						|
 | 
						|
  // Bug 1247100 - When a refresh is caused by an HTTP header,
 | 
						|
  // onRefreshAttempted will be fired before onLocationChange.
 | 
						|
  // When a refresh is caused by a <meta> tag in the document,
 | 
						|
  // onRefreshAttempted will be fired after onLocationChange.
 | 
						|
  //
 | 
						|
  // We only ever want to send a message to the parent after
 | 
						|
  // onLocationChange has fired, since the parent uses the
 | 
						|
  // onLocationChange update to clear transient notifications.
 | 
						|
  // Sending the message before onLocationChange will result in
 | 
						|
  // us creating the notification, and then clearing it very
 | 
						|
  // soon after.
 | 
						|
  //
 | 
						|
  // To account for both cases (onRefreshAttempted before
 | 
						|
  // onLocationChange, and onRefreshAttempted after onLocationChange),
 | 
						|
  // we'll hold a mapping of DOM Windows that we see get
 | 
						|
  // sent through both onLocationChange and onRefreshAttempted.
 | 
						|
  // When either run, they'll check the WeakMap for the existence
 | 
						|
  // of the DOM Window. If it doesn't exist, it'll add it. If
 | 
						|
  // it finds it, it'll know that it's safe to send the message
 | 
						|
  // to the parent, since we know that both have fired.
 | 
						|
  //
 | 
						|
  // The DOM Window is removed from blockedWindows when we notice
 | 
						|
  // the nsIWebProgress change state to STATE_STOP for the
 | 
						|
  // STATE_IS_WINDOW case.
 | 
						|
  //
 | 
						|
  // DOM Windows are mapped to a JS object that contains the data
 | 
						|
  // to be sent to the parent to show the notification. Since that
 | 
						|
  // data is only known when onRefreshAttempted is fired, it's only
 | 
						|
  // ever stashed in the map if onRefreshAttempted fires first -
 | 
						|
  // otherwise, null is set as the value of the mapping.
 | 
						|
  blockedWindows: new WeakMap(),
 | 
						|
 | 
						|
  init() {
 | 
						|
    if (Services.prefs.getBoolPref(this.PREF)) {
 | 
						|
      this.enable();
 | 
						|
    }
 | 
						|
 | 
						|
    Services.prefs.addObserver(this.PREF, this);
 | 
						|
  },
 | 
						|
 | 
						|
  uninit() {
 | 
						|
    if (Services.prefs.getBoolPref(this.PREF)) {
 | 
						|
      this.disable();
 | 
						|
    }
 | 
						|
 | 
						|
    Services.prefs.removeObserver(this.PREF, this);
 | 
						|
  },
 | 
						|
 | 
						|
  observe(subject, topic, data) {
 | 
						|
    if (topic == "nsPref:changed" && data == this.PREF) {
 | 
						|
      if (Services.prefs.getBoolPref(this.PREF)) {
 | 
						|
        this.enable();
 | 
						|
      } else {
 | 
						|
        this.disable();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  enable() {
 | 
						|
    this._filter = Cc[
 | 
						|
      "@mozilla.org/appshell/component/browser-status-filter;1"
 | 
						|
    ].createInstance(Ci.nsIWebProgress);
 | 
						|
    this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
 | 
						|
    this._filter.target = tabEventTarget;
 | 
						|
 | 
						|
    let webProgress = docShell
 | 
						|
      .QueryInterface(Ci.nsIInterfaceRequestor)
 | 
						|
      .getInterface(Ci.nsIWebProgress);
 | 
						|
    webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_ALL);
 | 
						|
 | 
						|
    addMessageListener("RefreshBlocker:Refresh", this);
 | 
						|
  },
 | 
						|
 | 
						|
  disable() {
 | 
						|
    let webProgress = docShell
 | 
						|
      .QueryInterface(Ci.nsIInterfaceRequestor)
 | 
						|
      .getInterface(Ci.nsIWebProgress);
 | 
						|
    webProgress.removeProgressListener(this._filter);
 | 
						|
 | 
						|
    this._filter.removeProgressListener(this);
 | 
						|
    this._filter = null;
 | 
						|
 | 
						|
    removeMessageListener("RefreshBlocker:Refresh", this);
 | 
						|
  },
 | 
						|
 | 
						|
  send(data) {
 | 
						|
    // Due to the |nsDocLoader| calling its |nsIWebProgressListener|s in
 | 
						|
    // reverse order, this will occur *before* the |BrowserChild| can send its
 | 
						|
    // |OnLocationChange| event to the parent, but we need this message to
 | 
						|
    // arrive after to ensure that the refresh blocker notification is not
 | 
						|
    // immediately cleared by the |OnLocationChange| from |BrowserChild|.
 | 
						|
    setTimeout(() => sendAsyncMessage("RefreshBlocker:Blocked", data), 0);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Notices when the nsIWebProgress transitions to STATE_STOP for
 | 
						|
   * the STATE_IS_WINDOW case, which will clear any mappings from
 | 
						|
   * blockedWindows.
 | 
						|
   */
 | 
						|
  onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
 | 
						|
    if (
 | 
						|
      aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
 | 
						|
      aStateFlags & Ci.nsIWebProgressListener.STATE_STOP
 | 
						|
    ) {
 | 
						|
      this.blockedWindows.delete(aWebProgress.DOMWindow);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Notices when the location has changed. If, when running,
 | 
						|
   * onRefreshAttempted has already fired for this DOM Window, will
 | 
						|
   * send the appropriate refresh blocked data to the parent.
 | 
						|
   */
 | 
						|
  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
 | 
						|
    let win = aWebProgress.DOMWindow;
 | 
						|
    if (this.blockedWindows.has(win)) {
 | 
						|
      let data = this.blockedWindows.get(win);
 | 
						|
      if (data) {
 | 
						|
        // We saw onRefreshAttempted before onLocationChange, so
 | 
						|
        // send the message to the parent to show the notification.
 | 
						|
        this.send(data);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      this.blockedWindows.set(win, null);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Notices when a refresh / reload was attempted. If, when running,
 | 
						|
   * onLocationChange has not yet run, will stash the appropriate data
 | 
						|
   * into the blockedWindows map to be sent when onLocationChange fires.
 | 
						|
   */
 | 
						|
  onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
 | 
						|
    let win = aWebProgress.DOMWindow;
 | 
						|
    let outerWindowID = win.windowUtils.outerWindowID;
 | 
						|
 | 
						|
    let data = {
 | 
						|
      URI: aURI.spec,
 | 
						|
      delay: aDelay,
 | 
						|
      sameURI: aSameURI,
 | 
						|
      outerWindowID,
 | 
						|
    };
 | 
						|
 | 
						|
    if (this.blockedWindows.has(win)) {
 | 
						|
      // onLocationChange must have fired before, so we can tell the
 | 
						|
      // parent to show the notification.
 | 
						|
      this.send(data);
 | 
						|
    } else {
 | 
						|
      // onLocationChange hasn't fired yet, so stash the data in the
 | 
						|
      // map so that onLocationChange can send it when it fires.
 | 
						|
      this.blockedWindows.set(win, data);
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  receiveMessage(message) {
 | 
						|
    let data = message.data;
 | 
						|
 | 
						|
    if (message.name == "RefreshBlocker:Refresh") {
 | 
						|
      let win = Services.wm.getOuterWindowWithId(data.outerWindowID);
 | 
						|
      let refreshURI = win.docShell.QueryInterface(Ci.nsIRefreshURI);
 | 
						|
 | 
						|
      let URI = Services.io.newURI(data.URI);
 | 
						|
 | 
						|
      refreshURI.forceRefreshURI(URI, null, data.delay, true);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  QueryInterface: ChromeUtils.generateQI([
 | 
						|
    Ci.nsIWebProgressListener2,
 | 
						|
    Ci.nsIWebProgressListener,
 | 
						|
    Ci.nsISupportsWeakReference,
 | 
						|
  ]),
 | 
						|
};
 | 
						|
 | 
						|
RefreshBlocker.init();
 | 
						|
 | 
						|
addEventListener("unload", () => {
 | 
						|
  RefreshBlocker.uninit();
 | 
						|
});
 |