Bug 1362034 - Have addTab() provide the correct triggering principal. r=ckerschb r=Gijs
authorJonathan Kingston <jkt@mozilla.com>
Fri, 06 Jul 2018 15:14:54 +0100
changeset 430798 6f3907a1da34a62133832dc981e9005ee30e9388
parent 430797 f194a48a48bb4ec47a2aefbfca4187d0be078967
child 430799 607f7fbeaeb46ca97549a623d4b83bf8aa7a5549
push id106266
push userjkingston@mozilla.com
push dateThu, 09 Aug 2018 16:43:39 +0000
treeherdermozilla-inbound@6f3907a1da34 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, Gijs
bugs1362034
milestone63.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 1362034 - Have addTab() provide the correct triggering principal. r=ckerschb r=Gijs Reviewers: ckerschb!, Gijs! Tags: #secure-revision Bug #: 1362034 Differential Revision: https://phabricator.services.mozilla.com/D2046
browser/base/content/browser-captivePortal.js
browser/base/content/browser-context.inc
browser/base/content/browser.js
browser/base/content/nsContextMenu.js
browser/base/content/tabbrowser.js
browser/base/content/utilityOverlay.js
browser/components/extensions/parent/ext-browser.js
browser/components/extensions/parent/ext-tabs.js
browser/components/nsBrowserGlue.js
browser/components/preferences/in-content/main.js
browser/components/sessionstore/SessionStore.jsm
browser/components/sessionstore/content/aboutSessionRestore.js
browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
devtools/client/framework/devtools-browser.js
devtools/client/netmonitor/src/utils/firefox/open-request-in-tab.js
devtools/client/performance-new/browser.js
devtools/client/responsive.html/browser/swap.js
devtools/startup/devtools-startup.js
docshell/base/nsDocShell.cpp
mobile/android/chrome/content/browser.js
mobile/android/components/extensions/ext-tabs.js
toolkit/components/normandy/lib/Heartbeat.jsm
--- a/browser/base/content/browser-captivePortal.js
+++ b/browser/base/content/browser-captivePortal.js
@@ -242,17 +242,22 @@ var CaptivePortalWatcher = {
   ensureCaptivePortalTab() {
     let tab;
     if (this._captivePortalTab) {
       tab = this._captivePortalTab.get();
     }
 
     // If the tab is gone or going, we need to open a new one.
     if (!tab || tab.closing || !tab.parentNode) {
-      tab = gBrowser.addTab(this.canonicalURL, { ownerTab: gBrowser.selectedTab });
+      tab = gBrowser.addWebTab(this.canonicalURL, {
+        ownerTab: gBrowser.selectedTab,
+        triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
+          userContextId: gBrowser.contentPrincipal.userContextId,
+        }),
+      });
       this._captivePortalTab = Cu.getWeakReference(tab);
     }
 
     gBrowser.selectedTab = tab;
 
     let canonicalURI = makeURI(this.canonicalURL);
 
     // When we are no longer captive, close the tab if it's at the canonical URL.
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -303,17 +303,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);"/>
+                oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms, this.principal);"/>
       <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
@@ -533,17 +533,18 @@ const gStoragePressureObserver = {
     let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
     let prefStrBundle = document.getElementById("bundle_preferences");
     let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
     buttons.push({
       label: prefStrBundle.getString("spaceAlert.learnMoreButton.label"),
       accessKey: prefStrBundle.getString("spaceAlert.learnMoreButton.accesskey"),
       callback(notificationBar, button) {
         let learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "storage-permissions";
-        gBrowser.selectedTab = gBrowser.addTab(learnMoreURL);
+        // This is a content URL, loaded from trusted UX.
+        gBrowser.selectedTab = gBrowser.addTrustedTab(learnMoreURL);
       }
     });
     if (usage < USAGE_THRESHOLD_BYTES) {
       // The firefox-used space < 5GB, then warn user to free some disk space.
       // This is because this usage is small and not the main cause for space issue.
       // In order to avoid the bad and wrong impression among users that
       // firefox eats disk space a lot, indicate users to clean up other disk space.
       msg = prefStrBundle.getFormattedString("spaceAlert.under5GB.message", [brandShortName]);
@@ -3966,18 +3967,21 @@ 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) {
+  _loadSearch(searchText, useNewTab, purpose, triggeringPrincipal) {
     let engine;
+    if (!triggeringPrincipal) {
+      throw new Error("Required argument triggeringPrincipal missing within _loadSearch");
+    }
 
     // If the search bar is visible, use the current engine, otherwise, fall
     // back to the default engine.
     if (isElementVisible(this.searchBar))
       engine = Services.search.currentEngine;
     else
       engine = Services.search.defaultEngine;
 
@@ -3991,43 +3995,30 @@ const BrowserSearch = {
       return null;
     }
 
     let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground");
     openLinkIn(submission.uri.spec,
                useNewTab ? "tab" : "current",
                { postData: submission.postData,
                  inBackground,
-                 relatedToCurrent: true });
+                 relatedToCurrent: true,
+                 triggeringPrincipal });
 
     return engine;
   },
 
   /**
-   * Just like _loadSearch, but preserving an old API.
-   *
-   * @return string Name of the search engine used to perform a search or null
-   *         if a search was not performed.
-   */
-  loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
-    let engine = BrowserSearch._loadSearch(searchText, useNewTab, purpose);
-    if (!engine) {
-      return null;
-    }
-    return engine.name;
-  },
-
-  /**
    * 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) {
-    let engine = BrowserSearch._loadSearch(terms, true, "contextmenu");
+  loadSearchFromContext(terms, triggeringPrincipal) {
+    let engine = BrowserSearch._loadSearch(terms, true, "contextmenu", triggeringPrincipal);
     if (engine) {
       BrowserSearch.recordSearchInTelemetry(engine, "contextmenu");
     }
   },
 
   pasteAndSearch(event) {
     BrowserSearch.searchBar.select();
     goDoCommand("cmd_paste");
@@ -7092,17 +7083,17 @@ function BrowserOpenAddonsMgr(aView) {
       aSubject.QueryInterface(Ci.nsIDOMWindow);
       aSubject.focus();
       resolve(aSubject);
     }, "EM-loaded");
   });
 }
 
 function BeginRecordExecution() {
-  gBrowser.selectedTab = gBrowser.addTab("about:blank", { recordExecution: "*" });
+  gBrowser.selectedTab = gBrowser.addWebTab("about:blank", { recordExecution: "*" });
 }
 
 function SaveRecordedExecution() {
   let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
   let window = gBrowser.ownerGlobal;
   fp.init(window, null, Ci.nsIFilePicker.modeSave);
   fp.open(rv => {
     if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace) {
@@ -7115,17 +7106,17 @@ function SaveRecordedExecution() {
 }
 
 function BeginReplayExecution() {
   let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
   let window = gBrowser.ownerGlobal;
   fp.init(window, null, Ci.nsIFilePicker.modeOpen);
   fp.open(rv => {
     if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace) {
-      gBrowser.selectedTab = gBrowser.addTab(null, { replayExecution: fp.file.path });
+      gBrowser.selectedTab = gBrowser.addWebTab(null, { replayExecution: fp.file.path });
     }
   });
 }
 
 function AddKeywordForSearchField() {
   let mm = gBrowser.selectedBrowser.messageManager;
 
   let onMessage = (message) => {
@@ -7384,17 +7375,18 @@ const gAccessibilityServiceIndicator = {
     return Services.prefs.getBoolPref("accessibility.indicator.enabled");
   },
 
   handleEvent({ key, type }) {
     if ((type === "keypress" && [" ", "Enter"].includes(key)) ||
          type === "click") {
       let a11yServicesSupportURL =
         Services.urlFormatter.formatURLPref("accessibility.support.url");
-      gBrowser.selectedTab = gBrowser.addTab(a11yServicesSupportURL);
+      // This is a known URL coming from trusted UI
+      gBrowser.selectedTab = gBrowser.addTrustedTab(a11yServicesSupportURL);
       Services.telemetry.scalarSet("a11y.indicator_acted_on", true);
     }
   },
 
   uninit() {
     Services.prefs.removeObserver("accessibility.indicator.enabled", this);
     Services.obs.removeObserver(this, "a11y-init-or-shutdown");
     this.update();
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1497,16 +1497,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;
 
     // 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/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1368,16 +1368,21 @@ window._gBrowser = {
       aOpener = params.opener;
       aOpenerBrowser = params.openerBrowser;
       aCreateLazyBrowser = params.createLazyBrowser;
       aNextTabParentId = params.nextTabParentId;
       aFocusUrlBar = params.focusUrlBar;
       aName = params.name;
     }
 
+    // all callers of loadOneTab need to pass a valid triggeringPrincipal.
+    if (!aTriggeringPrincipal) {
+      throw new Error("Required argument triggeringPrincipal missing within loadOneTab");
+    }
+
     var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
       Services.prefs.getBoolPref("browser.tabs.loadInBackground");
     var owner = bgLoad ? null : this.selectedTab;
 
     var tab = this.addTab(aURI, {
       triggeringPrincipal: aTriggeringPrincipal,
       referrerURI: aReferrerURI,
       referrerPolicy: aReferrerPolicy,
@@ -2129,16 +2134,40 @@ window._gBrowser = {
     tab.removeAttribute("linkedpanel");
 
     this._createLazyBrowser(tab);
 
     let evt = new CustomEvent("TabBrowserDiscarded", { bubbles: true });
     tab.dispatchEvent(evt);
   },
 
+  /**
+   * Loads a tab with a default null principal unless specified
+   */
+  addWebTab(aURI, params = {}) {
+    if (!params.triggeringPrincipal) {
+      params.triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal({
+        userContextId: params.userContextId,
+      });
+    }
+    if (Services.scriptSecurityManager.isSystemPrincipal(params.triggeringPrincipal)) {
+      throw new Error("System principal should never be passed into addWebTab()");
+    }
+    return this.addTab(aURI, params);
+  },
+
+  /**
+   * Must only be used sparingly for content that came from Chrome context
+   * If in doubt use addWebTab
+   */
+  addTrustedTab(aURI, params = {}) {
+    params.triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+    return this.addTab(aURI, params);
+  },
+
   // eslint-disable-next-line complexity
   addTab(aURI, {
     allowMixedContent,
     allowThirdPartyFixup,
     bulkOrderedOpen,
     charset,
     createLazyBrowser,
     disallowInheritPrincipal,
@@ -2164,16 +2193,23 @@ window._gBrowser = {
     sameProcessAsFrameLoader,
     skipAnimation,
     skipBackgroundNotify,
     triggeringPrincipal,
     userContextId,
     recordExecution,
     replayExecution,
   } = {}) {
+    // all callers of addTab that pass a params object need to pass
+    // a valid triggeringPrincipal.
+    if (!triggeringPrincipal) {
+      throw new Error("Required argument triggeringPrincipal missing within addTab");
+    }
+
+
     // if we're adding tabs, we're past interrupt mode, ditch the owner
     if (this.selectedTab.owner) {
       this.selectedTab.owner = null;
     }
 
     // Find the tab that opened this one, if any. This is used for
     // determining positioning, and inherited attributes such as the
     // user context ID.
@@ -2819,17 +2855,19 @@ window._gBrowser = {
     this._removingTabs.push(aTab);
     this._visibleTabs = null; // invalidate cache
 
     // Invalidate hovered tab state tracking for this closing tab.
     if (this.tabContainer._hoveredTab == aTab)
       aTab._mouseleave();
 
     if (newTab)
-      this.addTab(BROWSER_NEW_TAB_URL, { skipAnimation: true });
+      this.addTrustedTab(BROWSER_NEW_TAB_URL, {
+        skipAnimation: true,
+      });
     else
       TabBarVisibility.update();
 
     // We're committed to closing the tab now.
     // Dispatch a notification.
     // We dispatch it before any teardown so that event listeners can
     // inspect the tab that's about to close.
     var evt = new CustomEvent("TabClose", { bubbles: true, detail: { adoptedBy: aAdoptedByTab } });
@@ -3594,17 +3632,17 @@ window._gBrowser = {
     if (aIndex < numPinned || (aTab.pinned && aIndex == numPinned)) {
       params.pinned = true;
     }
 
     if (aTab.hasAttribute("usercontextid")) {
       // new tab must have the same usercontextid as the old one
       params.userContextId = aTab.getAttribute("usercontextid");
     }
-    let newTab = this.addTab("about:blank", params);
+    let newTab = this.addWebTab("about:blank", params);
     let newBrowser = this.getBrowserForTab(newTab);
 
     // Stop the about:blank load.
     newBrowser.stop();
     // Make sure it has a docshell.
     newBrowser.docShell;
 
     // We need to select the tab before calling swapBrowsersAndCloseOther
@@ -5271,20 +5309,32 @@ var TabContextMenu = {
   },
   createReopenInContainerMenu(event) {
     createUserContextMenu(event, {
       isContextMenu: true,
       excludeUserContextId: this.contextTab.getAttribute("usercontextid"),
     });
   },
   reopenInContainer(event) {
+    let userContextId = parseInt(event.target.getAttribute("data-usercontextid"));
+    /* Create a triggering principal that is able to load the new tab
+       For codebase principals that are about: chrome: or resource: we need system to load them.
+       Anything other than system principal needs to have the new userContextId.
+    */
+    let triggeringPrincipal = this.contextTab.linkedBrowser.contentPrincipal;
+    if (triggeringPrincipal.isNullPrincipal) {
+      triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal({ userContextId });
+    } else if (triggeringPrincipal.isCodebasePrincipal) {
+      triggeringPrincipal = Services.scriptSecurityManager.createCodebasePrincipal(triggeringPrincipal.URI, { userContextId });
+    }
     let newTab = gBrowser.addTab(this.contextTab.linkedBrowser.currentURI.spec, {
-      userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
+      userContextId,
       pinned: this.contextTab.pinned,
       index: this.contextTab._tPos + 1,
+      triggeringPrincipal,
     });
 
     if (gBrowser.selectedTab == this.contextTab) {
       gBrowser.selectedTab = newTab;
     }
     if (this.contextTab.muted) {
       if (!newTab.muted) {
         newTab.toggleMuteAudio(this.contextTab.muteReason);
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -207,16 +207,19 @@ function openTrustedLinkIn(url, where, a
 function openWebLinkIn(url, where, params) {
   if (!params) {
     params = {};
   }
 
   if (!params.triggeringPrincipal) {
     params.triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal({});
   }
+  if (Services.scriptSecurityManager.isSystemPrincipal(params.triggeringPrincipal)) {
+    throw new Error("System principal should never be passed into openWebLinkIn()");
+  }
 
   openUILinkIn(url, where, params);
 }
 
 /* openUILinkIn opens a URL in a place specified by the parameter |where|.
  *
  * |where| can be:
  *  "current"     current tab            (if there aren't any browser windows, then in a new window instead)
@@ -251,17 +254,18 @@ function openWebLinkIn(url, where, param
  *   userContextId        (unsigned int)
  *   targetBrowser        (XUL browser)
  */
 function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI) {
   var params;
 
   if (arguments.length == 3 && typeof arguments[2] == "object") {
     params = aAllowThirdPartyFixup;
-  } else {
+  }
+  if (!params || !params.triggeringPrincipal) {
     throw new Error("Required argument triggeringPrincipal missing within openUILinkIn");
   }
 
   params.fromChrome = true;
 
   openLinkIn(url, where, params);
 }
 
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -49,17 +49,21 @@ const getSender = (extension, target, se
 
 // Used by Extension.jsm
 global.tabGetSender = getSender;
 
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("uninstalling", (msg, extension) => {
   if (extension.uninstallURL) {
     let browser = windowTracker.topWindow.gBrowser;
-    browser.addTab(extension.uninstallURL, {relatedToCurrent: true});
+    browser.addTab(extension.uninstallURL, {
+      disallowInheritPrincipal: true,
+      relatedToCurrent: true,
+      triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+    });
   }
 });
 
 extensions.on("page-shutdown", (type, context) => {
   if (context.viewType == "tab") {
     if (context.extension.id !== context.xulBrowser.contentPrincipal.addonId) {
       // Only close extension tabs.
       // This check prevents about:addons from closing when it contains a
--- a/browser/components/extensions/parent/ext-tabs.js
+++ b/browser/components/extensions/parent/ext-tabs.js
@@ -522,28 +522,18 @@ this.tabs = class extends ExtensionAPI {
                 resolve(window);
               };
               Services.obs.addObserver(obs, "browser-delayed-startup-finished");
             } else {
               resolve(window);
             }
           }).then(window => {
             let url;
-
-            if (createProperties.url !== null) {
-              url = context.uri.resolve(createProperties.url);
+            let principal = context.principal;
 
-              if (!context.checkLoadURL(url, {dontReportErrors: true})) {
-                return Promise.reject({message: `Illegal URL: ${url}`});
-              }
-
-              if (createProperties.openInReaderMode) {
-                url = `about:reader?url=${encodeURIComponent(url)}`;
-              }
-            }
 
             if (createProperties.cookieStoreId && !extension.hasPermission("cookies")) {
               return Promise.reject({message: `No permission for cookieStoreId: ${createProperties.cookieStoreId}`});
             }
 
             let options = {};
             if (createProperties.cookieStoreId) {
               if (!global.isValidCookieStoreId(createProperties.cookieStoreId)) {
@@ -564,27 +554,43 @@ this.tabs = class extends ExtensionAPI {
                 if (!containerId) {
                   return Promise.reject({message: `No cookie store exists with ID ${createProperties.cookieStoreId}`});
                 }
 
                 options.userContextId = containerId;
               }
             }
 
+            if (createProperties.url !== null) {
+              url = context.uri.resolve(createProperties.url);
+
+              if (!context.checkLoadURL(url, {dontReportErrors: true})) {
+                return Promise.reject({message: `Illegal URL: ${url}`});
+              }
+
+              if (createProperties.openInReaderMode) {
+                url = `about:reader?url=${encodeURIComponent(url)}`;
+              }
+            } else {
+              url = window.BROWSER_NEW_TAB_URL;
+            }
             // Only set disallowInheritPrincipal on non-discardable urls as it
             // will override creating a lazy browser.  Setting triggeringPrincipal
             // will ensure other cases are handled, but setting it may prevent
             // creating about and data urls.
             let discardable = url && !url.startsWith("about:");
             if (!discardable) {
               // Make sure things like about:blank and data: URIs never inherit,
               // and instead always get a NullPrincipal.
               options.disallowInheritPrincipal = true;
-            } else {
-              options.triggeringPrincipal = context.principal;
+              // Falling back to codebase here as about: requires it, however is safe.
+              principal = Services.scriptSecurityManager.createCodebasePrincipal(Services.io.newURI(url), {
+                userContextId: options.userContextId,
+                privateBrowsingId: PrivateBrowsingUtils.isBrowserPrivate(window.gBrowser) ? 1 : 0,
+              });
             }
 
             tabListener.initTabReady();
             let currentTab = window.gBrowser.selectedTab;
 
             if (createProperties.openerTabId !== null) {
               options.ownerTab = tabTracker.getTab(createProperties.openerTabId);
               options.openerBrowser = options.ownerTab.linkedBrowser;
@@ -613,23 +619,24 @@ this.tabs = class extends ExtensionAPI {
               if (!discardable) {
                 return Promise.reject({message: `Cannot create a discarded new tab or "about" urls.`});
               }
               options.createLazyBrowser = true;
             } else if (createProperties.title) {
               return Promise.reject({message: `Title may only be set for discarded tabs.`});
             }
 
-            let nativeTab = window.gBrowser.addTab(url || window.BROWSER_NEW_TAB_URL, options);
+            options.triggeringPrincipal = principal;
+            let nativeTab = window.gBrowser.addTab(url, options);
             if (createProperties.discarded) {
               SessionStore.setTabState(nativeTab, {
                 entries: [{
                   url: url,
                   title: options.title,
-                  triggeringPrincipal_base64: Utils.serializePrincipal(context.principal),
+                  triggeringPrincipal_base64: Utils.serializePrincipal(principal),
                 }],
               });
             }
 
             if (active) {
               window.gBrowser.selectedTab = nativeTab;
               if (!url) {
                 window.focusAndSelectUrlBar();
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2350,17 +2350,17 @@ BrowserGlue.prototype = {
 
       const openTab = async (URI) => {
         let tab;
         if (!win) {
           win = await this._openURLInNewWindow(URI.uri);
           let tabs = win.gBrowser.tabs;
           tab = tabs[tabs.length - 1];
         } else {
-          tab = win.gBrowser.addTab(URI.uri);
+          tab = win.gBrowser.addWebTab(URI.uri);
         }
         tab.setAttribute("attention", true);
         return tab;
       };
 
       const firstTab = await openTab(URIs[0]);
       await Promise.all(URIs.slice(1).map(URI => openTab(URI)));
 
@@ -2430,17 +2430,17 @@ BrowserGlue.prototype = {
       imageURL = "chrome://branding/content/icon64.png";
     }
     let win = BrowserWindowTracker.getTopWindow({private: false});
     if (!win) {
       win = await this._openURLInNewWindow(url);
       let tabs = win.gBrowser.tabs;
       tab = tabs[tabs.length - 1];
     } else {
-      tab = win.gBrowser.addTab(url);
+      tab = win.gBrowser.addWebTab(url);
     }
     tab.setAttribute("attention", true);
     let clickCallback = (subject, topic, data) => {
       if (topic != "alertclickcallback")
         return;
       win.gBrowser.selectedTab = tab;
     };
 
@@ -2463,17 +2463,17 @@ BrowserGlue.prototype = {
     let clickCallback = async (subject, topic, data) => {
       if (topic != "alertclickcallback")
         return;
       let url = await FxAccounts.config.promiseManageDevicesURI("device-connected-notification");
       let win = BrowserWindowTracker.getTopWindow({private: false});
       if (!win) {
         this._openURLInNewWindow(url);
       } else {
-        win.gBrowser.addTab(url);
+        win.gBrowser.addWebTab(url);
       }
     };
 
     try {
       this.AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
     } catch (ex) {
       Cu.reportError("Error notifying of a new Sync device: " + ex);
     }
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -726,17 +726,17 @@ var gMainPane = {
     }
     const user = await fxAccounts.getSignedInUser();
     if (user) {
       // We have a user, open Sync preferences in the same tab
       win.openTrustedLinkIn("about:preferences#sync", "current");
       return;
     }
     let url = await FxAccounts.config.promiseSignInURI("dev-edition-setup");
-    let accountsTab = win.gBrowser.addTab(url);
+    let accountsTab = win.gBrowser.addWebTab(url);
     win.gBrowser.selectedTab = accountsTab;
   },
 
   // HOME PAGE
   /*
    * Preferences:
    *
    * browser.startup.page
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -2426,19 +2426,22 @@ var SessionStoreInternal = {
       throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
     if (!aWindow.gBrowser) {
       throw Components.Exception("Invalid window object: no gBrowser", Cr.NS_ERROR_INVALID_ARG);
     }
 
     // Create a new tab.
     let userContextId = aTab.getAttribute("usercontextid");
-    let newTab = aTab == aWindow.gBrowser.selectedTab ?
-      aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab, userContextId}) :
-      aWindow.gBrowser.addTab(null, {userContextId});
+
+    let tabOptions = {
+      userContextId,
+      ...(aTab == aWindow.gBrowser.selectedTab ? {relatedToCurrent: true, ownerTab: aTab} : {})
+    };
+    let newTab = aWindow.gBrowser.addTrustedTab(null, tabOptions);
 
     // Start the throbber to pretend we're doing something while actually
     // waiting for data from the frame script.
     newTab.setAttribute("busy", "true");
 
     // Collect state before flushing.
     let tabState = TabState.collect(aTab, TAB_CUSTOM_VALUES.get(aTab));
 
@@ -2517,17 +2520,17 @@ var SessionStoreInternal = {
     }
 
     // fetch the data of closed tab, while removing it from the array
     let {state, pos} = this.removeClosedTabData(closedTabs, aIndex);
 
     // create a new tab
     let tabbrowser = aWindow.gBrowser;
     let tab = tabbrowser.selectedTab =
-      tabbrowser.addTab(null, {
+      tabbrowser.addTrustedTab(null, {
         index: pos,
         pinned: state.pinned,
         userContextId: state.userContextId,
       });
 
     // restore tab content
     this.restoreTab(tab, state);
 
@@ -3487,23 +3490,23 @@ var SessionStoreInternal = {
           activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
           activeIndex = Math.max(activeIndex, 0);
           url = tabData.entries[activeIndex].url;
         }
 
         // Setting noInitialLabel is a perf optimization. Rendering tab labels
         // would make resizing the tabs more expensive as we're adding them.
         // Each tab will get its initial label set in restoreTab.
-        tab = tabbrowser.addTab(url,
-                                { createLazyBrowser,
-                                  skipAnimation: true,
-                                  noInitialLabel: true,
-                                  userContextId,
-                                  skipBackgroundNotify: true,
-                                  bulkOrderedOpen: true });
+        tab = tabbrowser.addTrustedTab(url,
+                                       { createLazyBrowser,
+                                         skipAnimation: true,
+                                         noInitialLabel: true,
+                                         userContextId,
+                                         skipBackgroundNotify: true,
+                                         bulkOrderedOpen: true });
 
         if (select) {
           let leftoverTab = tabbrowser.selectedTab;
           tabbrowser.selectedTab = tab;
           tabbrowser.removeTab(leftoverTab);
         }
       }
 
--- a/browser/components/sessionstore/content/aboutSessionRestore.js
+++ b/browser/components/sessionstore/content/aboutSessionRestore.js
@@ -259,17 +259,17 @@ function toggleRowChecked(aIx) {
   // we only disable the button when there's no cancel button.
   if (document.getElementById("errorCancel")) {
     document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked);
   }
 }
 
 function restoreSingleTab(aIx, aShifted) {
   var tabbrowser = getBrowserWindow().gBrowser;
-  var newTab = tabbrowser.addTab();
+  var newTab = tabbrowser.addWebTab();
   var item = gTreeData[aIx];
 
   var tabState = gStateObject.windows[item.parent.ix]
                              .tabs[aIx - gTreeData.indexOf(item.parent) - 1];
   // ensure tab would be visible on the tabstrip.
   tabState.hidden = false;
   SessionStore.setTabState(newTab, JSON.stringify(tabState));
 
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
@@ -35,23 +35,31 @@ var Tabs = {
       },
     },
 
     fourPinned: {
       selectors: ["#tabbrowser-tabs"],
       async applyConfig() {
         fiveTabsHelper();
         let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
-        let tab = browserWindow.gBrowser.addTab(PREFS_TAB);
+        let tab = browserWindow.gBrowser.addTab(PREFS_TAB, {
+          triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+        });
         browserWindow.gBrowser.pinTab(tab);
-        tab = browserWindow.gBrowser.addTab(CUST_TAB);
+        tab = browserWindow.gBrowser.addTab(CUST_TAB, {
+          triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+        });
         browserWindow.gBrowser.pinTab(tab);
-        tab = browserWindow.gBrowser.addTab("about:privatebrowsing");
+        tab = browserWindow.gBrowser.addTab("about:privatebrowsing", {
+          triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+        });
         browserWindow.gBrowser.pinTab(tab);
-        tab = browserWindow.gBrowser.addTab("about:home");
+        tab = browserWindow.gBrowser.addTab("about:home", {
+          triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+        });
         browserWindow.gBrowser.pinTab(tab);
         browserWindow.gBrowser.selectTabAtIndex(5);
         hoverTab(browserWindow.gBrowser.tabs[2]);
         // also hover the new tab button
         let newTabButton = browserWindow.document.getAnonymousElementByAttribute(browserWindow.
                            gBrowser.tabContainer, "anonid", "tabs-newtab-button");
         hoverTab(newTabButton);
         browserWindow.gBrowser.tabs[browserWindow.gBrowser.tabs.length - 1].
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -280,25 +280,25 @@ var gDevToolsBrowser = exports.gDevTools
   },
 
   /**
    * Open a tab on "about:debugging", optionally pre-select a given tab.
    */
    // Used by browser-sets.inc, command
   openAboutDebugging(gBrowser, hash) {
     const url = "about:debugging" + (hash ? "#" + hash : "");
-    gBrowser.selectedTab = gBrowser.addTab(url);
+    gBrowser.selectedTab = gBrowser.addTrustedTab(url);
   },
 
   /**
    * Open a tab to allow connects to a remote browser
    */
    // Used by browser-sets.inc, command
   openConnectScreen(gBrowser) {
-    gBrowser.selectedTab = gBrowser.addTab("chrome://devtools/content/framework/connect/connect.xhtml");
+    gBrowser.selectedTab = gBrowser.addTrustedTab("chrome://devtools/content/framework/connect/connect.xhtml");
   },
 
   /**
    * Open WebIDE
    */
    // Used by browser-sets.inc, command
    //         itself, webide widget
   openWebIDE() {
--- a/devtools/client/netmonitor/src/utils/firefox/open-request-in-tab.js
+++ b/devtools/client/netmonitor/src/utils/firefox/open-request-in-tab.js
@@ -28,18 +28,25 @@ function openRequestInTab(url, requestPo
 
   if (rawData && rawData.text) {
     const stringStream = getInputStreamFromString(rawData.text);
     postData = Cc["@mozilla.org/network/mime-input-stream;1"]
       .createInstance(Ci.nsIMIMEInputStream);
     postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
     postData.setData(stringStream);
   }
-
-  win.gBrowser.selectedTab = win.gBrowser.addTab(url, { postData });
+  const userContextId = win.gBrowser.contentPrincipal.userContextId;
+  win.gBrowser.selectedTab = win.gBrowser.addWebTab(url, {
+    // TODO this should be using the original request principal
+    triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
+      userContextId,
+    }),
+    userContextId,
+    postData,
+  });
 }
 
 function getInputStreamFromString(data) {
   const stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
     .createInstance(Ci.nsIStringInputStream);
   stringStream.data = data;
   return stringStream;
 }
--- a/devtools/client/performance-new/browser.js
+++ b/devtools/client/performance-new/browser.js
@@ -23,17 +23,21 @@ function receiveProfile(profile) {
   // of hosts.
   const win = Services.wm.getMostRecentWindow("navigator:browser");
   if (!win) {
     throw new Error("No browser window");
   }
   const browser = win.gBrowser;
   Services.focus.activeWindow = win;
 
-  const tab = browser.addTab("https://perf-html.io/from-addon");
+  const tab = browser.addWebTab("https://perf-html.io/from-addon", {
+    triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
+      userContextId: browser.contentPrincipal.userContextId,
+    })
+  });
   browser.selectedTab = tab;
   const mm = tab.linkedBrowser.messageManager;
   mm.loadFrameScript(
     "chrome://devtools/content/performance-new/frame-script.js",
     false
   );
   mm.sendAsyncMessage("devtools:perf-html-transfer-profile", profile);
 }
--- a/devtools/client/responsive.html/browser/swap.js
+++ b/devtools/client/responsive.html/browser/swap.js
@@ -2,16 +2,17 @@
  * 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/. */
 
 "use strict";
 
 const { Ci } = require("chrome");
 const { E10SUtils } = require("resource://gre/modules/E10SUtils.jsm");
 const { tunnelToInnerBrowser } = require("./tunnel");
+const Services = require("Services");
 
 function debug(msg) {
   // console.log(`RDM swap: ${msg}`);
 }
 
 /**
  * Swap page content from an existing tab into a new browser within a container
  * page.  Page state is preserved by using `swapFrameLoaders`, just like when
@@ -53,17 +54,20 @@ function swapToInnerBrowser({ tab, conta
 
   // A version of `gBrowser.addTab` that absorbs the `TabOpen` event.
   // The swap process uses a temporary tab, and there's no real need for others to hear
   // about it.  This hides the temporary tab from things like WebExtensions.
   const addTabSilently = (uri, options) => {
     browserWindow.addEventListener("TabOpen", event => {
       event.stopImmediatePropagation();
     }, { capture: true, once: true });
-    return gBrowser.addTab(uri, options);
+    options.triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal({
+      userContextId: options.userContextId,
+    });
+    return gBrowser.addWebTab(uri, options);
   };
 
   // A version of `gBrowser.swapBrowsersAndCloseOther` that absorbs the `TabClose` event.
   // The swap process uses a temporary tab, and there's no real need for others to hear
   // about it.  This hides the temporary tab from things like WebExtensions.
   const swapBrowsersAndCloseOtherSilently = (ourTab, otherTab) => {
     browserWindow.addEventListener("TabClose", event => {
       event.stopImmediatePropagation();
--- a/devtools/startup/devtools-startup.js
+++ b/devtools/startup/devtools-startup.js
@@ -686,17 +686,17 @@ DevToolsStartup.prototype = {
       params.push("keyid=" + keyId);
     }
 
     if (params.length > 0) {
       url += "?" + params.join("&");
     }
 
     // Set relatedToCurrent: true to open the tab next to the current one.
-    gBrowser.selectedTab = gBrowser.addTab(url, {relatedToCurrent: true});
+    gBrowser.selectedTab = gBrowser.addTrustedTab(url, {relatedToCurrent: true});
   },
 
   handleConsoleFlag: function(cmdLine) {
     const window = Services.wm.getMostRecentWindow("devtools:webconsole");
     if (!window) {
       const require = this.initDevTools("CommandLine");
       const { HUDService } = require("devtools/client/webconsole/hudservice");
       HUDService.toggleBrowserConsole().catch(console.error);
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -945,16 +945,17 @@ nsDocShell::LoadURI(nsIURI* aURI,
     else {
       triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
     }
   }
 
   uint32_t flags = 0;
 
   if (inheritPrincipal) {
+    MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(principalToInherit), "Should not inherit SystemPrincipal");
     flags |= INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL;
   }
 
   if (!sendReferrer) {
     flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER;
   }
 
   if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1182,23 +1182,26 @@ var BrowserApp = {
     let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
     let charset = "charset" in aParams ? aParams.charset : null;
 
     let tab = this.getTabForBrowser(aBrowser);
     if (tab) {
       if ("userRequested" in aParams) tab.userRequested = aParams.userRequested;
       tab.isSearch = ("isSearch" in aParams) ? aParams.isSearch : false;
     }
+    // Don't fall back to System here Bug 1474619
+    let triggeringPrincipal = "triggeringPrincipal" in aParams ? aParams.triggeringPrincipal : Services.scriptSecurityManager.getSystemPrincipal();
 
     try {
       aBrowser.loadURI(aURI, {
         flags,
         referrerURI,
         charset,
         postData,
+        triggeringPrincipal,
       });
     } catch(e) {
       if (tab) {
         let message = {
           type: "Content:LoadError",
           tabID: tab.id
         };
         GlobalEventDispatcher.sendRequest(message);
--- a/mobile/android/components/extensions/ext-tabs.js
+++ b/mobile/android/components/extensions/ext-tabs.js
@@ -242,29 +242,33 @@ this.tabs = class extends ExtensionAPI {
             return () => {
               windowTracker.removeListener("status", statusListener);
               windowTracker.removeListener("DOMTitleChanged", listener);
             };
           },
         }).api(),
 
         async create(createProperties) {
+          let principal = context.principal;
           let window = createProperties.windowId !== null ?
             windowTracker.getWindow(createProperties.windowId, context) :
             windowTracker.topWindow;
 
           let {BrowserApp} = window;
           let url;
 
           if (createProperties.url !== null) {
             url = context.uri.resolve(createProperties.url);
 
             if (!context.checkLoadURL(url, {dontReportErrors: true})) {
               return Promise.reject({message: `Illegal URL: ${url}`});
             }
+          } else {
+            // Falling back to system here as about:newtab requires it, however is safe.
+            principal = Services.scriptSecurityManager.getSystemPrincipal();
           }
 
           let options = {};
 
           let active = true;
           if (createProperties.active !== null) {
             active = createProperties.active;
           }
@@ -280,16 +284,17 @@ this.tabs = class extends ExtensionAPI {
             options.disallowInheritPrincipal = true;
           } else {
             options.triggeringPrincipal = context.principal;
           }
 
           options.parentId = BrowserApp.selectedTab.id;
 
           tabListener.initTabReady();
+          options.triggeringPrincipal = principal;
           let nativeTab = BrowserApp.addTab(url, options);
 
           if (createProperties.url) {
             tabListener.initializingTabs.add(nativeTab);
           }
 
           return tabManager.convert(nativeTab);
         },
--- a/toolkit/components/normandy/lib/Heartbeat.jsm
+++ b/toolkit/components/normandy/lib/Heartbeat.jsm
@@ -341,17 +341,20 @@ var Heartbeat = class {
     }
 
     // Open the engagement tab if we have a valid engagement URL.
     if (this.options.postAnswerUrl) {
       for (const key in engagementParams) {
         this.options.postAnswerUrl.searchParams.append(key, engagementParams[key]);
       }
       // Open the engagement URL in a new tab.
-      this.chromeWindow.gBrowser.selectedTab = this.chromeWindow.gBrowser.addTab(this.options.postAnswerUrl.toString());
+      let { gBrowser} = this.chromeWindow;
+      gBrowser.selectedTab = gBrowser.addWebTab(this.options.postAnswerUrl.toString(), {
+        triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
+      });
     }
 
     this.endTimerIfPresent("surveyEndTimer");
 
     this.engagementCloseTimer = setTimeout(() => this.close(), NOTIFICATION_TIME);
   }
 
   endTimerIfPresent(timerName) {