From 0355257f8360e06b80ebe5ac25b9378249757e93 Mon Sep 17 00:00:00 2001 From: Neil Deakin Date: Fri, 24 Jan 2020 19:53:55 +0000 Subject: [PATCH] Bug 1505915, move view source components to use JSWindowActor. This allows view frame source to work in out of process child frames, r=mconley Differential Revision: https://phabricator.services.mozilla.com/D60253 --HG-- rename : toolkit/components/viewsource/content/viewSource-content.js => toolkit/actors/ViewSourceChild.jsm rename : toolkit/components/viewsource/content/viewSource-content.js => toolkit/actors/ViewSourcePageChild.jsm rename : toolkit/components/viewsource/ViewSourceBrowser.jsm => toolkit/actors/ViewSourcePageParent.jsm extra : moz-landing-system : lando --- browser/base/content/nsContextMenu.js | 5 +- .../components/sessionstore/SessionStore.jsm | 7 - ...ionSourceChild.jsm => ViewSourceChild.jsm} | 187 ++++++- .../ViewSourcePageChild.jsm} | 457 ++++-------------- toolkit/actors/ViewSourcePageParent.jsm | 159 ++++++ toolkit/actors/moz.build | 4 +- .../viewsource/ViewSourceBrowser.jsm | 335 ------------- .../viewsource/content/viewSourceUtils.js | 78 +-- toolkit/components/viewsource/jar.mn | 1 - toolkit/components/viewsource/moz.build | 4 - .../viewsource/test/browser/browser.ini | 1 + .../test/browser/browser_gotoline.js | 10 +- .../test/browser/browser_partialsource.js | 46 ++ .../viewsource/test/browser/head.js | 21 +- toolkit/modules/ActorManagerParent.jsm | 36 +- 15 files changed, 579 insertions(+), 772 deletions(-) rename toolkit/actors/{SelectionSourceChild.jsm => ViewSourceChild.jsm} (58%) rename toolkit/{components/viewsource/content/viewSource-content.js => actors/ViewSourcePageChild.jsm} (57%) create mode 100644 toolkit/actors/ViewSourcePageParent.jsm delete mode 100644 toolkit/components/viewsource/ViewSourceBrowser.jsm create mode 100644 toolkit/components/viewsource/test/browser/browser_partialsource.js diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index 8df171ef2ea9..523a5f35435e 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -1174,7 +1174,10 @@ class nsContextMenu { return viewSourceBrowser; }; - top.gViewSourceUtils.viewPartialSourceInBrowser(browser, openSelectionFn); + top.gViewSourceUtils.viewPartialSourceInBrowser( + this.actor.browsingContext, + openSelectionFn + ); } // Open new "view source" window with the frame's URL. diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 9e6a82899e08..fd0af29adbf2 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -208,7 +208,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { TabStateCache: "resource:///modules/sessionstore/TabStateCache.jsm", TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.jsm", Utils: "resource://gre/modules/sessionstore/Utils.jsm", - ViewSourceBrowser: "resource://gre/modules/ViewSourceBrowser.jsm", setTimeout: "resource://gre/modules/Timer.jsm", }); @@ -4710,12 +4709,6 @@ var SessionStoreInternal = { }); } - // If the restored browser wants to show view source content, start up a - // view source browser that will load the required frame script. - if (uri && ViewSourceBrowser.isViewSource(uri)) { - new ViewSourceBrowser(browser); - } - browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent", { loadArguments, isRemotenessUpdate, diff --git a/toolkit/actors/SelectionSourceChild.jsm b/toolkit/actors/ViewSourceChild.jsm similarity index 58% rename from toolkit/actors/SelectionSourceChild.jsm rename to toolkit/actors/ViewSourceChild.jsm index 74cac7676979..c36d79022b1d 100644 --- a/toolkit/actors/SelectionSourceChild.jsm +++ b/toolkit/actors/ViewSourceChild.jsm @@ -3,28 +3,183 @@ * 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/. */ -var EXPORTED_SYMBOLS = ["SelectionSourceChild"]; +var EXPORTED_SYMBOLS = ["ViewSourceChild"]; -const { ActorChild } = ChromeUtils.import( - "resource://gre/modules/ActorChild.jsm" -); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); -class SelectionSourceChild extends ActorChild { - receiveMessage(message) { - const global = message.target; +ChromeUtils.defineModuleGetter( + this, + "ViewSourcePageChild", + "resource://gre/actors/ViewSourcePageChild.jsm" +); - if (message.name == "ViewSource:GetSelection") { - let selectionDetails; - try { - selectionDetails = this.getSelection(global); - } finally { - global.sendAsyncMessage( - "ViewSource:GetSelectionDone", - selectionDetails +class ViewSourceChild extends JSWindowActorChild { + receiveMessage(message) { + let data = message.data; + switch (message.name) { + case "ViewSource:LoadSource": + this.viewSource( + data.URL, + data.outerWindowID, + data.lineNumber, + data.shouldWrap ); + break; + case "ViewSource:LoadSourceWithSelection": + this.viewSourceWithSelection( + data.URL, + data.drawSelection, + data.baseURI + ); + break; + case "ViewSource:GetSelection": + let selectionDetails; + try { + selectionDetails = this.getSelection(this.document.ownerGlobal); + } catch (e) {} + return selectionDetails; + } + + return undefined; + } + + /** + * Called when the parent sends a message to view some source code. + * + * @param URL (required) + * The URL string of the source to be shown. + * @param outerWindowID (optional) + * The outerWindowID of the content window that has hosted + * the document, in case we want to retrieve it from the network + * cache. + * @param lineNumber (optional) + * The line number to focus as soon as the source has finished + * loading. + */ + viewSource(URL, outerWindowID, lineNumber) { + let pageDescriptor, forcedCharSet; + + if (outerWindowID) { + let contentWindow = Services.wm.getOuterWindowWithId(outerWindowID); + if (contentWindow) { + let otherDocShell = contentWindow.docShell; + + try { + pageDescriptor = otherDocShell.QueryInterface(Ci.nsIWebPageDescriptor) + .currentDescriptor; + } catch (e) { + // We couldn't get the page descriptor, so we'll probably end up re-retrieving + // this document off of the network. + } + + let utils = contentWindow.windowUtils; + let doc = contentWindow.document; + forcedCharSet = utils.docCharsetIsForced ? doc.characterSet : null; } } + + this.loadSource(URL, pageDescriptor, lineNumber, forcedCharSet); + } + + /** + * Loads a view source selection showing the given view-source url and + * highlight the selection. + * + * @param uri view-source uri to show + * @param drawSelection true to highlight the selection + * @param baseURI base URI of the original document + */ + viewSourceWithSelection(uri, drawSelection, baseURI) { + // This isn't ideal, but set a global in the view source page actor + // that indicates that a selection should be drawn. It will be read + // when by the page's pageshow listener. This should work as the + // view source page is always loaded in the same process. + ViewSourcePageChild.setNeedsDrawSelection(drawSelection); + + // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl) + let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; + let webNav = this.docShell.QueryInterface(Ci.nsIWebNavigation); + let loadURIOptions = { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + loadFlags, + baseURI: Services.io.newURI(baseURI), + }; + webNav.loadURI(uri, loadURIOptions); + } + + /** + * Common utility function used by both the current and deprecated APIs + * for loading source. + * + * @param URL (required) + * The URL string of the source to be shown. + * @param pageDescriptor (optional) + * The currentDescriptor off of an nsIWebPageDescriptor, in the + * event that the caller wants to try to load the source out of + * the network cache. + * @param lineNumber (optional) + * The line number to focus as soon as the source has finished + * loading. + * @param forcedCharSet (optional) + * The document character set to use instead of the default one. + */ + loadSource(URL, pageDescriptor, lineNumber, forcedCharSet) { + const viewSrcURL = "view-source:" + URL; + + if (forcedCharSet) { + try { + this.docShell.charset = forcedCharSet; + } catch (e) { + /* invalid charset */ + } + } + + ViewSourcePageChild.setInitialLineNumber(lineNumber); + + if (!pageDescriptor) { + this.loadSourceFromURL(viewSrcURL); + return; + } + + try { + let pageLoader = this.docShell.QueryInterface(Ci.nsIWebPageDescriptor); + pageLoader.loadPage( + pageDescriptor, + Ci.nsIWebPageDescriptor.DISPLAY_AS_SOURCE + ); + } catch (e) { + // We were not able to load the source from the network cache. + this.loadSourceFromURL(viewSrcURL); + return; + } + + let shEntrySource = pageDescriptor.QueryInterface(Ci.nsISHEntry); + let shistory = this.docShell.QueryInterface(Ci.nsIWebNavigation) + .sessionHistory.legacySHistory; + let shEntry = shistory.createEntry(); + shEntry.URI = Services.io.newURI(viewSrcURL); + shEntry.title = viewSrcURL; + let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + shEntry.triggeringPrincipal = systemPrincipal; + shEntry.setLoadTypeAsHistory(); + shEntry.cacheKey = shEntrySource.cacheKey; + shistory.addEntry(shEntry, true); + } + + /** + * Load some URL in the browser. + * + * @param URL + * The URL string to load. + */ + loadSourceFromURL(URL) { + let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; + let webNav = this.docShell.QueryInterface(Ci.nsIWebNavigation); + let loadURIOptions = { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + loadFlags, + }; + webNav.loadURI(URL, loadURIOptions); } /** @@ -213,7 +368,7 @@ class SelectionSourceChild extends ActorChild { tmpNode.appendChild(ancestorContainer); return { - uri: + URL: (isHTML ? "view-source:data:text/html;charset=utf-8," : "view-source:data:application/xml;charset=utf-8,") + diff --git a/toolkit/components/viewsource/content/viewSource-content.js b/toolkit/actors/ViewSourcePageChild.jsm similarity index 57% rename from toolkit/components/viewsource/content/viewSource-content.js rename to toolkit/actors/ViewSourcePageChild.jsm index 7d4689868220..687b676f4e3a 100644 --- a/toolkit/components/viewsource/content/viewSource-content.js +++ b/toolkit/actors/ViewSourcePageChild.jsm @@ -9,11 +9,7 @@ const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); -ChromeUtils.defineModuleGetter( - this, - "DeferredTask", - "resource://gre/modules/DeferredTask.jsm" -); +var EXPORTED_SYMBOLS = ["ViewSourcePageChild"]; XPCOMUtils.defineLazyGlobalGetters(this, ["NodeFilter"]); @@ -28,280 +24,105 @@ const BUNDLE_URL = "chrome://global/locale/viewSource.properties"; const MARK_SELECTION_START = "\uFDD0"; const MARK_SELECTION_END = "\uFDEF"; -var global = this; +/** + * When showing selection source, chrome will construct a page fragment to + * show, and then instruct content to draw a selection after load. This is + * set true when there is a pending request to draw selection. + */ +let gNeedsDrawSelection = false; /** - * ViewSourceContent should be loaded in the of the - * view source window, and initialized as soon as it has loaded. + * Start at a specific line number. */ -var ViewSourceContent = { - /** - * These are the messages that ViewSourceContent is prepared to listen - * for. If you need ViewSourceContent to handle more messages, add them - * here. - */ - messages: [ - "ViewSource:LoadSource", - "ViewSource:LoadSourceWithSelection", - "ViewSource:GoToLine", - ], +let gInitialLineNumber = -1; - /** - * When showing selection source, chrome will construct a page fragment to - * show, and then instruct content to draw a selection after load. This is - * set true when there is a pending request to draw selection. - */ - needsDrawSelection: false, - - get isViewSource() { - let uri = content.document.documentURI; - return uri.startsWith("view-source:"); +/** + * In-page context menu items that are injected after page load. + */ +let gContextMenuItems = [ + { + id: "goToLine", + accesskey: true, + handler(actor) { + actor.sendAsyncMessage("ViewSource:PromptAndGoToLine"); + }, }, - - get isAboutBlank() { - let uri = content.document.documentURI; - return uri == "about:blank"; + { + id: "wrapLongLines", + get checked() { + return Services.prefs.getBoolPref("view_source.wrap_long_lines"); + }, + handler(actor) { + actor.toggleWrapping(); + }, }, + { + id: "highlightSyntax", + get checked() { + return Services.prefs.getBoolPref("view_source.syntax_highlight"); + }, + handler(actor) { + actor.toggleSyntaxHighlighting(); + }, + }, +]; - /** - * This should be called as soon as this frame script has loaded. - */ - init() { - this.messages.forEach(msgName => { - addMessageListener(msgName, this); +class ViewSourcePageChild extends JSWindowActorChild { + constructor() { + super(); + + XPCOMUtils.defineLazyGetter(this, "bundle", function() { + return Services.strings.createBundle(BUNDLE_URL); }); + } - addEventListener("pagehide", this, true); - addEventListener("pageshow", this, true); - addEventListener("click", this); - addEventListener("unload", this); - Services.els.addSystemEventListener(global, "contextmenu", this, false); - }, + static setNeedsDrawSelection(value) { + gNeedsDrawSelection = value; + } - /** - * This should be called when the frame script is being unloaded, - * and the browser is tearing down. - */ - uninit() { - this.messages.forEach(msgName => { - removeMessageListener(msgName, this); - }); + static setInitialLineNumber(value) { + gInitialLineNumber = value; + } - removeEventListener("pagehide", this, true); - removeEventListener("pageshow", this, true); - removeEventListener("click", this); - removeEventListener("unload", this); - - Services.els.removeSystemEventListener(global, "contextmenu", this, false); - }, - - /** - * 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 && !this.isAboutBlank) { - return; + if (msg.name == "ViewSource:GoToLine") { + this.goToLine(msg.data.lineNumber); } - let data = msg.data; - switch (msg.name) { - case "ViewSource:LoadSource": - this.viewSource( - data.URL, - data.outerWindowID, - data.lineNumber, - data.shouldWrap - ); - break; - case "ViewSource:LoadSourceWithSelection": - this.viewSourceWithSelection( - data.URL, - data.drawSelection, - data.baseURI - ); - break; - case "ViewSource:GoToLine": - this.goToLine(data.lineNumber); - break; - } - }, + } /** * 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": this.onClick(event); break; - case "unload": - this.uninit(); - break; - case "contextmenu": - this.onContextMenu(event); - break; } - }, - - /** - * A getter for the view source string bundle. - */ - get bundle() { - delete this.bundle; - this.bundle = Services.strings.createBundle(BUNDLE_URL); - return this.bundle; - }, + } /** * A shortcut to the nsISelectionController for the content. */ get selectionController() { - return docShell + return this.docShell .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsISelectionDisplay) .QueryInterface(Ci.nsISelectionController); - }, + } /** * A shortcut to the nsIWebBrowserFind for the content. */ get webBrowserFind() { - return docShell + return this.docShell .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebBrowserFind); - }, - - /** - * Called when the parent sends a message to view some source code. - * - * @param URL (required) - * The URL string of the source to be shown. - * @param outerWindowID (optional) - * The outerWindowID of the content window that has hosted - * the document, in case we want to retrieve it from the network - * cache. - * @param lineNumber (optional) - * The line number to focus as soon as the source has finished - * loading. - */ - viewSource(URL, outerWindowID, lineNumber) { - let pageDescriptor, forcedCharSet; - - if (outerWindowID) { - let contentWindow = Services.wm.getOuterWindowWithId(outerWindowID); - let otherDocShell = contentWindow.docShell; - - try { - pageDescriptor = otherDocShell.QueryInterface(Ci.nsIWebPageDescriptor) - .currentDescriptor; - } catch (e) { - // We couldn't get the page descriptor, so we'll probably end up re-retrieving - // this document off of the network. - } - - let utils = contentWindow.windowUtils; - let doc = contentWindow.document; - forcedCharSet = utils.docCharsetIsForced ? doc.characterSet : null; - } - - this.loadSource(URL, pageDescriptor, lineNumber, forcedCharSet); - }, - - /** - * Common utility function used by both the current and deprecated APIs - * for loading source. - * - * @param URL (required) - * The URL string of the source to be shown. - * @param pageDescriptor (optional) - * The currentDescriptor off of an nsIWebPageDescriptor, in the - * event that the caller wants to try to load the source out of - * the network cache. - * @param lineNumber (optional) - * The line number to focus as soon as the source has finished - * loading. - * @param forcedCharSet (optional) - * The document character set to use instead of the default one. - */ - loadSource(URL, pageDescriptor, lineNumber, forcedCharSet) { - const viewSrcURL = "view-source:" + URL; - - if (forcedCharSet) { - try { - docShell.charset = forcedCharSet; - } catch (e) { - /* invalid charset */ - } - } - - if (lineNumber && lineNumber > 0) { - let doneLoading = event => { - // Ignore possible initial load of about:blank - if (this.isAboutBlank || !content.document.body) { - return; - } - this.goToLine(lineNumber); - removeEventListener("pageshow", doneLoading); - }; - - addEventListener("pageshow", doneLoading); - } - - if (!pageDescriptor) { - this.loadSourceFromURL(viewSrcURL); - return; - } - - try { - let pageLoader = docShell.QueryInterface(Ci.nsIWebPageDescriptor); - pageLoader.loadPage( - pageDescriptor, - Ci.nsIWebPageDescriptor.DISPLAY_AS_SOURCE - ); - } catch (e) { - // We were not able to load the source from the network cache. - this.loadSourceFromURL(viewSrcURL); - return; - } - - let shEntrySource = pageDescriptor.QueryInterface(Ci.nsISHEntry); - let shistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory - .legacySHistory; - let shEntry = shistory.createEntry(); - shEntry.URI = Services.io.newURI(viewSrcURL); - shEntry.title = viewSrcURL; - let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); - shEntry.triggeringPrincipal = systemPrincipal; - shEntry.setLoadTypeAsHistory(); - shEntry.cacheKey = shEntrySource.cacheKey; - shistory.addEntry(shEntry, true); - }, - - /** - * Load some URL in the browser. - * - * @param URL - * The URL string to load. - */ - loadSourceFromURL(URL) { - let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; - let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); - let loadURIOptions = { - triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), - loadFlags, - }; - webNav.loadURI(URL, loadURIOptions); - }, + } /** * This handler is for click events from: @@ -313,11 +134,11 @@ var ViewSourceContent = { let target = event.originalTarget; // Check for content menu actions if (target.id) { - this.contextMenuItems.forEach(itemSpec => { + gContextMenuItems.forEach(itemSpec => { if (itemSpec.id !== target.id) { return; } - itemSpec.handler.call(this, event); + itemSpec.handler(this); event.stopPropagation(); }); } @@ -334,10 +155,10 @@ var ViewSourceContent = { if (target == errorDoc.getElementById("goBackButton")) { // Instead of loading some safe page, just close the window - sendAsyncMessage("ViewSource:Close"); + this.sendAsyncMessage("ViewSource:Close"); } } - }, + } /** * Handler for the pageshow event. @@ -346,57 +167,27 @@ var ViewSourceContent = { * The pageshow event being handled. */ onPageShow(event) { - content.focus(); + this.contentWindow.focus(); // If we need to draw the selection, wait until an actual view source page // has loaded, instead of about:blank. if ( - this.needsDrawSelection && - content.document.documentURI.startsWith("view-source:") + gNeedsDrawSelection && + this.document.documentURI.startsWith("view-source:") ) { - this.needsDrawSelection = false; + gNeedsDrawSelection = false; this.drawSelection(); } - if (content.document.body) { + if (gInitialLineNumber >= 0) { + this.goToLine(gInitialLineNumber); + gInitialLineNumber = -1; + } + + if (this.document.body) { this.injectContextMenu(); } - - sendAsyncMessage("ViewSource:SourceLoaded"); - }, - - /** - * Handler for the pagehide event. - * - * @param event - * The pagehide event being handled. - */ - onPageHide(event) { - sendAsyncMessage("ViewSource:SourceUnloaded"); - }, - - onContextMenu(event) { - let node = event.target; - - let result = { - isEmail: false, - isLink: false, - href: "", - // We have to pass these in the event that we're running in - // a remote browser, so that ViewSourceChrome knows where to - // open the context menu. - screenX: event.screenX, - screenY: event.screenY, - }; - - if (node && node.localName == "a") { - result.isLink = node.href.startsWith("view-source:"); - result.isEmail = node.href.startsWith("mailto:"); - result.href = node.href.substring(node.href.indexOf(":") + 1); - } - - sendSyncMessage("ViewSource:ContextMenuOpening", result); - }, + } /** * Attempts to go to a particular line in the source code being @@ -409,7 +200,7 @@ var ViewSourceContent = { * The line number to attempt to go to. */ goToLine(lineNumber) { - let body = content.document.body; + let body = this.document.body; // The source document is made up of a number of pre elements with // id attributes in the format
, meaning that
@@ -439,11 +230,11 @@ var ViewSourceContent = {
     let found = this.findLocation(pre, lineNumber, null, -1, false, result);
 
     if (!found) {
-      sendAsyncMessage("ViewSource:GoToLine:Failed");
+      this.sendAsyncMessage("ViewSource:GoToLine:Failed");
       return;
     }
 
-    let selection = content.getSelection();
+    let selection = this.document.defaultView.getSelection();
     selection.removeAllRanges();
 
     // In our case, the range's startOffset is after "\n" on the previous line.
@@ -484,8 +275,8 @@ var ViewSourceContent = {
       true
     );
 
-    sendAsyncMessage("ViewSource:GoToLine:Success", { lineNumber });
-  },
+    this.sendAsyncMessage("ViewSource:GoToLine:Success", { lineNumber });
+  }
 
   /**
    * Some old code from the original view source implementation. Original
@@ -514,7 +305,7 @@ var ViewSourceContent = {
     let curLine = pre.id ? parseInt(pre.id.substring(4)) : 1;
 
     // Walk through each of the text nodes and count newlines.
-    let treewalker = content.document.createTreeWalker(
+    let treewalker = this.document.createTreeWalker(
       pre,
       NodeFilter.SHOW_TEXT,
       null
@@ -573,7 +364,7 @@ var ViewSourceContent = {
             break;
           }
         } else if (curLine == lineNumber && !("range" in result)) {
-          result.range = content.document.createRange();
+          result.range = this.document.createRange();
           result.range.setStart(textNode, curPos);
 
           // This will always be overridden later, except when we look for
@@ -589,17 +380,17 @@ var ViewSourceContent = {
     }
 
     return found || "range" in result;
-  },
+  }
 
   /**
    * Toggles the "wrap" class on the document body, which sets whether
    * or not long lines are wrapped.  Notifies parent to update the pref.
    */
   toggleWrapping() {
-    let body = content.document.body;
+    let body = this.document.body;
     let state = body.classList.toggle("wrap");
-    sendAsyncMessage("ViewSource:StoreWrapping", { state });
-  },
+    this.sendAsyncMessage("ViewSource:StoreWrapping", { state });
+  }
 
   /**
    * Toggles the "highlight" class on the document body, which sets whether
@@ -607,32 +398,10 @@ var ViewSourceContent = {
    * pref.
    */
   toggleSyntaxHighlighting() {
-    let body = content.document.body;
+    let body = this.document.body;
     let state = body.classList.toggle("highlight");
-    sendAsyncMessage("ViewSource:StoreSyntaxHighlighting", { state });
-  },
-
-  /**
-   * Loads a view source selection showing the given view-source url and
-   * highlight the selection.
-   *
-   * @param uri view-source uri to show
-   * @param drawSelection true to highlight the selection
-   * @param baseURI base URI of the original document
-   */
-  viewSourceWithSelection(uri, drawSelection, baseURI) {
-    this.needsDrawSelection = drawSelection;
-
-    // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
-    let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
-    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
-    let loadURIOptions = {
-      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
-      loadFlags,
-      baseURI: Services.io.newURI(baseURI),
-    };
-    webNav.loadURI(uri, loadURIOptions);
-  },
+    this.sendAsyncMessage("ViewSource:StoreSyntaxHighlighting", { state });
+  }
 
   /**
    * Using special markers left in the serialized source, this helper makes the
@@ -640,7 +409,7 @@ var ViewSourceContent = {
    * selected on the inflated view-source DOM.
    */
   drawSelection() {
-    content.document.title = this.bundle.GetStringFromName(
+    this.document.title = this.bundle.GetStringFromName(
       "viewSelectionSourceTitle"
     );
 
@@ -677,7 +446,7 @@ var ViewSourceContent = {
     var startLength = MARK_SELECTION_START.length;
     findInst.findNext();
 
-    var selection = content.getSelection();
+    var selection = this.document.defaultView.getSelection();
     if (!selection.rangeCount) {
       return;
     }
@@ -732,44 +501,13 @@ var ViewSourceContent = {
     findInst.wrapFind = wrapFind;
     findInst.findBackwards = findBackwards;
     findInst.searchString = searchString;
-  },
-
-  /**
-   * In-page context menu items that are injected after page load.
-   */
-  contextMenuItems: [
-    {
-      id: "goToLine",
-      accesskey: true,
-      handler() {
-        sendAsyncMessage("ViewSource:PromptAndGoToLine");
-      },
-    },
-    {
-      id: "wrapLongLines",
-      get checked() {
-        return Services.prefs.getBoolPref("view_source.wrap_long_lines");
-      },
-      handler() {
-        this.toggleWrapping();
-      },
-    },
-    {
-      id: "highlightSyntax",
-      get checked() {
-        return Services.prefs.getBoolPref("view_source.syntax_highlight");
-      },
-      handler() {
-        this.toggleSyntaxHighlighting();
-      },
-    },
-  ],
+  }
 
   /**
    * Add context menu items for view source specific actions.
    */
   injectContextMenu() {
-    let doc = content.document;
+    let doc = this.document;
 
     let menu = doc.createElementNS(NS_XHTML, "menu");
     menu.setAttribute("type", "context");
@@ -777,7 +515,7 @@ var ViewSourceContent = {
     doc.body.appendChild(menu);
     doc.body.setAttribute("contextmenu", "actions");
 
-    this.contextMenuItems.forEach(itemSpec => {
+    gContextMenuItems.forEach(itemSpec => {
       let item = doc.createElementNS(NS_XHTML, "menuitem");
       item.setAttribute("id", itemSpec.id);
       let labelName = `context_${itemSpec.id}_label`;
@@ -797,14 +535,14 @@ var ViewSourceContent = {
     });
 
     this.updateContextMenu();
-  },
+  }
 
   /**
    * Update state of checkbox-style context menu items.
    */
   updateContextMenu() {
-    let doc = content.document;
-    this.contextMenuItems.forEach(itemSpec => {
+    let doc = this.document;
+    gContextMenuItems.forEach(itemSpec => {
       if (!("checked" in itemSpec)) {
         return;
       }
@@ -815,6 +553,5 @@ var ViewSourceContent = {
         item.removeAttribute("checked");
       }
     });
-  },
-};
-ViewSourceContent.init();
+  }
+}
diff --git a/toolkit/actors/ViewSourcePageParent.jsm b/toolkit/actors/ViewSourcePageParent.jsm
new file mode 100644
index 000000000000..4070431a922e
--- /dev/null
+++ b/toolkit/actors/ViewSourcePageParent.jsm
@@ -0,0 +1,159 @@
+// -*- 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/. */
+
+ChromeUtils.defineModuleGetter(
+  this,
+  "Services",
+  "resource://gre/modules/Services.jsm"
+);
+
+const BUNDLE_URL = "chrome://global/locale/viewSource.properties";
+
+var EXPORTED_SYMBOLS = ["ViewSourcePageParent"];
+
+/**
+ * ViewSourcePageParent manages the view source  from the chrome side.
+ */
+class ViewSourcePageParent extends JSWindowActorParent {
+  constructor() {
+    super();
+
+    /**
+     * 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.
+     */
+    this.lastLineFound = null;
+  }
+
+  /**
+   * 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;
+
+    switch (message.name) {
+      case "ViewSource:PromptAndGoToLine":
+        this.promptAndGoToLine();
+        break;
+      case "ViewSource:GoToLine:Success":
+        this.onGoToLineSuccess(data.lineNumber);
+        break;
+      case "ViewSource:GoToLine:Failed":
+        this.onGoToLineFailed();
+        break;
+      case "ViewSource:StoreWrapping":
+        this.storeWrapping(data.state);
+        break;
+      case "ViewSource:StoreSyntaxHighlighting":
+        this.storeSyntaxHighlighting(data.state);
+        break;
+    }
+  }
+
+  /**
+   * A getter for the view source string bundle.
+   */
+  get bundle() {
+    if (this._bundle) {
+      return this._bundle;
+    }
+    return (this._bundle = Services.strings.createBundle(BUNDLE_URL));
+  }
+
+  /**
+   * Opens the "Go to line" prompt for a user to hop to a particular line
+   * of the source code they're viewing. This will keep prompting until the
+   * user either cancels out of the prompt, or enters a valid line number.
+   */
+  promptAndGoToLine() {
+    let input = { value: this.lastLineFound };
+    let window = Services.wm.getMostRecentWindow(null);
+
+    let ok = Services.prompt.prompt(
+      window,
+      this.bundle.GetStringFromName("goToLineTitle"),
+      this.bundle.GetStringFromName("goToLineText"),
+      input,
+      null,
+      { value: 0 }
+    );
+
+    if (!ok) {
+      return;
+    }
+
+    let line = parseInt(input.value, 10);
+
+    if (!(line > 0)) {
+      Services.prompt.alert(
+        window,
+        this.bundle.GetStringFromName("invalidInputTitle"),
+        this.bundle.GetStringFromName("invalidInputText")
+      );
+      this.promptAndGoToLine();
+    } else {
+      this.goToLine(line);
+    }
+  }
+
+  /**
+   * 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.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.
+   */
+  onGoToLineSuccess(lineNumber) {
+    // We'll pre-populate the "Go to line" prompt with this value the next
+    // time it comes up.
+    this.lastLineFound = lineNumber;
+  }
+
+  /**
+   * Called when the child reports that we failed to go to a particular
+   * line. This informs the user that their selection was likely out of range,
+   * and then reprompts the user to try again.
+   */
+  onGoToLineFailed() {
+    let window = Services.wm.getMostRecentWindow(null);
+    Services.prompt.alert(
+      window,
+      this.bundle.GetStringFromName("outOfRangeTitle"),
+      this.bundle.GetStringFromName("outOfRangeText")
+    );
+    this.promptAndGoToLine();
+  }
+
+  /**
+   * Update the wrapping pref based on the child's current state.
+   * @param state
+   *        Whether wrapping is currently enabled in the child.
+   */
+  storeWrapping(state) {
+    Services.prefs.setBoolPref("view_source.wrap_long_lines", state);
+  }
+
+  /**
+   * Update the syntax highlighting pref based on the child's current state.
+   * @param state
+   *        Whether syntax highlighting is currently enabled in the child.
+   */
+  storeSyntaxHighlighting(state) {
+    Services.prefs.setBoolPref("view_source.syntax_highlight", state);
+  }
+}
diff --git a/toolkit/actors/moz.build b/toolkit/actors/moz.build
index d2da96b9d535..a70e4e1814a7 100644
--- a/toolkit/actors/moz.build
+++ b/toolkit/actors/moz.build
@@ -48,11 +48,13 @@ FINAL_TARGET_FILES.actors += [
     'PrintingChild.jsm',
     'PurgeSessionHistoryChild.jsm',
     'SelectChild.jsm',
-    'SelectionSourceChild.jsm',
     'SelectParent.jsm',
     'ThumbnailsChild.jsm',
     'UAWidgetsChild.jsm',
     'UnselectedTabHoverChild.jsm',
+    'ViewSourceChild.jsm',
+    'ViewSourcePageChild.jsm',
+    'ViewSourcePageParent.jsm',
     'WebChannelChild.jsm',
     'WebChannelParent.jsm',
     'WebNavigationChild.jsm',
diff --git a/toolkit/components/viewsource/ViewSourceBrowser.jsm b/toolkit/components/viewsource/ViewSourceBrowser.jsm
deleted file mode 100644
index 17f83c0bc259..000000000000
--- a/toolkit/components/viewsource/ViewSourceBrowser.jsm
+++ /dev/null
@@ -1,335 +0,0 @@
-// -*- 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/. */
-
-ChromeUtils.defineModuleGetter(
-  this,
-  "Services",
-  "resource://gre/modules/Services.jsm"
-);
-
-const BUNDLE_URL = "chrome://global/locale/viewSource.properties";
-
-const FRAME_SCRIPT = "chrome://global/content/viewSource-content.js";
-
-var EXPORTED_SYMBOLS = ["ViewSourceBrowser"];
-
-// Keep a set of browsers we've seen before, so we can load our frame script as
-// needed into any new ones.
-var gKnownBrowsers = new WeakSet();
-
-/**
- * ViewSourceBrowser manages the view source  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 tab (or some other non-window case), an instance of this is
- * created by viewSourceUtils.js to wrap the .  The frame script will
- * be loaded by this module at construction time.
- */
-function ViewSourceBrowser(aBrowser) {
-  this._browser = aBrowser;
-  this.init();
-}
-
-ViewSourceBrowser.prototype = {
-  /**
-   * The  that will be displaying the view source content.
-   */
-  get browser() {
-    return this._browser;
-  },
-
-  /**
-   * 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,
-
-  /**
-   * 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.
-   */
-  messages: [
-    "ViewSource:PromptAndGoToLine",
-    "ViewSource:GoToLine:Success",
-    "ViewSource:GoToLine:Failed",
-    "ViewSource:StoreWrapping",
-    "ViewSource:StoreSyntaxHighlighting",
-  ],
-
-  /**
-   * 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.loadFrameScript();
-  },
-
-  /**
-   * 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);
-    });
-  },
-
-  /**
-   * For a new browser we've not seen before, load the frame script.
-   */
-  loadFrameScript() {
-    // Check for a browser first. There won't be one for the window case
-    // (still used by other applications like Thunderbird), as the element
-    // does not exist until the XUL document loads.
-    if (!this.browser) {
-      return;
-    }
-    if (!gKnownBrowsers.has(this.browser)) {
-      gKnownBrowsers.add(this.browser);
-      this.mm.loadFrameScript(FRAME_SCRIPT, false);
-    }
-  },
-
-  /**
-   * 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;
-
-    switch (message.name) {
-      case "ViewSource:PromptAndGoToLine":
-        this.promptAndGoToLine();
-        break;
-      case "ViewSource:GoToLine:Success":
-        this.onGoToLineSuccess(data.lineNumber);
-        break;
-      case "ViewSource:GoToLine:Failed":
-        this.onGoToLineFailed();
-        break;
-      case "ViewSource:StoreWrapping":
-        this.storeWrapping(data.state);
-        break;
-      case "ViewSource:StoreSyntaxHighlighting":
-        this.storeSyntaxHighlighting(data.state);
-        break;
-    }
-  },
-
-  /**
-   * 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);
-  },
-
-  /**
-   * A getter for the view source string bundle.
-   */
-  get bundle() {
-    if (this._bundle) {
-      return this._bundle;
-    }
-    return (this._bundle = Services.strings.createBundle(BUNDLE_URL));
-  },
-
-  /**
-   * Loads the source for a URL while applying some optional features if
-   * enabled.
-   *
-   * 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) {
-      this.browser.sameProcessAsFrameLoader = browser.frameLoader;
-
-      // If we're dealing with a remote browser, then the browser
-      // for view source needs to be remote as well.
-      this.updateBrowserRemoteness(browser.remoteType);
-    } else if (outerWindowID) {
-      throw new Error("Must supply the browser if passing the outerWindowID");
-    }
-
-    this.sendAsyncMessage("ViewSource:LoadSource", {
-      URL,
-      outerWindowID,
-      lineNumber,
-    });
-  },
-
-  /**
-   * Loads a view source selection showing the given view-source url and
-   * highlight the selection.
-   *
-   * @param uri view-source uri to show
-   * @param drawSelection true to highlight the selection
-   * @param baseURI base URI of the original document
-   */
-  loadViewSourceFromSelection(URL, drawSelection, baseURI) {
-    this.sendAsyncMessage("ViewSource:LoadSourceWithSelection", {
-      URL,
-      drawSelection,
-      baseURI,
-    });
-  },
-
-  /**
-   * 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.
-   * @param remoteType
-   *        The type of remote browser process.
-   */
-  updateBrowserRemoteness(remoteType) {
-    if (this.browser.remoteType != remoteType) {
-      // In this base case, where we are handed a  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");
-    }
-  },
-
-  /**
-   * Opens the "Go to line" prompt for a user to hop to a particular line
-   * of the source code they're viewing. This will keep prompting until the
-   * user either cancels out of the prompt, or enters a valid line number.
-   */
-  promptAndGoToLine() {
-    let input = { value: this.lastLineFound };
-    let window = Services.wm.getMostRecentWindow(null);
-
-    let ok = Services.prompt.prompt(
-      window,
-      this.bundle.GetStringFromName("goToLineTitle"),
-      this.bundle.GetStringFromName("goToLineText"),
-      input,
-      null,
-      { value: 0 }
-    );
-
-    if (!ok) {
-      return;
-    }
-
-    let line = parseInt(input.value, 10);
-
-    if (!(line > 0)) {
-      Services.prompt.alert(
-        window,
-        this.bundle.GetStringFromName("invalidInputTitle"),
-        this.bundle.GetStringFromName("invalidInputText")
-      );
-      this.promptAndGoToLine();
-    } else {
-      this.goToLine(line);
-    }
-  },
-
-  /**
-   * 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.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.
-   */
-  onGoToLineSuccess(lineNumber) {
-    // We'll pre-populate the "Go to line" prompt with this value the next
-    // time it comes up.
-    this.lastLineFound = lineNumber;
-  },
-
-  /**
-   * Called when the frame script reports that we failed to go to a particular
-   * line. This informs the user that their selection was likely out of range,
-   * and then reprompts the user to try again.
-   */
-  onGoToLineFailed() {
-    let window = Services.wm.getMostRecentWindow(null);
-    Services.prompt.alert(
-      window,
-      this.bundle.GetStringFromName("outOfRangeTitle"),
-      this.bundle.GetStringFromName("outOfRangeText")
-    );
-    this.promptAndGoToLine();
-  },
-
-  /**
-   * Update the wrapping pref based on the child's current state.
-   * @param state
-   *        Whether wrapping is currently enabled in the child.
-   */
-  storeWrapping(state) {
-    Services.prefs.setBoolPref("view_source.wrap_long_lines", state);
-  },
-
-  /**
-   * Update the syntax highlighting pref based on the child's current state.
-   * @param state
-   *        Whether syntax highlighting is currently enabled in the child.
-   */
-  storeSyntaxHighlighting(state) {
-    Services.prefs.setBoolPref("view_source.syntax_highlight", state);
-  },
-};
-
-/**
- * Helper to decide if a URI maps to view source content.
- * @param uri
- *        String containing the URI
- */
-ViewSourceBrowser.isViewSource = function(uri) {
-  return uri.startsWith("view-source:");
-};
diff --git a/toolkit/components/viewsource/content/viewSourceUtils.js b/toolkit/components/viewsource/content/viewSourceUtils.js
index 42ee103f19ab..da9efd0d55b8 100644
--- a/toolkit/components/viewsource/content/viewSourceUtils.js
+++ b/toolkit/components/viewsource/content/viewSourceUtils.js
@@ -14,11 +14,6 @@
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-ChromeUtils.defineModuleGetter(
-  this,
-  "ViewSourceBrowser",
-  "resource://gre/modules/ViewSourceBrowser.jsm"
-);
 ChromeUtils.defineModuleGetter(
   this,
   "PrivateBrowsingUtils",
@@ -30,6 +25,11 @@ var gViewSourceUtils = {
   mnsIWebProgress: Ci.nsIWebProgress,
   mnsIWebPageDescriptor: Ci.nsIWebPageDescriptor,
 
+  // Get the ViewSource actor for a browsing context.
+  getViewSourceActor(aBrowsingContext) {
+    return aBrowsingContext.currentWindowGlobal.getActor("ViewSource");
+  },
+
   /**
    * Opens the view source window.
    *
@@ -106,9 +106,41 @@ var gViewSourceUtils = {
    *        lineNumber (optional):
    *          The line number to focus on once the source is loaded.
    */
-  viewSourceInBrowser(aArgs) {
-    let viewSourceBrowser = new ViewSourceBrowser(aArgs.viewSourceBrowser);
-    viewSourceBrowser.loadViewSource(aArgs);
+  viewSourceInBrowser({
+    URL,
+    viewSourceBrowser,
+    browser,
+    outerWindowID,
+    lineNumber,
+  }) {
+    if (!URL) {
+      throw new Error("Must supply a URL when opening view source.");
+    }
+
+    if (browser) {
+      viewSourceBrowser.sameProcessAsFrameLoader = browser.frameLoader;
+
+      // If we're dealing with a remote browser, then the browser
+      // for view source needs to be remote as well.
+      if (viewSourceBrowser.remoteType != browser.remoteType) {
+        // In this base case, where we are handed a  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");
+      }
+    } else if (outerWindowID) {
+      throw new Error("Must supply the browser if passing the outerWindowID");
+    }
+
+    let viewSourceActor = this.getViewSourceActor(
+      viewSourceBrowser.browsingContext
+    );
+    viewSourceActor.sendAsyncMessage("ViewSource:LoadSource", {
+      URL,
+      outerWindowID,
+      lineNumber,
+    });
   },
 
   /**
@@ -116,31 +148,21 @@ var gViewSourceUtils = {
    * .  This allows for non-window display methods, such as a tab from
    * Firefox.
    *
-   * @param aViewSourceInBrowser
-   *        The browser containing the page to view the source of.
+   * @param aBrowsingContext:
+   *        The child browsing context containing the document to view the source of.
    * @param aGetBrowserFn
    *        A function that will return a browser to open the source in.
    */
-  viewPartialSourceInBrowser(aViewSourceInBrowser, aGetBrowserFn) {
-    let mm = aViewSourceInBrowser.messageManager;
-    mm.addMessageListener("ViewSource:GetSelectionDone", function gotSelection(
-      message
-    ) {
-      mm.removeMessageListener("ViewSource:GetSelectionDone", gotSelection);
+  async viewPartialSourceInBrowser(aBrowsingContext, aGetBrowserFn) {
+    let sourceActor = this.getViewSourceActor(aBrowsingContext);
+    if (sourceActor) {
+      let data = await sourceActor.sendQuery("ViewSource:GetSelection", {});
 
-      if (!message.data) {
-        return;
-      }
-
-      let viewSourceBrowser = new ViewSourceBrowser(aGetBrowserFn());
-      viewSourceBrowser.loadViewSourceFromSelection(
-        message.data.uri,
-        message.data.drawSelection,
-        message.data.baseURI
+      let targetActor = this.getViewSourceActor(
+        aGetBrowserFn().browsingContext
       );
-    });
-
-    mm.sendAsyncMessage("ViewSource:GetSelection");
+      targetActor.sendAsyncMessage("ViewSource:LoadSourceWithSelection", data);
+    }
   },
 
   buildEditorArgs(aPath, aLineNumber) {
diff --git a/toolkit/components/viewsource/jar.mn b/toolkit/components/viewsource/jar.mn
index 7585980ea6c7..e483f3080a4b 100644
--- a/toolkit/components/viewsource/jar.mn
+++ b/toolkit/components/viewsource/jar.mn
@@ -4,4 +4,3 @@
 
 toolkit.jar:
   content/global/viewSourceUtils.js         (content/viewSourceUtils.js)
-  content/global/viewSource-content.js      (content/viewSource-content.js)
diff --git a/toolkit/components/viewsource/moz.build b/toolkit/components/viewsource/moz.build
index aecd25682991..c13e61e30be7 100644
--- a/toolkit/components/viewsource/moz.build
+++ b/toolkit/components/viewsource/moz.build
@@ -9,9 +9,5 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
-EXTRA_JS_MODULES += [
-    'ViewSourceBrowser.jsm',
-]
-
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'View Source')
diff --git a/toolkit/components/viewsource/test/browser/browser.ini b/toolkit/components/viewsource/test/browser/browser.ini
index 8b90795a2d0c..206ad969a897 100644
--- a/toolkit/components/viewsource/test/browser/browser.ini
+++ b/toolkit/components/viewsource/test/browser/browser.ini
@@ -9,5 +9,6 @@ support-files = head.js
 skip-if = (os == "win" && processor == "aarch64") # disabled on aarch64 due to 1531590
 [browser_gotoline.js]
 [browser_open_docgroup.js]
+[browser_partialsource.js]
 [browser_srcdoc.js]
 [browser_viewsourceprefs.js]
diff --git a/toolkit/components/viewsource/test/browser/browser_gotoline.js b/toolkit/components/viewsource/test/browser/browser_gotoline.js
index 2321b9371119..7837517ec64c 100644
--- a/toolkit/components/viewsource/test/browser/browser_gotoline.js
+++ b/toolkit/components/viewsource/test/browser/browser_gotoline.js
@@ -24,9 +24,13 @@ var checkViewSource = async function(aTab) {
   });
 
   for (let i = 1; i <= 3; i++) {
-    browser.messageManager.sendAsyncMessage("ViewSource:GoToLine", {
-      lineNumber: i,
-    });
+    browser.sendMessageToActor(
+      "ViewSource:GoToLine",
+      {
+        lineNumber: i,
+      },
+      "ViewSourcePage"
+    );
     await SpecialPowers.spawn(browser, [i], async function(i) {
       let selection = content.getSelection();
       Assert.equal(selection.toString(), "line " + i, "Correct text selected");
diff --git a/toolkit/components/viewsource/test/browser/browser_partialsource.js b/toolkit/components/viewsource/test/browser/browser_partialsource.js
new file mode 100644
index 000000000000..48b0adccc8eb
--- /dev/null
+++ b/toolkit/components/viewsource/test/browser/browser_partialsource.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const frameSource =
+  "some textother text";
+const sources = [
+  ``,
+  ``,
+];
+
+add_task(async function partial_source() {
+  for (let source of sources) {
+    let tab = await BrowserTestUtils.openNewForegroundTab(
+      gBrowser,
+      "data:text/html," + source
+    );
+
+    let frameBC = gBrowser.selectedBrowser.browsingContext.getChildren()[0];
+
+    await SpecialPowers.spawn(frameBC, [], () => {
+      let element = content.document.getElementById("other");
+      content.focus();
+      content.getSelection().selectAllChildren(element);
+    });
+
+    let sourceTab = await openViewPartialSource("#other", frameBC);
+
+    let browser = gBrowser.selectedBrowser;
+    let textContent = await SpecialPowers.spawn(browser, [], async function() {
+      return content.document.body.textContent;
+    });
+    is(
+      textContent,
+      'other text',
+      "Correct content loaded"
+    );
+    let selection = await SpecialPowers.spawn(browser, [], async function() {
+      return String(content.getSelection());
+    });
+    is(selection, "other text", "Correct text selected");
+
+    gBrowser.removeTab(sourceTab);
+    gBrowser.removeTab(tab);
+  }
+});
diff --git a/toolkit/components/viewsource/test/browser/head.js b/toolkit/components/viewsource/test/browser/head.js
index c129dc62e51a..186a17c3e047 100644
--- a/toolkit/components/viewsource/test/browser/head.js
+++ b/toolkit/components/viewsource/test/browser/head.js
@@ -87,9 +87,13 @@ async function openViewSource() {
  * @param aCSSSelector - used to specify a node within the selection to
  *                       view the source of. It is expected that this node is
  *                       within an existing selection.
+ * @param aBrowsingContext - browsing context containing a subframe (optional).
  * @returns the new tab which shows the source.
  */
-async function openViewPartialSource(aCSSSelector) {
+async function openViewPartialSource(
+  aCSSSelector,
+  aBrowsingContext = gBrowser.selectedBrowser
+) {
   let contentAreaContextMenuPopup = document.getElementById(
     "contentAreaContextMenu"
   );
@@ -100,7 +104,7 @@ async function openViewPartialSource(aCSSSelector) {
   await BrowserTestUtils.synthesizeMouseAtCenter(
     aCSSSelector,
     { type: "contextmenu", button: 2 },
-    gBrowser.selectedBrowser
+    aBrowsingContext
   );
   await popupShownPromise;
 
@@ -161,13 +165,12 @@ async function openViewFrameSourceTab(aCSSSelector) {
  * complete.
  */
 function waitForSourceLoaded(tab) {
-  return new Promise(resolve => {
-    let mm = tab.linkedBrowser.messageManager;
-    mm.addMessageListener("ViewSource:SourceLoaded", function sourceLoaded() {
-      mm.removeMessageListener("ViewSource:SourceLoaded", sourceLoaded);
-      setTimeout(resolve, 0);
-    });
-  });
+  return BrowserTestUtils.waitForContentEvent(
+    tab.linkedBrowser,
+    "pageshow",
+    false,
+    event => String(event.target.location).startsWith("view-source")
+  );
 }
 
 /**
diff --git a/toolkit/modules/ActorManagerParent.jsm b/toolkit/modules/ActorManagerParent.jsm
index 0c19dd0d1f25..ba627c70f25b 100644
--- a/toolkit/modules/ActorManagerParent.jsm
+++ b/toolkit/modules/ActorManagerParent.jsm
@@ -300,6 +300,35 @@ let ACTORS = {
     allFrames: true,
   },
 
+  // This actor is available for all pages that one can
+  // view the source of, however it won't be created until a
+  // request to view the source is made via the message
+  // 'ViewSource:LoadSource' or 'ViewSource:LoadSourceWithSelection'.
+  ViewSource: {
+    child: {
+      moduleURI: "resource://gre/actors/ViewSourceChild.jsm",
+    },
+
+    allFrames: true,
+  },
+
+  // This actor is for the view-source page itself.
+  ViewSourcePage: {
+    parent: {
+      moduleURI: "resource://gre/actors/ViewSourcePageParent.jsm",
+    },
+    child: {
+      moduleURI: "resource://gre/actors/ViewSourcePageChild.jsm",
+      events: {
+        pageshow: { capture: true },
+        click: {},
+      },
+    },
+
+    matches: ["view-source:*"],
+    allFrames: true,
+  },
+
   WebChannel: {
     parent: {
       moduleURI: "resource://gre/actors/WebChannelParent.jsm",
@@ -485,13 +514,6 @@ let LEGACY_ACTORS = {
     },
   },
 
-  SelectionSource: {
-    child: {
-      module: "resource://gre/actors/SelectionSourceChild.jsm",
-      messages: ["ViewSource:GetSelection"],
-    },
-  },
-
   UnselectedTabHover: {
     child: {
       module: "resource://gre/actors/UnselectedTabHoverChild.jsm",