Bug 1524970: Update more frontend code to explicitly pass a csp. r=Gijs,jdescottes
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Wed, 27 Mar 2019 16:38:01 +0000
changeset 525211 3adf16249cb6b6617084d5f1896dec1cce93fc45
parent 525210 1735ced17de7faa90488d1e942184c9c6ca4f6c1
child 525212 5c2caf4471c6059d0c1fee47108a2a96b5ddfcf9
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, jdescottes
bugs1524970
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1524970: Update more frontend code to explicitly pass a csp. r=Gijs,jdescottes Differential Revision: https://phabricator.services.mozilla.com/D24959
browser/base/content/browser-context.inc
browser/base/content/browser.js
browser/base/content/nsContextMenu.js
browser/components/newtab/lib/ASRouter.jsm
browser/components/pocket/content/main.js
devtools/client/shared/link.js
devtools/shared/fronts/targets/target-mixin.js
dom/interfaces/base/nsIBrowser.idl
toolkit/content/widgets/browser-custom-element.js
toolkit/modules/RemoteWebProgress.jsm
toolkit/modules/WebProgressChild.jsm
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -315,17 +315,17 @@
                 accesskey="&selectAllCmd.accesskey;"
                 command="cmd_selectAll"/>
       <menuseparator id="context-sep-selectall"/>
       <menuitem id="context-keywordfield"
                 label="&keywordfield.label;"
                 accesskey="&keywordfield.accesskey;"
                 oncommand="AddKeywordForSearchField();"/>
       <menuitem id="context-searchselect"
-                oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms, this.principal);"/>
+                oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms, this.principal, this.csp);"/>
       <menuseparator id="context-sep-sendlinktodevice" class="sync-ui-item"
                      hidden="true"/>
       <menu id="context-sendlinktodevice"
             class="sync-ui-item"
             label="&sendLinkToDevice.label;"
             accesskey="&sendLinkToDevice.accesskey;"
             hidden="true">
         <menupopup id="context-sendlinktodevice-popup"
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1827,17 +1827,17 @@ var gBrowserInit = {
                 window.arguments[6], !!window.arguments[6], window.arguments[7],
                 // TODO fix allowInheritPrincipal to default to false.
                 // Default to true unless explicitly set to false because of bug 1475201.
                 window.arguments[8] !== false, window.arguments[9]);
         window.focus();
       } else {
         // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
         // Such callers expect that window.arguments[0] is handled as a single URI.
-        loadOneOrMoreURIs(uriToLoad, Services.scriptSecurityManager.getSystemPrincipal());
+        loadOneOrMoreURIs(uriToLoad, Services.scriptSecurityManager.getSystemPrincipal(), null);
       }
     });
   },
 
   /**
    * Use this function as an entry point to schedule tasks that
    * need to run once per window after startup, and can be scheduled
    * by using an idle callback.
@@ -2303,17 +2303,17 @@ function BrowserHome(aEvent) {
   switch (where) {
   case "current":
     // If we're going to load an initial page in the current tab as the
     // home page, we set initialPageLoadedFromURLBar so that the URL
     // bar is cleared properly (even during a remoteness flip).
     if (isInitialPage(homePage)) {
       gBrowser.selectedBrowser.initialPageLoadedFromUserAction = homePage;
     }
-    loadOneOrMoreURIs(homePage, Services.scriptSecurityManager.getSystemPrincipal());
+    loadOneOrMoreURIs(homePage, Services.scriptSecurityManager.getSystemPrincipal(), null);
     if (isBlankPageURL(homePage)) {
       focusAndSelectUrlBar();
     } else {
       gBrowser.selectedBrowser.focus();
     }
     notifyObservers = true;
     break;
   case "tabshifted":
@@ -2323,16 +2323,17 @@ function BrowserHome(aEvent) {
     // The homepage observer event should only be triggered when the homepage opens
     // in the foreground. This is mostly to support the homepage changed by extension
     // doorhanger which doesn't currently support background pages. This may change in
     // bug 1438396.
     notifyObservers = !loadInBackground;
     gBrowser.loadTabs(urls, {
       inBackground: loadInBackground,
       triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+      csp: null,
     });
     break;
   case "window":
     // OpenBrowserWindow will trigger the observer event, so no need to do so here.
     notifyObservers = false;
     OpenBrowserWindow();
     break;
   }
@@ -2340,30 +2341,31 @@ function BrowserHome(aEvent) {
     // A notification for when a user has triggered their homepage. This is used
     // to display a doorhanger explaining that an extension has modified the
     // homepage, if necessary. Observers are only notified if the homepage
     // becomes the active page.
     Services.obs.notifyObservers(null, "browser-open-homepage-start");
   }
 }
 
-function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal) {
+function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal, aCsp) {
   // we're not a browser window, pass the URI string to a new browser window
   if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
     window.openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", "all,dialog=no", aURIString);
     return;
   }
 
   // This function throws for certain malformed URIs, so use exception handling
   // so that we don't disrupt startup
   try {
     gBrowser.loadTabs(aURIString.split("|"), {
       inBackground: false,
       replace: true,
       triggeringPrincipal: aTriggeringPrincipal,
+      csp: aCsp,
     });
   } catch (e) {
   }
 }
 
 /**
  * Focuses the location bar input field and selects its contents.
  *
@@ -2552,16 +2554,29 @@ function BrowserTryToCloseWindow() {
 
 function loadURI(uri, referrerInfo, postData, allowThirdPartyFixup,
                  userContextId, originPrincipal, forceAboutBlankViewerInCurrent,
                  triggeringPrincipal, allowInheritPrincipal = false, csp = null) {
   if (!triggeringPrincipal) {
     throw new Error("Must load with a triggering Principal");
   }
 
+  // After Bug 965637 we can remove that Error because the CSP will not
+  // hang off the Principal anymore. Please note that the SystemPrincipal
+  // can not hold a CSP!
+  if (AppConstants.EARLY_BETA_OR_EARLIER) {
+    // Please note that the backend will still query the CSP from the Principal in
+    // release versions of Firefox. We use this error just to annotate all the
+    // callsites to explicitly pass a CSP before we can remove the CSP from
+    // the Principal within Bug 965637.
+    if (!triggeringPrincipal.isSystemPrincipal && triggeringPrincipal.csp && !csp) {
+      throw new Error("If Principal has CSP then we need an explicit CSP");
+    }
+  }
+
   try {
     openLinkIn(uri, "current",
                { referrerInfo,
                  postData,
                  allowThirdPartyFixup,
                  userContextId,
                  originPrincipal,
                  triggeringPrincipal,
@@ -3711,16 +3726,20 @@ var browserDragAndDrop = {
       aEvent.preventDefault();
     }
   },
 
   getTriggeringPrincipal(aEvent) {
     return Services.droppedLinkHandler.getTriggeringPrincipal(aEvent);
   },
 
+  getCSP(aEvent) {
+    return Services.droppedLinkHandler.getCSP(aEvent);
+  },
+
   validateURIsForDrop(aEvent, aURIsCount, aURIs) {
     return Services.droppedLinkHandler.validateURIsForDrop(aEvent,
                                                            aURIsCount,
                                                            aURIs);
   },
 
   dropLinks(aEvent, aDisallowInherit) {
     return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
@@ -3788,16 +3807,17 @@ var newTabButtonObserver = {
   onDragOver(aEvent) {
     browserDragAndDrop.dragOver(aEvent);
   },
   onDragExit(aEvent) {},
   async onDrop(aEvent) {
     let shiftKey = aEvent.shiftKey;
     let links = browserDragAndDrop.dropLinks(aEvent);
     let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
+    let csp = browserDragAndDrop.getCSP(aEvent);
 
     if (links.length >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
       // Sync dialog cannot be used inside drop event handler.
       let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(links.length,
                                                                   window);
       if (!answer) {
         return;
       }
@@ -3809,30 +3829,32 @@ var newTabButtonObserver = {
         // Allow third-party services to fixup this URL.
         openNewTabWith(data.url, shiftKey, {
           // TODO fix allowInheritPrincipal
           // (this is required by javascript: drop to the new window) Bug 1475201
           allowInheritPrincipal: true,
           postData: data.postData,
           allowThirdPartyFixup: true,
           triggeringPrincipal,
+          csp,
         });
       }
     }
   },
 };
 
 var newWindowButtonObserver = {
   onDragOver(aEvent) {
     browserDragAndDrop.dragOver(aEvent);
   },
   onDragExit(aEvent) {},
   async onDrop(aEvent) {
     let links = browserDragAndDrop.dropLinks(aEvent);
     let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
+    let csp = browserDragAndDrop.getCSP(aEvent);
 
     if (links.length >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
       // Sync dialog cannot be used inside drop event handler.
       let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(links.length,
                                                                   window);
       if (!answer) {
         return;
       }
@@ -3844,16 +3866,17 @@ var newWindowButtonObserver = {
         // Allow third-party services to fixup this URL.
         openNewWindowWith(data.url, {
           // TODO fix allowInheritPrincipal
           // (this is required by javascript: drop to the new window) Bug 1475201
           allowInheritPrincipal: true,
           postData: data.postData,
           allowThirdPartyFixup: true,
           triggeringPrincipal,
+          csp,
         });
       }
     }
   },
 };
 const DOMEventHandler = {
   init() {
     let mm = window.messageManager;
@@ -4261,17 +4284,17 @@ const BrowserSearch = {
    * @param purpose [optional]
    *        A string meant to indicate the context of the search request. This
    *        allows the search service to provide a different nsISearchSubmission
    *        depending on e.g. where the search is triggered in the UI.
    *
    * @return engine The search engine used to perform a search, or null if no
    *                search was performed.
    */
-  _loadSearch(searchText, useNewTab, purpose, triggeringPrincipal) {
+  _loadSearch(searchText, useNewTab, purpose, triggeringPrincipal, csp) {
     if (!triggeringPrincipal) {
       throw new Error("Required argument triggeringPrincipal missing within _loadSearch");
     }
 
     let engine = Services.search.defaultEngine;
 
     let submission = engine.getSubmission(searchText, null, purpose); // HTML response
 
@@ -4284,29 +4307,30 @@ const BrowserSearch = {
     }
 
     let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground");
     openLinkIn(submission.uri.spec,
                useNewTab ? "tab" : "current",
                { postData: submission.postData,
                  inBackground,
                  relatedToCurrent: true,
-                 triggeringPrincipal });
+                 triggeringPrincipal,
+                 csp });
 
     return engine;
   },
 
   /**
    * Perform a search initiated from the context menu.
    *
    * This should only be called from the context menu. See
    * BrowserSearch.loadSearch for the preferred API.
    */
-  loadSearchFromContext(terms, triggeringPrincipal) {
-    let engine = BrowserSearch._loadSearch(terms, true, "contextmenu", triggeringPrincipal);
+  loadSearchFromContext(terms, triggeringPrincipal, csp) {
+    let engine = BrowserSearch._loadSearch(terms, true, "contextmenu", triggeringPrincipal, csp);
     if (engine) {
       BrowserSearch.recordSearchInTelemetry(engine, "contextmenu");
     }
   },
 
   pasteAndSearch(event) {
     BrowserSearch.searchBar.select();
     goDoCommand("cmd_paste");
@@ -6422,16 +6446,17 @@ function middleMousePaste(event) {
     }
 
     if (where != "current" ||
         lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
       openUILink(data.url, event,
                  { ignoreButton: true,
                    allowInheritPrincipal: data.mayInheritPrincipal,
                    triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
+                   csp: gBrowser.selectedBrowser.csp,
                  });
     }
   });
 
   if (event instanceof Event) {
     event.stopPropagation();
   }
 }
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -860,31 +860,33 @@ nsContextMenu.prototype = {
     openLinkIn(this.linkURL, "current", this._openLinkInParameters());
   },
 
   // Open frame in a new tab.
   openFrameInTab() {
     openLinkIn(gContextMenuContentData.docLocation, "tab",
                { charset: gContextMenuContentData.charSet,
                  triggeringPrincipal: this.browser.contentPrincipal,
+                 csp: this.browser.csp,
                  referrerInfo: gContextMenuContentData.frameReferrerInfo });
   },
 
   // Reload clicked-in frame.
   reloadFrame(aEvent) {
     let forceReload = aEvent.shiftKey;
     this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadFrame",
                                                  null, { target: this.target, forceReload });
   },
 
   // Open clicked-in frame in its own window.
   openFrame() {
     openLinkIn(gContextMenuContentData.docLocation, "window",
                { charset: gContextMenuContentData.charSet,
                  triggeringPrincipal: this.browser.contentPrincipal,
+                 csp: this.browser.csp,
                  referrerInfo: gContextMenuContentData.frameReferrerInfo });
   },
 
   // Open clicked-in frame in the same window.
   showOnlyThisFrame() {
     urlSecurityCheck(gContextMenuContentData.docLocation,
                      this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
@@ -946,16 +948,17 @@ nsContextMenu.prototype = {
   },
 
   viewImageDesc(e) {
     urlSecurityCheck(this.imageDescURL,
                      this.principal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
     openUILink(this.imageDescURL, e, { referrerInfo: gContextMenuContentData.referrerInfo,
                                        triggeringPrincipal: this.principal,
+                                       csp: this.csp,
     });
   },
 
   viewFrameInfo() {
     BrowserPageInfo(gContextMenuContentData.docLocation, null, null,
                     this.frameOuterWindowID, this.browser);
   },
 
@@ -992,16 +995,17 @@ nsContextMenu.prototype = {
       }, Cu.reportError);
     } else {
       urlSecurityCheck(this.mediaURL,
                        this.principal,
                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
       openUILink(this.mediaURL, e, { referrerInfo,
                                      forceAllowDataURI: true,
                                      triggeringPrincipal: this.principal,
+                                     csp: this.csp,
       });
     }
   },
 
   saveVideoFrameAsImage() {
     let mm = this.browser.messageManager;
     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
 
@@ -1048,16 +1052,17 @@ nsContextMenu.prototype = {
   // Change current window to the URL of the background image.
   viewBGImage(e) {
     urlSecurityCheck(this.bgImageURL,
                      this.principal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
 
     openUILink(this.bgImageURL, e, { referrerInfo: gContextMenuContentData.referrerInfo,
                                      triggeringPrincipal: this.principal,
+                                     csp: this.csp,
     });
   },
 
   setDesktopBackground() {
     let mm = this.browser.messageManager;
 
     mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground", null,
                         { target: this.target });
@@ -1521,16 +1526,17 @@ nsContextMenu.prototype = {
   // Formats the 'Search <engine> for "<selection or link text>"' context menu.
   formatSearchContextItem() {
     var menuItem = document.getElementById("context-searchselect");
     let selectedText = this.isTextSelected ? this.textSelected : this.linkTextStr;
 
     // Store searchTerms in context menu item so we know what to search onclick
     menuItem.searchTerms = selectedText;
     menuItem.principal = this.principal;
+    menuItem.csp = this.csp;
 
     // Copied to alert.js' prefillAlertInfo().
     // If the JS character after our truncation point is a trail surrogate,
     // include it in the truncated string to avoid splitting a surrogate pair.
     if (selectedText.length > 15) {
       let truncLength = 15;
       let truncChar = selectedText[15].charCodeAt(0);
       if (truncChar >= 0xDC00 && truncChar <= 0xDFFF)
--- a/browser/components/newtab/lib/ASRouter.jsm
+++ b/browser/components/newtab/lib/ASRouter.jsm
@@ -1025,16 +1025,17 @@ class _ASRouter {
       case ra.OPEN_PRIVATE_BROWSER_WINDOW:
         // Forcefully open about:privatebrowsing
         target.browser.ownerGlobal.OpenBrowserWindow({private: true});
         break;
       case ra.OPEN_URL:
         target.browser.ownerGlobal.openLinkIn(action.data.args, action.data.where || "current", {
           private: false,
           triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+          csp: null,
         });
         break;
       case ra.OPEN_ABOUT_PAGE:
         target.browser.ownerGlobal.openTrustedLinkIn(`about:${action.data.args}`, "tab");
         break;
       case ra.OPEN_PREFERENCES_PAGE:
         target.browser.ownerGlobal.openPreferences(action.data.category, {origin: action.data.origin});
         break;
@@ -1051,16 +1052,17 @@ class _ASRouter {
         target.browser.ownerGlobal.ConfirmationHint.show(tab, "pinTab", {showDescription: true});
         break;
       case ra.SHOW_FIREFOX_ACCOUNTS:
         const url = await FxAccounts.config.promiseSignUpURI("snippets");
         // We want to replace the current tab.
         target.browser.ownerGlobal.openLinkIn(url, "current", {
           private: false,
           triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+          csp: null,
         });
         break;
     }
   }
 
   dispatch(action, target) {
     this.onMessage({data: action, target});
   }
--- a/browser/components/pocket/content/main.js
+++ b/browser/components/pocket/content/main.js
@@ -90,17 +90,17 @@ var pktUI = (function() {
     /**
      * Show the sign-up panel
      */
     function showSignUp() {
         // AB test: Direct logged-out users to tab vs panel
         if (pktApi.getSignupPanelTabTestVariant() == "v2") {
             let site = Services.prefs.getCharPref("extensions.pocket.site");
             openTabWithUrl("https://" + site + "/firefox_learnmore?s=ffi&t=autoredirect&tv=page_learnmore&src=ff_ext",
-                           Services.scriptSecurityManager.createNullPrincipal({}));
+                           Services.scriptSecurityManager.createNullPrincipal({}), null);
 
             // force the panel closed before it opens
             getPanel().hidePopup();
 
             return;
         }
 
         // Control: Show panel as normal
@@ -318,25 +318,25 @@ var pktUI = (function() {
         var _showMessageId = "show";
         pktUIMessaging.addMessageListener(iframe, _showMessageId, function(panelId, data) {
             // Let panel know that it is ready
             pktUIMessaging.sendMessageToPanel(panelId, _showMessageId);
         });
 
         // Open a new tab with a given url
         var _openTabWithUrlMessageId = "openTabWithUrl";
-        pktUIMessaging.addMessageListener(iframe, _openTabWithUrlMessageId, function(panelId, data, contentPrincipal) {
+        pktUIMessaging.addMessageListener(iframe, _openTabWithUrlMessageId, function(panelId, data, contentPrincipal, csp) {
             try {
               urlSecurityCheck(data.url, contentPrincipal, Services.scriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
             } catch (ex) {
               return;
             }
 
             var url = data.url;
-            openTabWithUrl(url, contentPrincipal);
+            openTabWithUrl(url, contentPrincipal, csp);
             pktUIMessaging.sendResponseMessageToPanel(panelId, _openTabWithUrlMessageId, url);
         });
 
         // Close the panel
         var _closeMessageId = "close";
         pktUIMessaging.addMessageListener(iframe, _closeMessageId, function(panelId, data) {
             getPanel().hidePopup();
         });
@@ -471,46 +471,49 @@ var pktUI = (function() {
     }
 
     // -- Browser Navigation -- //
 
     /**
      * Open a new tab with a given url and notify the iframe panel that it was opened
      */
 
-    function openTabWithUrl(url, aTriggeringPrincipal) {
+    function openTabWithUrl(url, aTriggeringPrincipal, aCsp) {
         let recentWindow = Services.wm.getMostRecentWindow("navigator:browser");
         if (!recentWindow) {
           Cu.reportError("Pocket: No open browser windows to openTabWithUrl");
           return;
         }
 
         // If the user is in permanent private browsing than this is not an issue,
         // since the current window will always share the same cookie jar as the other
         // windows.
         if (!PrivateBrowsingUtils.isWindowPrivate(recentWindow) ||
             PrivateBrowsingUtils.permanentPrivateBrowsing) {
           recentWindow.openWebLinkIn(url, "tab", {
             triggeringPrincipal: aTriggeringPrincipal,
+            csp: aCsp,
           });
           return;
         }
 
         for (let win of Services.wm.getEnumerator("navigator:browser")) {
           if (!PrivateBrowsingUtils.isWindowPrivate(win)) {
             win.openWebLinkIn(url, "tab", {
               triggeringPrincipal: aTriggeringPrincipal,
+              csp: aCsp,
             });
             return;
           }
         }
 
         // If there were no non-private windows opened already.
         recentWindow.openWebLinkIn(url, "window", {
           triggeringPrincipal: aTriggeringPrincipal,
+          csp: aCsp,
         });
     }
 
 
     // -- Helper Functions -- //
 
     function getCurrentUrl() {
         return gBrowser.currentURI.spec;
@@ -589,17 +592,20 @@ var pktUIMessaging = (function() {
             if (!nodePrincipal || !nodePrincipal.URI || !nodePrincipal.URI.spec.startsWith("about:pocket")) {
                 return;
             }
 
             // Pass in information to callback
             var payload = JSON.parse(e.target.getAttribute("payload"))[0];
             var panelId = payload.panelId;
             var data = payload.data;
-            callback(panelId, data, nodePrincipal);
+            // After Bug 965637 we can query the csp from the document instead
+            // of the nodePrincipal, we can just use: e.target.ownerDocument.csp;
+            var csp = e.target.nodePrincipal.csp;
+            callback(panelId, data, nodePrincipal, csp);
 
             // Cleanup the element
             e.target.remove();
         }, false, true);
     }
 
     /**
      * Send a message to the panel's iframe
--- a/devtools/client/shared/link.js
+++ b/devtools/client/shared/link.js
@@ -59,16 +59,17 @@ exports.openContentLink = async function
   if (!top) {
     return;
   }
   if (!options.triggeringPrincipal && top.gBrowser) {
     const tab = top.gBrowser.selectedTab;
     if (TargetFactory.isKnownTab(tab)) {
       const target = await TargetFactory.forTab(tab);
       options.triggeringPrincipal = target.contentPrincipal;
+      options.csp = target.csp;
     }
   }
   top.openWebLinkIn(url, "tab", options);
 };
 
 /**
  * Open a trusted |url| in a new tab using the SystemPrincipal.
  *
--- a/devtools/shared/fronts/targets/target-mixin.js
+++ b/devtools/shared/fronts/targets/target-mixin.js
@@ -353,16 +353,27 @@ function TargetMixin(parentClass) {
      */
     get contentPrincipal() {
       if (!this.isLocalTab) {
         return null;
       }
       return this.tab.linkedBrowser.contentPrincipal;
     }
 
+    /**
+     * Similar to the above get contentPrincipal(), the get csp()
+     * returns the CSP which should be used for opening links.
+     */
+    get csp() {
+      if (!this.isLocalTab) {
+        return null;
+      }
+      return this.tab.linkedBrowser.csp;
+    }
+
     // Attach the console actor
     async attachConsole() {
       this.activeConsole = await this.getFront("console");
       await this.activeConsole.startListeners([]);
 
       this._onInspectObject = packet => this.emit("inspect-object", packet);
       this.activeConsole.on("inspectObject", this._onInspectObject);
     }
--- a/dom/interfaces/base/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.idl
@@ -1,13 +1,14 @@
 /* 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/. */
 #include "nsISupports.idl"
 
+interface nsIContentSecurityPolicy;
 interface nsIPrincipal;
 interface nsIURI;
 
 webidl FrameLoader;
 
 [scriptable, uuid(14e5a0cb-e223-4202-95e8-fe53275193ea)]
 interface nsIBrowser : nsISupports
 {
@@ -71,16 +72,17 @@ interface nsIBrowser : nsISupports
    */
   void enableDisableCommandsRemoteOnly(in AString action,
                                        in unsigned long enabledLength,
                                        [array, size_is(enabledLength)] in string enabledCommands,
                                        in unsigned long disabledLength,
                                        [array, size_is(disabledLength)] in string disabledCommands);
 
   readonly attribute nsIPrincipal contentPrincipal;
+  readonly attribute nsIContentSecurityPolicy csp;
 
   /**
    * Called by Gecko when we need to call the web progress listeners on our
    * browser element in order to notify them about a content blocking event
    * which happened in the content process.
    * @param isWebProgressPassed whether we're passed a webProgress argument
    * @param isTopLevel whether we're in the top-level document
    * @param isLoadingDocument whether we're in the process of loading a document
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -233,16 +233,18 @@ class MozBrowser extends MozElements.Moz
     this._contentTitle = "";
 
     this._characterSet = "";
 
     this._mayEnableCharacterEncodingMenu = null;
 
     this._contentPrincipal = null;
 
+    this._csp = null;
+
     this._contentRequestContextID = null;
 
     this._fullZoom = 1;
 
     this._textZoom = 1;
 
     this._isSyntheticDocument = false;
 
@@ -597,16 +599,22 @@ class MozBrowser extends MozElements.Moz
   get mayEnableCharacterEncodingMenu() {
     return this.isRemoteBrowser ? this._mayEnableCharacterEncodingMenu : this.docShell.mayEnableCharacterEncodingMenu;
   }
 
   get contentPrincipal() {
     return this.isRemoteBrowser ? this._contentPrincipal : this.contentDocument.nodePrincipal;
   }
 
+  get csp() {
+    // After Bug 965637 we can query the csp directly from the contentDocument
+    // instead of contentDocument.nodePrincipal.
+    return this.isRemoteBrowser ? this._csp : this.contentDocument.nodePrincipal.csp;
+  }
+
   get contentRequestContextID() {
     if (this.isRemoteBrowser) {
       return this._contentRequestContextID;
     }
     try {
       return this.contentDocument.documentLoadGroup
         .requestContextID;
     } catch (e) {
@@ -804,19 +812,21 @@ class MozBrowser extends MozElements.Moz
       aURI = "about:blank";
     }
     let {
       flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
         referrerInfo,
         triggeringPrincipal,
         postData,
         headers,
+        csp,
     } = aParams || {};
     let loadURIOptions = {
       triggeringPrincipal,
+      csp,
       referrerInfo,
       loadFlags: flags,
       postData,
       headers,
     };
     this._wrapURIChangeCall(() =>
       this.webNavigation.loadURI(aURI, loadURIOptions));
   }
@@ -1037,16 +1047,19 @@ class MozBrowser extends MozElements.Moz
       this._remoteWebNavigationImpl = this._remoteWebNavigation.wrappedJSObject;
       this._remoteWebNavigationImpl.swapBrowser(this);
 
       // Initialize contentPrincipal to the about:blank principal for this loadcontext
       let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
       let aboutBlank = Services.io.newURI("about:blank");
       let ssm = Services.scriptSecurityManager;
       this._contentPrincipal = ssm.getLoadContextCodebasePrincipal(aboutBlank, this.loadContext);
+      // CSP for about:blank is null; if we ever change _contentPrincipal above,
+      // we should re-evaluate the CSP here.
+      this._csp = null;
 
       this.messageManager.addMessageListener("Browser:Init", this);
       this.messageManager.addMessageListener("DOMTitleChanged", this);
       this.messageManager.addMessageListener("ImageDocumentLoaded", this);
       this.messageManager.addMessageListener("FullZoomChange", this);
       this.messageManager.addMessageListener("TextZoomChange", this);
       this.messageManager.addMessageListener("ZoomChangeUsingMouseWheel", this);
 
--- a/toolkit/modules/RemoteWebProgress.jsm
+++ b/toolkit/modules/RemoteWebProgress.jsm
@@ -2,16 +2,19 @@
 // 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/.
 
 var EXPORTED_SYMBOLS = ["RemoteWebProgressManager"];
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
+ChromeUtils.defineModuleGetter(this, "E10SUtils",
+                               "resource://gre/modules/E10SUtils.jsm");
+
 function RemoteWebProgressRequest(uriOrSpec, originalURIOrSpec, matchedList) {
   this.wrappedJSObject = this;
 
   this._uri = uriOrSpec instanceof Ci.nsIURI ? uriOrSpec
                                           : Services.io.newURI(uriOrSpec);
   this._originalURI = originalURIOrSpec instanceof Ci.nsIURI ?
                         originalURIOrSpec : Services.io.newURI(originalURIOrSpec);
   this._matchedList = matchedList;
@@ -282,16 +285,17 @@ RemoteWebProgressManager.prototype = {
       remoteWebNav.canGoForward = json.canGoForward;
 
       if (isTopLevel) {
         remoteWebNav._currentURI = location;
         this._browser._documentURI = Services.io.newURI(json.documentURI);
         this._browser._contentTitle = json.title;
         this._browser._imageDocument = null;
         this._browser._contentPrincipal = json.principal;
+        this._browser._csp = E10SUtils.deserializeCSP(json.csp);
         this._browser._isSyntheticDocument = json.synthetic;
         this._browser._innerWindowID = json.innerWindowID;
         this._browser._contentRequestContextID = json.requestContextID;
       }
 
       this._callProgressListeners(
         Ci.nsIWebProgress.NOTIFY_LOCATION, "onLocationChange", webProgress,
         request, location, flags
--- a/toolkit/modules/WebProgressChild.jsm
+++ b/toolkit/modules/WebProgressChild.jsm
@@ -6,16 +6,18 @@
 
 var EXPORTED_SYMBOLS = ["WebProgressChild"];
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "AppConstants",
                                "resource://gre/modules/AppConstants.jsm");
+ChromeUtils.defineModuleGetter(this, "E10SUtils",
+                               "resource://gre/modules/E10SUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
                                    "@mozilla.org/xre/app-info;1",
                                    "nsICrashReporter");
 XPCOMUtils.defineLazyServiceGetter(this, "serializationHelper",
                                    "@mozilla.org/network/serialization-helper;1",
                                    "nsISerializationHelper");
 
@@ -144,16 +146,20 @@ class WebProgressChild {
     json.canGoForward = webNav.canGoForward;
 
     if (aWebProgress && aWebProgress.isTopLevel) {
       json.documentURI = this.mm.content.document.documentURIObject.spec;
       json.title = this.mm.content.document.title;
       json.charset = this.mm.content.document.characterSet;
       json.mayEnableCharacterEncodingMenu = this.mm.docShell.mayEnableCharacterEncodingMenu;
       json.principal = this.mm.content.document.nodePrincipal;
+      // After Bug 965637 we can query the csp directly from content.document
+      // instead of content.document.nodePrincipal.
+      let csp = this.mm.content.document.nodePrincipal.csp;
+      json.csp = E10SUtils.serializeCSP(csp);
       json.synthetic = this.mm.content.document.mozSyntheticDocument;
       json.inLoadURI = this.inLoadURI;
       json.requestContextID = this.mm.content.document.documentLoadGroup
         ? this.mm.content.document.documentLoadGroup.requestContextID
         : null;
 
       if (AppConstants.MOZ_CRASHREPORTER && CrashReporter.enabled) {
         let uri = aLocationURI;