Bug 1067325 - Extract view source core to support tabs. r=mconley
authorJ. Ryan Stinnett <jryans@gmail.com>
Mon, 18 May 2015 11:10:41 -0500
changeset 265166 9885070539900dcb29a5a75524c474a49f07b27b
parent 265165 abc3ecdd131ae91c6bfbd17a32ce84ed7626554c
child 265167 8217cfec9f58c5a837fbee871b9d1ff14cfba039
push id2101
push userjryans@gmail.com
push dateMon, 18 May 2015 16:11:00 +0000
reviewersmconley
bugs1067325
milestone41.0a1
Bug 1067325 - Extract view source core to support tabs. r=mconley
browser/app/profile/firefox.js
browser/base/content/browser.js
toolkit/components/viewsource/ViewSourceBrowser.jsm
toolkit/components/viewsource/content/viewPartialSource.js
toolkit/components/viewsource/content/viewPartialSource.xul
toolkit/components/viewsource/content/viewSource-content.js
toolkit/components/viewsource/content/viewSource.js
toolkit/components/viewsource/content/viewSource.xul
toolkit/components/viewsource/content/viewSourceUtils.js
toolkit/components/viewsource/jar.mn
toolkit/components/viewsource/moz.build
toolkit/components/viewsource/test/browser/browser_gotoline.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1907,8 +1907,10 @@ pref("browser.reader.detectedFirstArticl
 pref("reader.parse-node-limit", 0);
 
 pref("browser.pocket.enabled", true);
 pref("browser.pocket.api", "api.getpocket.com");
 pref("browser.pocket.site", "getpocket.com");
 pref("browser.pocket.oAuthConsumerKey", "40249-e88c401e1b1f2242d9e441c4");
 pref("browser.pocket.useLocaleList", true);
 pref("browser.pocket.enabledLocales", "en-US de es-ES ja ja-JP-mac ru");
+
+pref("view_source.tab", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -981,16 +981,17 @@ var gBrowserInit = {
     DevEdition.init();
     AboutPrivateBrowsingListener.init();
 
     let mm = window.getGroupMessageManager("browsers");
     mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
     mm.loadFrameScript("chrome://browser/content/content.js", true);
     mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
     mm.loadFrameScript("chrome://global/content/manifestMessages.js", true);
+    mm.loadFrameScript("chrome://global/content/viewSource-content.js", true);
 
     window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad);
 
     // initialize observers and listeners
     // and give C++ access to gBrowser
     XULBrowserWindow.init();
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(nsIWebNavigation)
@@ -2317,21 +2318,21 @@ function readFromClipboard()
  * @param aArgsOrDocument
  *        Either an object or a Document. Passing a Document is deprecated,
  *        and is not supported with e10s. This function will throw if
  *        aArgsOrDocument is a CPOW.
  *
  *        If aArgsOrDocument is an object, that object can take the
  *        following properties:
  *
- *        browser:
+ *        URL (required):
+ *          A string URL for the page we'd like to view the source of.
+ *        browser (optional):
  *          The browser containing the document that we would like to view the
- *          source of.
- *        URL:
- *          A string URL for the page we'd like to view the source of.
+ *          source of. This is required if outerWindowID is passed.
  *        outerWindowID (optional):
  *          The outerWindowID of the content window containing the document that
  *          we want to view the source of. You only need to provide this if you
  *          want to attempt to retrieve the document source from the network
  *          cache.
  *        lineNumber (optional):
  *          The line number to focus on once the source is loaded.
  */
@@ -2354,17 +2355,28 @@ function BrowserViewSourceOfDocument(aAr
     let outerWindowID = requestor.getInterface(Ci.nsIDOMWindowUtils)
                                  .outerWindowID;
     let URL = browser.currentURI.spec;
     args = { browser, outerWindowID, URL };
   } else {
     args = aArgsOrDocument;
   }
 
-  top.gViewSourceUtils.viewSource(args);
+  let inTab = Services.prefs.getBoolPref("view_source.tab");
+  if (inTab) {
+    let viewSourceURL = `view-source:${args.URL}`;
+    let tab = gBrowser.loadOneTab(viewSourceURL, {
+      relatedToCurrent: true,
+      inBackground: false
+    });
+    args.viewSourceBrowser = gBrowser.getBrowserForTab(tab);
+    top.gViewSourceUtils.viewSourceInBrowser(args);
+  } else {
+    top.gViewSourceUtils.viewSource(args);
+  }
 }
 
 /**
  * Opens the View Source dialog for the source loaded in the root
  * top-level document of the browser. This is really just a
  * convenience wrapper around BrowserViewSourceOfDocument.
  *
  * @param browser
new file mode 100644
--- /dev/null
+++ b/toolkit/components/viewsource/ViewSourceBrowser.jsm
@@ -0,0 +1,164 @@
+// -*- 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/. */
+
+const { utils: Cu, interfaces: Ci, classes: Cc } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+  "resource://gre/modules/Deprecated.jsm");
+
+this.EXPORTED_SYMBOLS = ["ViewSourceBrowser"];
+
+/**
+ * ViewSourceBrowser manages for the view source <browser> from the chrome side.
+ * It's companion frame script, viewSource-content.js, needs to be loaded as a
+ * frame script into the browser being managed.
+ *
+ * For a view source window using viewSource.xul, the script viewSource.js in
+ * the window extends an instance of this with more window specific functions.
+ * The page script takes care of load the companion frame script.
+ *
+ * For a view source tab (or some other non-window case), an instance of this is
+ * created by viewSourceUtils.js to wrap the <browser>.  The caller that manages
+ * the <browser> is responsible for ensuring the companion frame script has been
+ * loaded.
+ */
+this.ViewSourceBrowser = function ViewSourceBrowser(aBrowser) {
+  this._browser = aBrowser;
+  this.init();
+}
+
+ViewSourceBrowser.prototype = {
+  /**
+   * The <browser> that will be displaying the view source content.
+   */
+  get browser() {
+    return this._browser;
+  },
+
+  /**
+   * These are the messages that ViewSourceBrowser will listen for
+   * from the frame script it injects. Any message names added here
+   * will automatically have ViewSourceBrowser listen for those messages,
+   * and remove the listeners on teardown.
+   */
+  // TODO: Some messages will appear here in a later patch
+  messages: [
+  ],
+
+  /**
+   * This should be called as soon as the script loads. When this function
+   * executes, we can assume the DOM content has not yet loaded.
+   */
+  init() {
+    this.messages.forEach((msgName) => {
+      this.mm.addMessageListener(msgName, this);
+    });
+  },
+
+  /**
+   * This should be called when the window is closing. This function should
+   * clean up event and message listeners.
+   */
+  uninit() {
+    this.messages.forEach((msgName) => {
+      this.mm.removeMessageListener(msgName, this);
+    });
+  },
+
+  /**
+   * Anything added to the messages array will get handled here, and should
+   * get dispatched to a specific function for the message name.
+   */
+  receiveMessage(message) {
+    let data = message.data;
+
+    // TODO: Some messages will appear here in a later patch
+    switch(message.name) {
+      default:
+        // Unhandled
+        return false;
+    }
+  },
+
+  /**
+   * Getter for the message manager of the view source browser.
+   */
+  get mm() {
+    return this.browser.messageManager;
+  },
+
+  /**
+   * Send a message to the view source browser.
+   */
+  sendAsyncMessage(...args) {
+    this.browser.messageManager.sendAsyncMessage(...args);
+  },
+
+  /**
+   * Loads the source for a URL while applying some optional features if
+   * enabled.
+   *
+   * For the viewSource.xul window, this is called by onXULLoaded above.
+   * For view source in a specific browser, this is manually called after
+   * this object is constructed.
+   *
+   * This takes a single object argument containing:
+   *
+   *   URL (required):
+   *     A string URL for the page we'd like to view the source of.
+   *   browser:
+   *     The browser containing the document that we would like to view the
+   *     source of. This argument is optional if outerWindowID is not passed.
+   *   outerWindowID (optional):
+   *     The outerWindowID of the content window containing the document that
+   *     we want to view the source of. This is the only way of attempting to
+   *     load the source out of the network cache.
+   *   lineNumber (optional):
+   *     The line number to focus on once the source is loaded.
+   */
+  loadViewSource({ URL, browser, outerWindowID, lineNumber }) {
+    if (!URL) {
+      throw new Error("Must supply a URL when opening view source.");
+    }
+
+    if (browser) {
+      // If we're dealing with a remote browser, then the browser
+      // for view source needs to be remote as well.
+      this.updateBrowserRemoteness(browser.isRemoteBrowser);
+    } else {
+      if (outerWindowID) {
+        throw new Error("Must supply the browser if passing the outerWindowID");
+      }
+    }
+
+    this.sendAsyncMessage("ViewSource:LoadSource",
+                          { URL, outerWindowID, lineNumber });
+  },
+
+  /**
+   * Updates the "remote" attribute of the view source browser. This
+   * will remove the browser from the DOM, and then re-add it in the
+   * same place it was taken from.
+   *
+   * @param shouldBeRemote
+   *        True if the browser should be made remote. If the browsers
+   *        remoteness already matches this value, this function does
+   *        nothing.
+   */
+  updateBrowserRemoteness(shouldBeRemote) {
+    if (this.browser.isRemoteBrowser != shouldBeRemote) {
+      // In this base case, where we are handed a <browser> someone else is
+      // managing, we don't know for sure that it's safe to toggle remoteness.
+      // For view source in a window, this is overridden to actually do the
+      // flip if needed.
+      throw new Error("View source browser's remoteness mismatch");
+    }
+  },
+};
--- a/toolkit/components/viewsource/content/viewPartialSource.js
+++ b/toolkit/components/viewsource/content/viewPartialSource.js
@@ -169,17 +169,17 @@ function viewPartialSourceForSelection(s
   // before drawing the selection.
   if (canDrawSelection) {
     window.document.getElementById("content").addEventListener("load", drawSelection, true);
   }
 
   // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
   var loadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
   var referrerPolicy = Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT;
-  ViewSourceChrome.webNav.loadURIWithOptions((isHTML ?
+  viewSourceChrome.webNav.loadURIWithOptions((isHTML ?
                                               "view-source:data:text/html;charset=utf-8," :
                                               "view-source:data:application/xml;charset=utf-8,")
                                              + encodeURIComponent(tmpNode.innerHTML),
                                              loadFlags,
                                              null, referrerPolicy,  // referrer
                                              null, null,  // postData, headers
                                              Services.io.newURI(doc.baseURI, null, null));
 }
--- a/toolkit/components/viewsource/content/viewPartialSource.xul
+++ b/toolkit/components/viewsource/content/viewPartialSource.xul
@@ -45,19 +45,19 @@
   <command id="cmd_close" oncommand="window.close();"/>
   <commandset id="editMenuCommands"/>
   <command id="cmd_find"
            oncommand="document.getElementById('FindToolbar').onFindCommand();"/>
   <command id="cmd_findAgain"
            oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
   <command id="cmd_findPrevious"
            oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
-  <command id="cmd_goToLine" oncommand="ViewSourceChrome.promptAndGoToLine();" disabled="true"/>
-  <command id="cmd_highlightSyntax" oncommand="ViewSourceChrome.toggleSyntaxHighlighting();"/>
-  <command id="cmd_wrapLongLines" oncommand="ViewSourceChrome.toggleWrapping();"/>
+  <command id="cmd_goToLine" oncommand="viewSourceChrome.promptAndGoToLine();" disabled="true"/>
+  <command id="cmd_highlightSyntax" oncommand="viewSourceChrome.toggleSyntaxHighlighting();"/>
+  <command id="cmd_wrapLongLines" oncommand="viewSourceChrome.toggleWrapping();"/>
   <command id="cmd_textZoomReduce" oncommand="ZoomManager.reduce();"/>
   <command id="cmd_textZoomEnlarge" oncommand="ZoomManager.enlarge();"/>
   <command id="cmd_textZoomReset" oncommand="ZoomManager.reset();"/>
 
   <keyset id="editMenuKeys"/>
   <keyset id="viewSourceKeys">
     <key id="key_savePage" key="&savePageCmd.commandkey;" modifiers="accel" command="cmd_savePage"/>
     <key id="key_print" key="&printCmd.commandkey;" modifiers="accel" command="cmd_print"/>
@@ -75,21 +75,21 @@
 
   <menupopup id="viewSourceContextMenu">
     <menuitem id="cMenu_findAgain"/>
     <menuseparator/>
     <menuitem id="cMenu_copy"/>
     <menuitem id="context-copyLink"
               label="&copyLinkCmd.label;"
               accesskey="&copyLinkCmd.accesskey;"
-              oncommand="ViewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
+              oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
     <menuitem id="context-copyEmail"
               label="&copyEmailCmd.label;"
               accesskey="&copyEmailCmd.accesskey;"
-              oncommand="ViewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
+              oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
     <menuseparator/>
     <menuitem id="cMenu_selectAll"/>
   </menupopup>
 
   <!-- Menu -->
   <toolbox id="viewSource-toolbox">
     <menubar id="viewSource-main-menubar">
 
--- a/toolkit/components/viewsource/content/viewSource-content.js
+++ b/toolkit/components/viewsource/content/viewSource-content.js
@@ -46,16 +46,21 @@ let ViewSourceContent = {
    * and removed on pagehide. When the initial about:blank is transitioned
    * away from, a pagehide is fired without us having attached ourselves
    * first. We use this boolean to keep track of whether or not we're
    * attached, so we don't attempt to remove our listener when it's not
    * yet there (which throws).
    */
   selectionListenerAttached: false,
 
+  get isViewSource() {
+    let uri = content.document.documentURI;
+    return uri == "about:blank" || uri.startsWith("view-source:");
+  },
+
   /**
    * This should be called as soon as this frame script has loaded.
    */
   init() {
     this.messages.forEach((msgName) => {
       addMessageListener(msgName, this);
     });
 
@@ -88,16 +93,19 @@ let ViewSourceContent = {
     }
   },
 
   /**
    * Anything added to the messages array will get handled here, and should
    * get dispatched to a specific function for the message name.
    */
   receiveMessage(msg) {
+    if (!this.isViewSource) {
+      return;
+    }
     let data = msg.data;
     let objects = msg.objects;
     switch(msg.name) {
       case "ViewSource:LoadSource":
         this.viewSource(data.URL, data.outerWindowID, data.lineNumber,
                         data.shouldWrap);
         break;
       case "ViewSource:LoadSourceDeprecated":
@@ -119,16 +127,19 @@ let ViewSourceContent = {
     }
   },
 
   /**
    * Any events should get handled here, and should get dispatched to
    * a specific function for the event type.
    */
   handleEvent(event) {
+    if (!this.isViewSource) {
+      return;
+    }
     switch(event.type) {
       case "pagehide":
         this.onPageHide(event);
         break;
       case "pageshow":
         this.onPageShow(event);
         break;
       case "click":
@@ -247,16 +258,21 @@ let ViewSourceContent = {
     let loadFromURL = false;
 
     if (forcedCharSet) {
       docShell.charset = forcedCharSet;
     }
 
     if (lineNumber) {
       let doneLoading = (event) => {
+        let uri = content.document.documentURI;
+        // Ignore possible initial load of about:blank
+        if (uri == "about:blank") {
+          return;
+        }
         this.goToLine(lineNumber);
         removeEventListener("pageshow", doneLoading);
       };
 
       addEventListener("pageshow", doneLoading);
     }
 
     if (!pageDescriptor) {
@@ -336,21 +352,22 @@ let ViewSourceContent = {
 
   /**
    * Handler for the pageshow event.
    *
    * @param event
    *        The pageshow event being handled.
    */
   onPageShow(event) {
-    content.getSelection()
-           .QueryInterface(Ci.nsISelectionPrivate)
-           .addSelectionListener(this);
-    this.selectionListenerAttached = true;
-
+    let selection = content.getSelection();
+    if (selection) {
+      selection.QueryInterface(Ci.nsISelectionPrivate)
+               .addSelectionListener(this);
+      this.selectionListenerAttached = true;
+    }
     content.focus();
     sendAsyncMessage("ViewSource:SourceLoaded");
   },
 
   /**
    * Handler for the pagehide event.
    *
    * @param event
--- a/toolkit/components/viewsource/content/viewSource.js
+++ b/toolkit/components/viewsource/content/viewSource.js
@@ -2,16 +2,17 @@
 
 /* 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 { utils: Cu, interfaces: Ci, classes: Cc } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/ViewSourceBrowser.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
   "resource://gre/modules/CharsetMenu.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
   "resource://gre/modules/Deprecated.jsm");
 
@@ -26,19 +27,35 @@ XPCOMUtils.defineLazyModuleGetter(this, 
       return null;
     delete window[name];
     return window[name] = element;
   });
 });
 
 /**
  * ViewSourceChrome is the primary interface for interacting with
- * the view source browser. It initializes itself on script load.
+ * the view source browser from a self-contained window.  It extends
+ * ViewSourceBrowser with additional things needed inside the special window.
+ *
+ * It initializes itself on script load.
  */
-let ViewSourceChrome = {
+function ViewSourceChrome() {
+  ViewSourceBrowser.call(this);
+}
+
+ViewSourceChrome.prototype = {
+  __proto__: ViewSourceBrowser.prototype,
+
+  /**
+   * The <browser> that will be displaying the view source content.
+   */
+  get browser() {
+    return gBrowser;
+  },
+
   /**
    * Holds the value of the last line found via the "Go to line"
    * command, to pre-populate the prompt the next time it is
    * opened.
    */
   lastLineFound: null,
 
   /**
@@ -50,78 +67,76 @@ let ViewSourceChrome = {
   contextMenuData: {},
 
   /**
    * These are the messages that ViewSourceChrome will listen for
    * from the frame script it injects. Any message names added here
    * will automatically have ViewSourceChrome listen for those messages,
    * and remove the listeners on teardown.
    */
-  messages: [
+  messages: ViewSourceBrowser.prototype.messages.concat([
     "ViewSource:SourceLoaded",
     "ViewSource:SourceUnloaded",
     "ViewSource:Close",
     "ViewSource:OpenURL",
     "ViewSource:GoToLine:Success",
     "ViewSource:GoToLine:Failed",
     "ViewSource:UpdateStatus",
     "ViewSource:ContextMenuOpening",
-  ],
+  ]),
 
   /**
-   * This should be called as soon as the script loads. When this function
-   * executes, we can assume the DOM content has not yet loaded.
+   * This called via ViewSourceBrowser's constructor.  This should be called as
+   * soon as the script loads.  When this function executes, we can assume the
+   * DOM content has not yet loaded.
    */
   init() {
-    // We use the window message manager so that if we switch remoteness of the
-    // browser (which we might do if we're attempting to load the document
-    // source out of the network cache), we automatically re-load the frame
-    // script.
-    let wMM = window.messageManager;
-    wMM.loadFrameScript("chrome://global/content/viewSource-content.js", true);
-    this.messages.forEach((msgName) => {
-      wMM.addMessageListener(msgName, this);
-    });
+    this.mm.loadFrameScript("chrome://global/content/viewSource-content.js", true);
 
     this.shouldWrap = Services.prefs.getBoolPref("view_source.wrap_long_lines");
     this.shouldHighlight =
       Services.prefs.getBoolPref("view_source.syntax_highlight");
 
     addEventListener("load", this);
     addEventListener("unload", this);
     addEventListener("AppCommand", this, true);
     addEventListener("MozSwipeGesture", this, true);
+
+    ViewSourceBrowser.prototype.init.call(this);
   },
 
   /**
    * This should be called when the window is closing. This function should
    * clean up event and message listeners.
    */
   uninit() {
-    let wMM = window.messageManager;
-    this.messages.forEach((msgName) => {
-      wMM.removeMessageListener(msgName, this);
-    });
+    ViewSourceBrowser.prototype.uninit.call(this);
 
     // "load" event listener is removed in its handler, to
     // ensure we only fire it once.
     removeEventListener("unload", this);
     removeEventListener("AppCommand", this, true);
     removeEventListener("MozSwipeGesture", this, true);
     gContextMenu.removeEventListener("popupshowing", this);
     gContextMenu.removeEventListener("popuphidden", this);
-    Services.els.removeSystemEventListener(gBrowser, "dragover", this, true);
-    Services.els.removeSystemEventListener(gBrowser, "drop", this, true);
+    Services.els.removeSystemEventListener(this.browser, "dragover", this,
+                                           true);
+    Services.els.removeSystemEventListener(this.browser, "drop", this, true);
   },
 
   /**
    * Anything added to the messages array will get handled here, and should
    * get dispatched to a specific function for the message name.
    */
   receiveMessage(message) {
+    if (ViewSourceBrowser.prototype.receiveMessage.call(this, message)) {
+      // Handled in super class
+      return;
+    }
+
     let data = message.data;
 
     switch(message.name) {
       case "ViewSource:SourceLoaded":
         this.onSourceLoaded();
         break;
       case "ViewSource:SourceUnloaded":
         this.onSourceUnloaded();
@@ -138,17 +153,17 @@ let ViewSourceChrome = {
       case "ViewSource:GoToLine:Success":
         this.onGoToLineSuccess(data.lineNumber);
         break;
       case "ViewSource:UpdateStatus":
         this.updateStatus(data.label);
         break;
       case "ViewSource:ContextMenuOpening":
         this.onContextMenuOpening(data.isLink, data.isEmail, data.href);
-        if (gBrowser.isRemoteBrowser) {
+        if (this.browser.isRemoteBrowser) {
           this.openContextMenu(data.screenX, data.screenY);
         }
         break;
     }
   },
 
   /**
    * Any events should get handled here, and should get dispatched to
@@ -183,45 +198,52 @@ let ViewSourceChrome = {
     }
   },
 
   /**
    * Getter that returns whether or not the view source browser
    * has history enabled on it.
    */
   get historyEnabled() {
-    return !gBrowser.hasAttribute("disablehistory");
+    return !this.browser.hasAttribute("disablehistory");
   },
 
   /**
-   * Getter for the message manager of the view source browser.
+   * Getter for the message manager used to communicate with the view source
+   * browser.
+   *
+   * In this window version of view source, we use the window message manager
+   * for loading scripts and listening for messages so that if we switch
+   * remoteness of the browser (which we might do if we're attempting to load
+   * the document source out of the network cache), we automatically re-load
+   * the frame script.
    */
   get mm() {
-    return gBrowser.messageManager;
+    return window.messageManager;
   },
 
   /**
    * Getter for the nsIWebNavigation of the view source browser.
    */
   get webNav() {
-    return gBrowser.webNavigation;
+    return this.browser.webNavigation;
   },
 
   /**
    * Send the browser forward in its history.
    */
   goForward() {
-    gBrowser.goForward();
+    this.browser.goForward();
   },
 
   /**
    * Send the browser backward in its history.
    */
   goBack() {
-    gBrowser.goBack();
+    this.browser.goBack();
   },
 
   /**
    * This should be called once when the DOM has finished loading. Here we
    * set the state of various menu items, and add event listeners to
    * DOM nodes.
    *
    * This is also the place where we handle any arguments that have been
@@ -262,113 +284,94 @@ let ViewSourceChrome = {
     let highlightMenuItem = document.getElementById("menu_highlightSyntax");
     if (this.shouldHighlight) {
       highlightMenuItem.setAttribute("checked", "true");
     }
 
     gContextMenu.addEventListener("popupshowing", this);
     gContextMenu.addEventListener("popuphidden", this);
 
-    Services.els.addSystemEventListener(gBrowser, "dragover", this, true);
-    Services.els.addSystemEventListener(gBrowser, "drop", this, true);
+    Services.els.addSystemEventListener(this.browser, "dragover", this, true);
+    Services.els.addSystemEventListener(this.browser, "drop", this, true);
 
     if (!this.historyEnabled) {
       // Disable the BACK and FORWARD commands and hide the related menu items.
       let viewSourceNavigation = document.getElementById("viewSourceNavigation");
       if (viewSourceNavigation) {
         viewSourceNavigation.setAttribute("disabled", "true");
         viewSourceNavigation.setAttribute("hidden", "true");
       }
     }
 
     // This will only work with non-remote browsers. See bug 1158377.
-    gBrowser.droppedLinkHandler = function (event, url, name) {
-      ViewSourceChrome.loadURL(url);
+    this.browser.droppedLinkHandler = (event, url, name) => {
+      this.loadURL(url);
       event.preventDefault();
     };
 
     // We require the first argument to do any loading of source.
     // otherwise, we're done.
     if (!window.arguments[0]) {
       return;
     }
 
     if (typeof window.arguments[0] == "string") {
       // We're using the deprecated API
-      return ViewSourceChrome._loadViewSourceDeprecated();
+      return this._loadViewSourceDeprecated(window.arguments);
     }
 
     // We're using the modern API, which allows us to view the
     // source of documents from out of process browsers.
     let args = window.arguments[0];
-
-    if (!args.URL) {
-      throw new Error("Must supply a URL when opening view source.");
-    }
-
-    if (args.browser) {
-      // If we're dealing with a remote browser, then the browser
-      // for view source needs to be remote as well.
-      this.updateBrowserRemoteness(args.browser.isRemoteBrowser);
-    } else {
-      if (args.outerWindowID) {
-        throw new Error("Must supply the browser if passing the outerWindowID");
-      }
-    }
-
-    this.mm.sendAsyncMessage("ViewSource:LoadSource", {
-      URL: args.URL,
-      outerWindowID: args.outerWindowID,
-      lineNumber: args.lineNumber,
-    });
+    this.loadViewSource(args);
   },
 
   /**
    * This is the deprecated API for viewSource.xul, for old-timer consumers.
    * This API might eventually go away.
    */
-  _loadViewSourceDeprecated() {
+  _loadViewSourceDeprecated(aArguments) {
     Deprecated.warning("The arguments you're passing to viewSource.xul " +
                        "are using an out-of-date API.",
                        "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
     // Parse the 'arguments' supplied with the dialog.
     //    arg[0] - URL string.
     //    arg[1] - Charset value in the form 'charset=xxx'.
     //    arg[2] - Page descriptor used to load content from the cache.
     //    arg[3] - Line number to go to.
     //    arg[4] - Whether charset was forced by the user
 
-    if (window.arguments[3] == "selection" ||
-        window.arguments[3] == "mathml") {
+    if (aArguments[3] == "selection" ||
+        aArguments[3] == "mathml") {
       // viewPartialSource.js will take care of loading the content.
       return;
     }
 
-    if (window.arguments[2]) {
-      let pageDescriptor = window.arguments[2];
+    if (aArguments[2]) {
+      let pageDescriptor = aArguments[2];
       if (Cu.isCrossProcessWrapper(pageDescriptor)) {
         throw new Error("Cannot pass a CPOW as the page descriptor to viewSource.xul.");
       }
     }
 
-    if (gBrowser.isRemoteBrowser) {
+    if (this.browser.isRemoteBrowser) {
       throw new Error("Deprecated view source API should not use a remote browser.");
     }
 
     let forcedCharSet;
-    if (window.arguments[4] && window.arguments[1].startsWith("charset=")) {
-      forcedCharSet = window.arguments[1].split("=")[1];
+    if (aArguments[4] && aArguments[1].startsWith("charset=")) {
+      forcedCharSet = aArguments[1].split("=")[1];
     }
 
-    gBrowser.messageManager.sendAsyncMessage("ViewSource:LoadSourceDeprecated", {
-      URL: window.arguments[0],
-      lineNumber: window.arguments[3],
+    this.sendAsyncMessage("ViewSource:LoadSourceDeprecated", {
+      URL: aArguments[0],
+      lineNumber: aArguments[3],
       forcedCharSet,
     }, {
-      pageDescriptor: window.arguments[2],
+      pageDescriptor: aArguments[2],
     });
   },
 
   /**
    * Handler for the AppCommand event.
    *
    * @param event
    *        The AppCommand event being handled.
@@ -415,17 +418,17 @@ let ViewSourceChrome = {
    */
   onSourceLoaded() {
     document.getElementById("cmd_goToLine").removeAttribute("disabled");
 
     if (this.historyEnabled) {
       this.updateCommands();
     }
 
-    gBrowser.focus();
+    this.browser.focus();
   },
 
   /**
    * Called as soon as the frame script reports that some source
    * code has been unloaded from the browser.
    */
   onSourceUnloaded() {
     // Disable "go to line" while reloading due to e.g. change of charset
@@ -441,23 +444,24 @@ let ViewSourceChrome = {
    *        The click event on a character set menuitem.
    */
   onSetCharacterSet(event) {
     if (event.target.hasAttribute("charset")) {
       let charset = event.target.getAttribute("charset");
 
       // If we don't have history enabled, we have to do a reload in order to
       // show the character set change. See bug 136322.
-      this.mm.sendAsyncMessage("ViewSource:SetCharacterSet", {
+      this.sendAsyncMessage("ViewSource:SetCharacterSet", {
         charset: charset,
         doPageLoad: this.historyEnabled,
       });
 
       if (this.historyEnabled) {
-        gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
+        this.browser
+            .reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
       }
     }
   },
 
   /**
    * Called from the frame script when the context menu is about to
    * open. This tells ViewSourceChrome things about the item that
    * the context menu is being opened on. This should be called before
@@ -578,17 +582,17 @@ let ViewSourceChrome = {
   /**
    * Loads the source of a URL. This will probably end up hitting the
    * network.
    *
    * @param URL
    *        A URL string to be opened in the view source browser.
    */
   loadURL(URL) {
-    this.mm.sendAsyncMessage("ViewSource:LoadSource", { URL });
+    this.sendAsyncMessage("ViewSource:LoadSource", { URL });
   },
 
   /**
    * Updates any commands that are dependant on command broadcasters.
    */
   updateCommands() {
     let backBroadcaster = document.getElementById("Browser:Back");
     let forwardBroadcaster = document.getElementById("Browser:Forward");
@@ -651,17 +655,17 @@ let ViewSourceChrome = {
 
   /**
    * Go to a particular line of the source code. This act is asynchronous.
    *
    * @param lineNumber
    *        The line number to try to go to to.
    */
   goToLine(lineNumber) {
-    this.mm.sendAsyncMessage("ViewSource:GoToLine", { lineNumber });
+    this.sendAsyncMessage("ViewSource:GoToLine", { lineNumber });
   },
 
   /**
    * Called when the frame script reports that a line was successfully gotten
    * to.
    *
    * @param lineNumber
    *        The line number that we successfully got to.
@@ -685,18 +689,18 @@ let ViewSourceChrome = {
                           gViewSourceBundle.getString("outOfRangeText"));
     this.promptAndGoToLine();
   },
 
   /**
    * Reloads the browser, bypassing the network cache.
    */
   reload() {
-    gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
-                             Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+    this.browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
+                                 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
   },
 
   /**
    * Closes the view source window.
    */
   close() {
     window.close();
   },
@@ -705,78 +709,78 @@ let ViewSourceChrome = {
    * Called when the user clicks on the "Wrap Long Lines" menu item, and
    * flips the user preference for wrapping long lines in the view source
    * browser.
    */
   toggleWrapping() {
     this.shouldWrap = !this.shouldWrap;
     Services.prefs.setBoolPref("view_source.wrap_long_lines",
                                this.shouldWrap);
-    this.mm.sendAsyncMessage("ViewSource:ToggleWrapping");
+    this.sendAsyncMessage("ViewSource:ToggleWrapping");
   },
 
   /**
    * Called when the user clicks on the "Syntax Highlighting" menu item, and
    * flips the user preference for wrapping long lines in the view source
    * browser.
    */
   toggleSyntaxHighlighting() {
     this.shouldHighlight = !this.shouldHighlight;
     // We can't flip this value in the child, since prefs are read-only there.
     // We flip it here, and then cause a reload in the child to make the change
     // occur.
     Services.prefs.setBoolPref("view_source.syntax_highlight",
                                this.shouldHighlight);
-    this.mm.sendAsyncMessage("ViewSource:ToggleSyntaxHighlighting");
+    this.sendAsyncMessage("ViewSource:ToggleSyntaxHighlighting");
   },
 
   /**
    * Updates the "remote" attribute of the view source browser. This
    * will remove the browser from the DOM, and then re-add it in the
    * same place it was taken from.
    *
    * @param shouldBeRemote
    *        True if the browser should be made remote. If the browsers
    *        remoteness already matches this value, this function does
    *        nothing.
    */
   updateBrowserRemoteness(shouldBeRemote) {
-    if (gBrowser.isRemoteBrowser == shouldBeRemote) {
+    if (this.browser.isRemoteBrowser == shouldBeRemote) {
       return;
     }
 
-    let parentNode = gBrowser.parentNode;
-    let nextSibling = gBrowser.nextSibling;
+    let parentNode = this.browser.parentNode;
+    let nextSibling = this.browser.nextSibling;
 
-    gBrowser.remove();
+    this.browser.remove();
     if (shouldBeRemote) {
-      gBrowser.setAttribute("remote", "true");
+      this.browser.setAttribute("remote", "true");
     } else {
-      gBrowser.removeAttribute("remote");
+      this.browser.removeAttribute("remote");
     }
     // If nextSibling was null, this will put the browser at
     // the end of the list.
-    parentNode.insertBefore(gBrowser, nextSibling);
+    parentNode.insertBefore(this.browser, nextSibling);
 
     if (shouldBeRemote) {
       // We're going to send a message down to the remote browser
       // to load the source content - however, in order for the
       // contentWindowAsCPOW and contentDocumentAsCPOW values on
       // the remote browser to be set, we must set up the
       // RemoteWebProgress, which is lazily loaded. We only need
       // contentWindowAsCPOW for the printing support, and this
       // should go away once bug 1146454 is fixed, since we can
-      // then just pass the outerWindowID of the gBrowser to
+      // then just pass the outerWindowID of the this.browser to
       // PrintUtils.
-      gBrowser.webProgress;
+      this.browser.webProgress;
     }
   },
 };
 
-ViewSourceChrome.init();
+let viewSourceChrome = new ViewSourceChrome();
 
 /**
  * PrintUtils uses this to make Print Preview work.
  */
 let PrintPreviewListener = {
   getPrintPreviewBrowser() {
     let browser = document.getElementById("ppBrowser");
     if (!browser) {
@@ -815,17 +819,17 @@ let PrintPreviewListener = {
 };
 
 // viewZoomOverlay.js uses this
 function getBrowser() {
   return gBrowser;
 }
 
 this.__defineGetter__("gPageLoader", function () {
-  var webnav = ViewSourceChrome.webNav;
+  var webnav = viewSourceChrome.webNav;
   if (!webnav)
     return null;
   delete this.gPageLoader;
   this.gPageLoader = (webnav instanceof Ci.nsIWebPageDescriptor) ? webnav
                                                                  : null;
   return this.gPageLoader;
 });
 
@@ -838,98 +842,98 @@ function ViewSourceSavePage()
                gPageLoader);
 }
 
 // Below are old deprecated functions and variables left behind for
 // compatibility reasons. These will be removed soon via bug 1159293.
 
 this.__defineGetter__("gLastLineFound", function () {
   Deprecated.warning("gLastLineFound is deprecated - please use " +
-                     "ViewSourceChrome.lastLineFound instead.",
+                     "viewSourceChrome.lastLineFound instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  return ViewSourceChrome.lastLineFound;
+  return viewSourceChrome.lastLineFound;
 });
 
 function onLoadViewSource() {
   Deprecated.warning("onLoadViewSource() is deprecated - please use " +
-                     "ViewSourceChrome.onXULLoaded() instead.",
+                     "viewSourceChrome.onXULLoaded() instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  ViewSourceChrome.onXULLoaded();
+  viewSourceChrome.onXULLoaded();
 }
 
 function isHistoryEnabled() {
   Deprecated.warning("isHistoryEnabled() is deprecated - please use " +
-                     "ViewSourceChrome.historyEnabled instead.",
+                     "viewSourceChrome.historyEnabled instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  return ViewSourceChrome.historyEnabled;
+  return viewSourceChrome.historyEnabled;
 }
 
 function ViewSourceClose() {
   Deprecated.warning("ViewSourceClose() is deprecated - please use " +
-                     "ViewSourceChrome.close() instead.",
+                     "viewSourceChrome.close() instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  ViewSourceChrome.close();
+  viewSourceChrome.close();
 }
 
 function ViewSourceReload() {
   Deprecated.warning("ViewSourceReload() is deprecated - please use " +
-                     "ViewSourceChrome.reload() instead.",
+                     "viewSourceChrome.reload() instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  ViewSourceChrome.reload();
+  viewSourceChrome.reload();
 }
 
 function getWebNavigation()
 {
   Deprecated.warning("getWebNavigation() is deprecated - please use " +
-                     "ViewSourceChrome.webNav instead.",
+                     "viewSourceChrome.webNav instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
   // The original implementation returned null if anything threw during
   // the getting of the webNavigation.
   try {
-    return ViewSourceChrome.webNav;
+    return viewSourceChrome.webNav;
   } catch (e) {
     return null;
   }
 }
 
 function viewSource(url) {
   Deprecated.warning("viewSource() is deprecated - please use " +
-                     "ViewSourceChrome.loadURL() instead.",
+                     "viewSourceChrome.loadURL() instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  ViewSourceChrome.loadURL(url);
+  viewSourceChrome.loadURL(url);
 }
 
 function ViewSourceGoToLine()
 {
   Deprecated.warning("ViewSourceGoToLine() is deprecated - please use " +
-                     "ViewSourceChrome.promptAndGoToLine() instead.",
+                     "viewSourceChrome.promptAndGoToLine() instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  ViewSourceChrome.promptAndGoToLine();
+  viewSourceChrome.promptAndGoToLine();
 }
 
 function goToLine(line)
 {
   Deprecated.warning("goToLine() is deprecated - please use " +
-                     "ViewSourceChrome.goToLine() instead.",
+                     "viewSourceChrome.goToLine() instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  ViewSourceChrome.goToLine(line);
+  viewSourceChrome.goToLine(line);
 }
 
 function BrowserForward(aEvent) {
   Deprecated.warning("BrowserForward() is deprecated - please use " +
-                     "ViewSourceChrome.goForward() instead.",
+                     "viewSourceChrome.goForward() instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  ViewSourceChrome.goForward();
+  viewSourceChrome.goForward();
 }
 
 function BrowserBack(aEvent) {
   Deprecated.warning("BrowserBack() is deprecated - please use " +
-                     "ViewSourceChrome.goBack() instead.",
+                     "viewSourceChrome.goBack() instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  ViewSourceChrome.goBack();
+  viewSourceChrome.goBack();
 }
 
 function UpdateBackForwardCommands() {
   Deprecated.warning("UpdateBackForwardCommands() is deprecated - please use " +
-                     "ViewSourceChrome.updateCommands() instead.",
+                     "viewSourceChrome.updateCommands() instead.",
                      "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/View_Source_for_XUL_Applications");
-  ViewSourceChrome.updateCommands();
+  viewSourceChrome.updateCommands();
 }
--- a/toolkit/components/viewsource/content/viewSource.xul
+++ b/toolkit/components/viewsource/content/viewSource.xul
@@ -49,26 +49,26 @@
   <command id="cmd_findAgain"
            oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
   <command id="cmd_findPrevious"
            oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
 #ifdef XP_MACOSX
   <command id="cmd_findSelection"
            oncommand="document.getElementById('FindToolbar').onFindSelectionCommand();"/>
 #endif
-  <command id="cmd_reload" oncommand="ViewSourceChrome.reload();"/>
-  <command id="cmd_goToLine" oncommand="ViewSourceChrome.promptAndGoToLine();" disabled="true"/>
-  <command id="cmd_highlightSyntax" oncommand="ViewSourceChrome.toggleSyntaxHighlighting();"/>
-  <command id="cmd_wrapLongLines" oncommand="ViewSourceChrome.toggleWrapping();"/>
+  <command id="cmd_reload" oncommand="viewSourceChrome.reload();"/>
+  <command id="cmd_goToLine" oncommand="viewSourceChrome.promptAndGoToLine();" disabled="true"/>
+  <command id="cmd_highlightSyntax" oncommand="viewSourceChrome.toggleSyntaxHighlighting();"/>
+  <command id="cmd_wrapLongLines" oncommand="viewSourceChrome.toggleWrapping();"/>
   <command id="cmd_textZoomReduce" oncommand="ZoomManager.reduce();"/>
   <command id="cmd_textZoomEnlarge" oncommand="ZoomManager.enlarge();"/>
   <command id="cmd_textZoomReset" oncommand="ZoomManager.reset();"/>
 
-  <command id="Browser:Back" oncommand="ViewSourceChrome.goBack()" observes="viewSourceNavigation"/>
-  <command id="Browser:Forward" oncommand="ViewSourceChrome.goForward()" observes="viewSourceNavigation"/>
+  <command id="Browser:Back" oncommand="viewSourceChrome.goBack()" observes="viewSourceNavigation"/>
+  <command id="Browser:Forward" oncommand="viewSourceChrome.goForward()" observes="viewSourceNavigation"/>
 
   <broadcaster id="viewSourceNavigation"/>
 
   <keyset id="editMenuKeys"/>
   <keyset id="viewSourceKeys">
     <key id="key_savePage" key="&savePageCmd.commandkey;" modifiers="accel" command="cmd_savePage"/>
     <key id="key_print" key="&printCmd.commandkey;" modifiers="accel" command="cmd_print"/>
     <key id="key_close" key="&closeCmd.commandkey;" modifiers="accel" command="cmd_close"/>
@@ -126,21 +126,21 @@
               observes="viewSourceNavigation"/>
     <menuseparator observes="viewSourceNavigation"/>
     <menuitem id="cMenu_findAgain"/>
     <menuseparator/>
     <menuitem id="cMenu_copy"/>
     <menuitem id="context-copyLink"
               label="&copyLinkCmd.label;"
               accesskey="&copyLinkCmd.accesskey;"
-              oncommand="ViewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
+              oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
     <menuitem id="context-copyEmail"
               label="&copyEmailCmd.label;"
               accesskey="&copyEmailCmd.accesskey;"
-              oncommand="ViewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
+              oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
     <menuseparator/>
     <menuitem id="cMenu_selectAll"/>
   </menupopup>
 
   <!-- Menu -->
   <toolbox id="viewSource-toolbox">
     <menubar id="viewSource-main-menubar">
 
@@ -201,17 +201,17 @@
                         key="key_textZoomReset"/>
             </menupopup>
           </menu>
 
           <!-- Charset Menu -->
           <menu id="charsetMenu"
                 label="&charsetMenu2.label;"
                 accesskey="&charsetMenu2.accesskey;"
-                oncommand="ViewSourceChrome.onSetCharacterSet(event);"
+                oncommand="viewSourceChrome.onSetCharacterSet(event);"
                 onpopupshowing="CharsetMenu.build(event.target);"
                 onpopupshown="CharsetMenu.update(event.target, content.document.characterSet);">
             <menupopup/>
           </menu>
           <menuseparator/>
           <menuitem id="menu_wrapLongLines" type="checkbox" command="cmd_wrapLongLines"
                     label="&menu_wrapLongLines.title;" accesskey="&menu_wrapLongLines.accesskey;"/>
           <menuitem type="checkbox" id="menu_highlightSyntax" command="cmd_highlightSyntax"
--- a/toolkit/components/viewsource/content/viewSourceUtils.js
+++ b/toolkit/components/viewsource/content/viewSourceUtils.js
@@ -7,16 +7,20 @@
 /*
  * To keep the global namespace safe, don't define global variables and
  * functions in this file.
  *
  * This file silently depends on contentAreaUtils.js for
  * getDefaultFileName, getNormalizedLeafName and getDefaultExtension
  */
 
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
+  "resource://gre/modules/ViewSourceBrowser.jsm");
+
 var gViewSourceUtils = {
 
   mnsIWebBrowserPersist: Components.interfaces.nsIWebBrowserPersist,
   mnsIWebProgress: Components.interfaces.nsIWebProgress,
   mnsIWebPageDescriptor: Components.interfaces.nsIWebPageDescriptor,
 
   /**
    * Opens the view source window.
@@ -55,16 +59,44 @@ var gViewSourceUtils = {
                           .getService(Components.interfaces.nsIPrefBranch);
     if (prefs.getBoolPref("view_source.editor.external")) {
       this.openInExternalEditor(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber);
     } else {
       this._openInInternalViewer(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber);
     }
   },
 
+  /**
+   * Displays view source in the provided <browser>.  This allows for non-window
+   * display methods, such as a tab from Firefox.  The caller that manages
+   * the <browser> is responsible for ensuring the companion frame script,
+   * viewSource-content.js, has been loaded for the <browser>.
+   *
+   * @param aArgs
+   *        An object with the following properties:
+   *
+   *        URL (required):
+   *          A string URL for the page we'd like to view the source of.
+   *        viewSourceBrowser (required):
+   *          The browser to display the view source in.
+   *        browser (optional):
+   *          The browser containing the document that we would like to view the
+   *          source of. This is required if outerWindowID is passed.
+   *        outerWindowID (optional):
+   *          The outerWindowID of the content window containing the document that
+   *          we want to view the source of. Pass this if you want to attempt to
+   *          load the document source out of the network cache.
+   *        lineNumber (optional):
+   *          The line number to focus on once the source is loaded.
+   */
+  viewSourceInBrowser: function(aArgs) {
+    let viewSourceBrowser = new ViewSourceBrowser(aArgs.viewSourceBrowser);
+    viewSourceBrowser.loadViewSource(aArgs);
+  },
+
   // Opens the interval view source viewer
   _openInInternalViewer: function(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber)
   {
     // try to open a view-source window while inheriting the charset (if any)
     var charset = null;
     var isForcedCharset = false;
     if (aDocument) {
       if (Components.utils.isCrossProcessWrapper(aDocument)) {
--- a/toolkit/components/viewsource/jar.mn
+++ b/toolkit/components/viewsource/jar.mn
@@ -4,9 +4,9 @@
 
 toolkit.jar:
   content/global/viewSource.css             (content/viewSource.css)
   content/global/viewSource.js              (content/viewSource.js)
 * content/global/viewSource.xul             (content/viewSource.xul)
   content/global/viewPartialSource.js       (content/viewPartialSource.js)
 * content/global/viewPartialSource.xul      (content/viewPartialSource.xul)
   content/global/viewSourceUtils.js         (content/viewSourceUtils.js)
-  content/global/viewSource-content.js      (content/viewSource-content.js)
\ No newline at end of file
+  content/global/viewSource-content.js      (content/viewSource-content.js)
--- a/toolkit/components/viewsource/moz.build
+++ b/toolkit/components/viewsource/moz.build
@@ -4,10 +4,14 @@
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
+EXTRA_JS_MODULES += [
+    'ViewSourceBrowser.jsm',
+]
+
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'View Source')
--- a/toolkit/components/viewsource/test/browser/browser_gotoline.js
+++ b/toolkit/components/viewsource/test/browser/browser_gotoline.js
@@ -19,17 +19,17 @@ add_task(function*() {
 
 let checkViewSource = Task.async(function* (aWindow) {
   is(aWindow.gBrowser.contentDocument.body.textContent, content, "Correct content loaded");
   let selection = aWindow.gBrowser.contentWindow.getSelection();
   let statusPanel = aWindow.document.getElementById("statusbar-line-col");
   is(statusPanel.getAttribute("label"), "", "Correct status bar text");
 
   for (let i = 1; i <= 3; i++) {
-    aWindow.ViewSourceChrome.goToLine(i);
+    aWindow.viewSourceChrome.goToLine(i);
     let result = yield ContentTask.spawn(aWindow.gBrowser, i, function*(i) {
       let selection = content.getSelection();
       return (selection.toString() == "line " + i);
     });
 
     ok(result, "Correct text selected");
 
     yield ContentTaskUtils.waitForCondition(() => {