Bug 1461548 - Part 0. Move RefreshBlocker to a new frame script. r=mconley
authorFelipe Gomes <felipc@gmail.com>
Fri, 25 May 2018 14:22:14 -0300
changeset 419984 6ed202b3c4356ed5f46d9d633088dc0d1084c88c
parent 419983 37f13f4b96e2e19c313f412182712ca767a45ec6
child 419985 2a22b2c857678a615d3dc178698bc8f9aea388c0
push id34054
push userdluca@mozilla.com
push dateSat, 26 May 2018 04:10:20 +0000
treeherdermozilla-central@c4cf8be3acc8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1461548
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1461548 - Part 0. Move RefreshBlocker to a new frame script. r=mconley
browser/base/content/content-refreshblocker.js
browser/base/content/tab-content.js
browser/base/jar.mn
copy from browser/base/content/tab-content.js
copy to browser/base/content/content-refreshblocker.js
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/content-refreshblocker.js
@@ -1,585 +1,14 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* 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 content script contains code that requires a tab browser. */
-
 /* eslint-env mozilla/frame-script */
 
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-ChromeUtils.defineModuleGetter(this, "E10SUtils",
-  "resource://gre/modules/E10SUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "BrowserUtils",
-  "resource://gre/modules/BrowserUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "Utils",
-  "resource://gre/modules/sessionstore/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
-  "resource://gre/modules/PrivateBrowsingUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "AboutReader",
-  "resource://gre/modules/AboutReader.jsm");
-ChromeUtils.defineModuleGetter(this, "ReaderMode",
-  "resource://gre/modules/ReaderMode.jsm");
-XPCOMUtils.defineLazyGetter(this, "SimpleServiceDiscovery", function() {
-  let ssdp = ChromeUtils.import("resource://gre/modules/SimpleServiceDiscovery.jsm", {}).SimpleServiceDiscovery;
-  // Register targets
-  ssdp.registerDevice({
-    id: "roku:ecp",
-    target: "roku:ecp",
-    factory(aService) {
-      ChromeUtils.import("resource://gre/modules/RokuApp.jsm");
-      return new RokuApp(aService);
-    },
-    types: ["video/mp4"],
-    extensions: ["mp4"]
-  });
-  return ssdp;
-});
-
-// TabChildGlobal
-var global = this;
-
-
-addEventListener("MozDOMPointerLock:Entered", function(aEvent) {
-  sendAsyncMessage("PointerLock:Entered", {
-    originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix
-  });
-});
-
-addEventListener("MozDOMPointerLock:Exited", function(aEvent) {
-  sendAsyncMessage("PointerLock:Exited");
-});
-
-
-addMessageListener("Browser:HideSessionRestoreButton", function(message) {
-  // Hide session restore button on about:home
-  let doc = content.document;
-  let container;
-  if (doc.documentURI.toLowerCase() == "about:home" &&
-      (container = doc.getElementById("sessionRestoreContainer"))) {
-    container.hidden = true;
-  }
-});
-
-
-// XXX(nika): Should we try to call this in the parent process instead?
-addMessageListener("Browser:Reload", function(message) {
-  /* First, we'll try to use the session history object to reload so
-   * that framesets are handled properly. If we're in a special
-   * window (such as view-source) that has no session history, fall
-   * back on using the web navigation's reload method.
-   */
-
-  let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
-  try {
-    if (webNav.sessionHistory) {
-      webNav = webNav.sessionHistory;
-    }
-  } catch (e) {
-  }
-
-  let reloadFlags = message.data.flags;
-  try {
-    E10SUtils.wrapHandlingUserInput(content, message.data.handlingUserInput,
-                                    () => webNav.reload(reloadFlags));
-  } catch (e) {
-  }
-});
-
-addMessageListener("MixedContent:ReenableProtection", function() {
-  docShell.mixedContentChannel = null;
-});
-
-var AboutReaderListener = {
-
-  _articlePromise: null,
-
-  _isLeavingReaderableReaderMode: false,
-
-  init() {
-    addEventListener("AboutReaderContentLoaded", this, false, true);
-    addEventListener("DOMContentLoaded", this, false);
-    addEventListener("pageshow", this, false);
-    addEventListener("pagehide", this, false);
-    addMessageListener("Reader:ToggleReaderMode", this);
-    addMessageListener("Reader:PushState", this);
-  },
-
-  receiveMessage(message) {
-    switch (message.name) {
-      case "Reader:ToggleReaderMode":
-        if (!this.isAboutReader) {
-          this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
-          ReaderMode.enterReaderMode(docShell, content);
-        } else {
-          this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
-          ReaderMode.leaveReaderMode(docShell, content);
-        }
-        break;
-
-      case "Reader:PushState":
-        this.updateReaderButton(!!(message.data && message.data.isArticle));
-        break;
-    }
-  },
-
-  get isAboutReader() {
-    if (!content) {
-      return false;
-    }
-    return content.document.documentURI.startsWith("about:reader");
-  },
-
-  get isReaderableAboutReader() {
-    return this.isAboutReader &&
-      !content.document.documentElement.dataset.isError;
-  },
-
-  handleEvent(aEvent) {
-    if (aEvent.originalTarget.defaultView != content) {
-      return;
-    }
-
-    switch (aEvent.type) {
-      case "AboutReaderContentLoaded":
-        if (!this.isAboutReader) {
-          return;
-        }
-
-        if (content.document.body) {
-          // Update the toolbar icon to show the "reader active" icon.
-          sendAsyncMessage("Reader:UpdateReaderButton");
-          new AboutReader(global, content, this._articlePromise);
-          this._articlePromise = null;
-        }
-        break;
-
-      case "pagehide":
-        this.cancelPotentialPendingReadabilityCheck();
-        // this._isLeavingReaderableReaderMode is used here to keep the Reader Mode icon
-        // visible in the location bar when transitioning from reader-mode page
-        // back to the readable source page.
-        sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderableReaderMode });
-        if (this._isLeavingReaderableReaderMode) {
-          this._isLeavingReaderableReaderMode = false;
-        }
-        break;
-
-      case "pageshow":
-        // If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
-        // event, so we need to rely on "pageshow" in this case.
-        if (aEvent.persisted) {
-          this.updateReaderButton();
-        }
-        break;
-      case "DOMContentLoaded":
-        this.updateReaderButton();
-        break;
-
-    }
-  },
-
-  /**
-   * NB: this function will update the state of the reader button asynchronously
-   * after the next mozAfterPaint call (assuming reader mode is enabled and
-   * this is a suitable document). Calling it on things which won't be
-   * painted is not going to work.
-   */
-  updateReaderButton(forceNonArticle) {
-    if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
-        !content || !(content.document instanceof content.HTMLDocument) ||
-        content.document.mozSyntheticDocument) {
-      return;
-    }
-
-    this.scheduleReadabilityCheckPostPaint(forceNonArticle);
-  },
-
-  cancelPotentialPendingReadabilityCheck() {
-    if (this._pendingReadabilityCheck) {
-      removeEventListener("MozAfterPaint", this._pendingReadabilityCheck);
-      delete this._pendingReadabilityCheck;
-    }
-  },
-
-  scheduleReadabilityCheckPostPaint(forceNonArticle) {
-    if (this._pendingReadabilityCheck) {
-      // We need to stop this check before we re-add one because we don't know
-      // if forceNonArticle was true or false last time.
-      this.cancelPotentialPendingReadabilityCheck();
-    }
-    this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(this, forceNonArticle);
-    addEventListener("MozAfterPaint", this._pendingReadabilityCheck);
-  },
-
-  onPaintWhenWaitedFor(forceNonArticle, event) {
-    // In non-e10s, we'll get called for paints other than ours, and so it's
-    // possible that this page hasn't been laid out yet, in which case we
-    // should wait until we get an event that does relate to our layout. We
-    // determine whether any of our content got painted by checking if there
-    // are any painted rects.
-    if (!event.clientRects.length) {
-      return;
-    }
-
-    this.cancelPotentialPendingReadabilityCheck();
-    // Only send updates when there are articles; there's no point updating with
-    // |false| all the time.
-    if (ReaderMode.isProbablyReaderable(content.document)) {
-      sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
-    } else if (forceNonArticle) {
-      sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
-    }
-  },
-};
-AboutReaderListener.init();
-
-
-var ContentSearchMediator = {
-
-  whitelist: new Set([
-    "about:home",
-    "about:newtab",
-  ]),
-
-  init(chromeGlobal) {
-    chromeGlobal.addEventListener("ContentSearchClient", this, true, true);
-    addMessageListener("ContentSearch", this);
-  },
-
-  handleEvent(event) {
-    if (this._contentWhitelisted) {
-      this._sendMsg(event.detail.type, event.detail.data);
-    }
-  },
-
-  receiveMessage(msg) {
-    if (msg.data.type == "AddToWhitelist") {
-      for (let uri of msg.data.data) {
-        this.whitelist.add(uri);
-      }
-      this._sendMsg("AddToWhitelistAck");
-      return;
-    }
-    if (this._contentWhitelisted) {
-      this._fireEvent(msg.data.type, msg.data.data);
-    }
-  },
-
-  get _contentWhitelisted() {
-    return this.whitelist.has(content.document.documentURI);
-  },
-
-  _sendMsg(type, data = null) {
-    sendAsyncMessage("ContentSearch", {
-      type,
-      data,
-    });
-  },
-
-  _fireEvent(type, data = null) {
-    let event = Cu.cloneInto({
-      detail: {
-        type,
-        data,
-      },
-    }, content);
-    content.dispatchEvent(new content.CustomEvent("ContentSearchService",
-                                                  event));
-  },
-};
-ContentSearchMediator.init(this);
-
-var PageStyleHandler = {
-  init() {
-    addMessageListener("PageStyle:Switch", this);
-    addMessageListener("PageStyle:Disable", this);
-    addEventListener("pageshow", () => this.sendStyleSheetInfo());
-  },
-
-  get markupDocumentViewer() {
-    return docShell.contentViewer;
-  },
-
-  sendStyleSheetInfo() {
-    let filteredStyleSheets = this._filterStyleSheets(this.getAllStyleSheets());
-
-    sendAsyncMessage("PageStyle:StyleSheets", {
-      filteredStyleSheets,
-      authorStyleDisabled: this.markupDocumentViewer.authorStyleDisabled,
-      preferredStyleSheetSet: content.document.preferredStyleSheetSet
-    });
-  },
-
-  getAllStyleSheets(frameset = content) {
-    let selfSheets = Array.slice(frameset.document.styleSheets);
-    let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame));
-    return selfSheets.concat(...subSheets);
-  },
-
-  receiveMessage(msg) {
-    switch (msg.name) {
-      case "PageStyle:Switch":
-        this.markupDocumentViewer.authorStyleDisabled = false;
-        this._stylesheetSwitchAll(content, msg.data.title);
-        break;
-
-      case "PageStyle:Disable":
-        this.markupDocumentViewer.authorStyleDisabled = true;
-        break;
-    }
-
-    this.sendStyleSheetInfo();
-  },
-
-  _stylesheetSwitchAll(frameset, title) {
-    if (!title || this._stylesheetInFrame(frameset, title)) {
-      this._stylesheetSwitchFrame(frameset, title);
-    }
-
-    for (let i = 0; i < frameset.frames.length; i++) {
-      // Recurse into sub-frames.
-      this._stylesheetSwitchAll(frameset.frames[i], title);
-    }
-  },
-
-  _stylesheetSwitchFrame(frame, title) {
-    var docStyleSheets = frame.document.styleSheets;
-
-    for (let i = 0; i < docStyleSheets.length; ++i) {
-      let docStyleSheet = docStyleSheets[i];
-      if (docStyleSheet.title) {
-        docStyleSheet.disabled = (docStyleSheet.title != title);
-      } else if (docStyleSheet.disabled) {
-        docStyleSheet.disabled = false;
-      }
-    }
-  },
-
-  _stylesheetInFrame(frame, title) {
-    return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title);
-  },
-
-  _filterStyleSheets(styleSheets) {
-    let result = [];
-
-    for (let currentStyleSheet of styleSheets) {
-      if (!currentStyleSheet.title)
-        continue;
-
-      // Skip any stylesheets that don't match the screen media type.
-      if (currentStyleSheet.media.length > 0) {
-        let mediaQueryList = currentStyleSheet.media.mediaText;
-        if (!content.matchMedia(mediaQueryList).matches) {
-          continue;
-        }
-      }
-
-      let URI;
-      try {
-        if (!currentStyleSheet.ownerNode ||
-            // special-case style nodes, which have no href
-            currentStyleSheet.ownerNode.nodeName.toLowerCase() != "style") {
-          URI = Services.io.newURI(currentStyleSheet.href);
-        }
-      } catch (e) {
-        if (e.result != Cr.NS_ERROR_MALFORMED_URI) {
-          throw e;
-        }
-        continue;
-      }
-
-      // We won't send data URIs all of the way up to the parent, as these
-      // can be arbitrarily large.
-      let sentURI = (!URI || URI.scheme == "data") ? null : URI.spec;
-
-      result.push({
-        title: currentStyleSheet.title,
-        disabled: currentStyleSheet.disabled,
-        href: sentURI,
-      });
-    }
-
-    return result;
-  },
-};
-PageStyleHandler.init();
-
-// Keep a reference to the translation content handler to avoid it it being GC'ed.
-var trHandler = null;
-if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
-  ChromeUtils.import("resource:///modules/translation/TranslationContentHandler.jsm");
-  trHandler = new TranslationContentHandler(global, docShell);
-}
-
-function gKeywordURIFixup(fixupInfo) {
-  fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
-  if (!fixupInfo.consumer) {
-    return;
-  }
-
-  // Ignore info from other docshells
-  let parent = fixupInfo.consumer.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeRootTreeItem;
-  if (parent != docShell)
-    return;
-
-  let data = {};
-  for (let f of Object.keys(fixupInfo)) {
-    if (f == "consumer" || typeof fixupInfo[f] == "function")
-      continue;
-
-    if (fixupInfo[f] && fixupInfo[f] instanceof Ci.nsIURI) {
-      data[f] = fixupInfo[f].spec;
-    } else {
-      data[f] = fixupInfo[f];
-    }
-  }
-
-  sendAsyncMessage("Browser:URIFixup", data);
-}
-Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup");
-addEventListener("unload", () => {
-  Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
-}, false);
-
-addMessageListener("Browser:AppTab", function(message) {
-  if (docShell) {
-    docShell.isAppTab = message.data.isAppTab;
-  }
-});
-
-var WebBrowserChrome = {
-  onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
-    return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
-  },
-
-  // Check whether this URI should load in the current process
-  shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData, aTriggeringPrincipal) {
-    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData)) {
-      E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, false);
-      return false;
-    }
-
-    return true;
-  },
-
-  shouldLoadURIInThisProcess(aURI) {
-    return E10SUtils.shouldLoadURIInThisProcess(aURI);
-  },
-
-  // Try to reload the currently active or currently loading page in a new process.
-  reloadInFreshProcess(aDocShell, aURI, aReferrer, aTriggeringPrincipal, aLoadFlags) {
-    E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, true, aLoadFlags);
-    return true;
-  }
-};
-
-if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
-  let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-                         .getInterface(Ci.nsITabChild);
-  tabchild.webBrowserChrome = WebBrowserChrome;
-}
-
-
-var DOMFullscreenHandler = {
-
-  init() {
-    addMessageListener("DOMFullscreen:Entered", this);
-    addMessageListener("DOMFullscreen:CleanUp", this);
-    addEventListener("MozDOMFullscreen:Request", this);
-    addEventListener("MozDOMFullscreen:Entered", this);
-    addEventListener("MozDOMFullscreen:NewOrigin", this);
-    addEventListener("MozDOMFullscreen:Exit", this);
-    addEventListener("MozDOMFullscreen:Exited", this);
-  },
-
-  get _windowUtils() {
-    if (!content) {
-      return null;
-    }
-    return content.QueryInterface(Ci.nsIInterfaceRequestor)
-                  .getInterface(Ci.nsIDOMWindowUtils);
-  },
-
-  receiveMessage(aMessage) {
-    let windowUtils = this._windowUtils;
-    switch (aMessage.name) {
-      case "DOMFullscreen:Entered": {
-        this._lastTransactionId = windowUtils.lastTransactionId;
-        if (!windowUtils.handleFullscreenRequests() &&
-            !content.document.fullscreenElement) {
-          // If we don't actually have any pending fullscreen request
-          // to handle, neither we have been in fullscreen, tell the
-          // parent to just exit.
-          sendAsyncMessage("DOMFullscreen:Exit");
-        }
-        break;
-      }
-      case "DOMFullscreen:CleanUp": {
-        // If we've exited fullscreen at this point, no need to record
-        // transaction id or call exit fullscreen. This is especially
-        // important for non-e10s, since in that case, it is possible
-        // that no more paint would be triggered after this point.
-        if (content.document.fullscreenElement && windowUtils) {
-          this._lastTransactionId = windowUtils.lastTransactionId;
-          windowUtils.exitFullscreen();
-        }
-        break;
-      }
-    }
-  },
-
-  handleEvent(aEvent) {
-    switch (aEvent.type) {
-      case "MozDOMFullscreen:Request": {
-        sendAsyncMessage("DOMFullscreen:Request");
-        break;
-      }
-      case "MozDOMFullscreen:NewOrigin": {
-        sendAsyncMessage("DOMFullscreen:NewOrigin", {
-          originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix,
-        });
-        break;
-      }
-      case "MozDOMFullscreen:Exit": {
-        sendAsyncMessage("DOMFullscreen:Exit");
-        break;
-      }
-      case "MozDOMFullscreen:Entered":
-      case "MozDOMFullscreen:Exited": {
-        addEventListener("MozAfterPaint", this);
-        if (!content || !content.document.fullscreenElement) {
-          // If we receive any fullscreen change event, and find we are
-          // actually not in fullscreen, also ask the parent to exit to
-          // ensure that the parent always exits fullscreen when we do.
-          sendAsyncMessage("DOMFullscreen:Exit");
-        }
-        break;
-      }
-      case "MozAfterPaint": {
-        // Only send Painted signal after we actually finish painting
-        // the transition for the fullscreen change.
-        // Note that this._lastTransactionId is not set when in non-e10s
-        // mode, so we need to check that explicitly.
-        if (!this._lastTransactionId ||
-            aEvent.transactionId > this._lastTransactionId) {
-          removeEventListener("MozAfterPaint", this);
-          sendAsyncMessage("DOMFullscreen:Painted");
-        }
-        break;
-      }
-    }
-  }
-};
-DOMFullscreenHandler.init();
-
 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.
   //
@@ -741,59 +170,11 @@ var RefreshBlocker = {
     }
   },
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener2, Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
 };
 
 RefreshBlocker.init();
 
-var UserContextIdNotifier = {
-  init() {
-    addEventListener("DOMWindowCreated", this);
-  },
-
-  uninit() {
-    removeEventListener("DOMWindowCreated", this);
-  },
-
-  handleEvent(aEvent) {
-    // When the window is created, we want to inform the tabbrowser about
-    // the userContextId in use in order to update the UI correctly.
-    // Just because we cannot change the userContextId from an active docShell,
-    // we don't need to check DOMContentLoaded again.
-    this.uninit();
-
-    // We use the docShell because content.document can have been loaded before
-    // setting the originAttributes.
-    let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
-    let userContextId = loadContext.originAttributes.userContextId;
-
-    sendAsyncMessage("Browser:WindowCreated", { userContextId });
-  }
-};
-
-UserContextIdNotifier.init();
-
-Services.obs.notifyObservers(this, "tab-content-frameloader-created");
-
 addEventListener("unload", () => {
   RefreshBlocker.uninit();
 });
-
-addMessageListener("AllowScriptsToClose", () => {
-  content.QueryInterface(Ci.nsIInterfaceRequestor)
-         .getInterface(Ci.nsIDOMWindowUtils)
-         .allowScriptsToClose();
-});
-
-addEventListener("MozAfterPaint", function onFirstPaint() {
-  removeEventListener("MozAfterPaint", onFirstPaint);
-  sendAsyncMessage("Browser:FirstPaint");
-});
-
-// Remove this once bug 1397365 is fixed.
-addEventListener("MozAfterPaint", function onFirstNonBlankPaint() {
-  if (content.document.documentURI == "about:blank" && !content.opener)
-    return;
-  removeEventListener("MozAfterPaint", onFirstNonBlankPaint);
-  sendAsyncMessage("Browser:FirstNonBlankPaint");
-});
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -570,187 +570,16 @@ var DOMFullscreenHandler = {
         }
         break;
       }
     }
   }
 };
 DOMFullscreenHandler.init();
 
-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) {
-    sendAsyncMessage("RefreshBlocker:Blocked", data);
-  },
-
-  /**
-   * 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.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindowUtils)
-                           .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.QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIDocShell)
-                          .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();
-
 var UserContextIdNotifier = {
   init() {
     addEventListener("DOMWindowCreated", this);
   },
 
   uninit() {
     removeEventListener("DOMWindowCreated", this);
   },
@@ -770,20 +599,16 @@ var UserContextIdNotifier = {
     sendAsyncMessage("Browser:WindowCreated", { userContextId });
   }
 };
 
 UserContextIdNotifier.init();
 
 Services.obs.notifyObservers(this, "tab-content-frameloader-created");
 
-addEventListener("unload", () => {
-  RefreshBlocker.uninit();
-});
-
 addMessageListener("AllowScriptsToClose", () => {
   content.QueryInterface(Ci.nsIInterfaceRequestor)
          .getInterface(Ci.nsIDOMWindowUtils)
          .allowScriptsToClose();
 });
 
 addEventListener("MozAfterPaint", function onFirstPaint() {
   removeEventListener("MozAfterPaint", onFirstPaint);
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -74,16 +74,17 @@ browser.jar:
         content/browser/defaultthemes/light.icon.svg  (content/defaultthemes/light.icon.svg)
         content/browser/history-swipe-arrow.svg       (content/history-swipe-arrow.svg)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
         content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
         content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
         content/browser/pageinfo/feeds.js             (content/pageinfo/feeds.js)
         content/browser/pageinfo/permissions.js       (content/pageinfo/permissions.js)
         content/browser/pageinfo/security.js          (content/pageinfo/security.js)
+        content/browser/content-refreshblocker.js     (content/content-refreshblocker.js)
         content/browser/robot.ico                     (content/robot.ico)
         content/browser/static-robot.png              (content/static-robot.png)
         content/browser/safeMode.css                  (content/safeMode.css)
         content/browser/safeMode.js                   (content/safeMode.js)
         content/browser/safeMode.xul                  (content/safeMode.xul)
         content/browser/sanitize.xul                  (content/sanitize.xul)
         content/browser/sanitizeDialog.js             (content/sanitizeDialog.js)
         content/browser/sanitizeDialog.css            (content/sanitizeDialog.css)