Bug 1284395, r=bz,mconley,baku
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Tue, 06 Sep 2016 14:19:45 +0100
changeset 315431 a45cfe898352daa445ebc702273c73c56b3ef3a1
parent 315430 cde7a9badcab194e25adf5fc48a991f6fa41fb87
child 315432 9792d02838ac3aac8f0cfa36063a05101eaec291
push id30750
push usercbook@mozilla.com
push dateWed, 28 Sep 2016 13:57:20 +0000
treeherdermozilla-central@b1d60f2f68c7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, mconley, baku
bugs1284395
milestone52.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 1284395, r=bz,mconley,baku MozReview-Commit-ID: 1nPyv7G3q7d
browser/base/content/browser.js
browser/base/content/content.js
browser/base/content/nsContextMenu.js
browser/base/content/tabbrowser.xml
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_modifiedclick_inherit_principal.js
browser/base/content/utilityOverlay.js
browser/modules/ContentClick.jsm
toolkit/content/browser-child.js
toolkit/content/widgets/browser.xml
toolkit/content/widgets/remote-browser.xml
toolkit/modules/BrowserUtils.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1123,31 +1123,35 @@ var gBrowserInit = {
           Cu.reportError(e);
         }
       }
       // window.arguments[2]: referrer (nsIURI | string)
       //                 [3]: postData (nsIInputStream)
       //                 [4]: allowThirdPartyFixup (bool)
       //                 [5]: referrerPolicy (int)
       //                 [6]: userContextId (int)
+      //                 [7]: originPrincipal (nsIPrincipal)
       else if (window.arguments.length >= 3) {
         let referrerURI = window.arguments[2];
         if (typeof(referrerURI) == "string") {
           try {
             referrerURI = makeURI(referrerURI);
           } catch (e) {
             referrerURI = null;
           }
         }
         let referrerPolicy = (window.arguments[5] != undefined ?
             window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
         let userContextId = (window.arguments[6] != undefined ?
             window.arguments[6] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID);
         loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
-                window.arguments[4] || false, referrerPolicy, userContextId);
+                window.arguments[4] || false, referrerPolicy, userContextId,
+                // pass the origin principal (if any) and force its use to create
+                // an initial about:blank viewer if present:
+                window.arguments[7], !!window.arguments[7]);
         window.focus();
       }
       // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
       // Such callers expect that window.arguments[0] is handled as a single URI.
       else {
         loadOneOrMoreURIs(uriToLoad);
       }
     }
@@ -2028,24 +2032,27 @@ function BrowserCloseTabOrWindow() {
 
 function BrowserTryToCloseWindow()
 {
   if (WindowIsClosing())
     window.close();     // WindowIsClosing does all the necessary checks
 }
 
 function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy,
-                 userContextId) {
+                 userContextId, originPrincipal, forceAboutBlankViewerInCurrent) {
   try {
     openLinkIn(uri, "current",
                { referrerURI: referrer,
                  referrerPolicy: referrerPolicy,
                  postData: postData,
                  allowThirdPartyFixup: allowThirdPartyFixup,
-                 userContextId: userContextId });
+                 userContextId: userContextId,
+                 originPrincipal,
+                 forceAboutBlankViewerInCurrent,
+               });
   } catch (e) {}
 }
 
 /**
  * Given a urlbar value, discerns between URIs, keywords and aliases.
  *
  * @param url
  *        The urlbar value.
@@ -5579,21 +5586,24 @@ function handleLinkClick(event, href, li
     let referrerAttrValue = Services.netUtils.parseAttributePolicyString(linkNode.
                             getAttribute("referrerpolicy"));
     if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
       referrerPolicy = referrerAttrValue;
     }
   }
 
   urlSecurityCheck(href, doc.nodePrincipal);
-  let params = { charset: doc.characterSet,
-                 allowMixedContent: persistAllowMixedContentInChildTab,
-                 referrerURI: referrerURI,
-                 referrerPolicy: referrerPolicy,
-                 noReferrer: BrowserUtils.linkHasNoReferrer(linkNode) };
+  let params = {
+    charset: doc.characterSet,
+    allowMixedContent: persistAllowMixedContentInChildTab,
+    referrerURI: referrerURI,
+    referrerPolicy: referrerPolicy,
+    noReferrer: BrowserUtils.linkHasNoReferrer(linkNode),
+    originPrincipal: doc.nodePrincipal,
+  };
 
   // The new tab/window must use the same userContextId
   if (doc.nodePrincipal.originAttributes.userContextId) {
     params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
   }
 
   openLinkIn(href, where, params);
   event.preventDefault();
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -502,16 +502,17 @@ var ClickEventHandler = {
       if (docShell.mixedContentChannel) {
         const sm = Services.scriptSecurityManager;
         try {
           let targetURI = BrowserUtils.makeURI(href);
           sm.checkSameOriginURI(docshell.mixedContentChannel.URI, targetURI, false);
           json.allowMixedContent = true;
         } catch (e) {}
       }
+      json.originPrincipal = ownerDoc.nodePrincipal;
 
       sendAsyncMessage("Content:Click", json);
       return;
     }
 
     // This might be middle mouse navigation.
     if (event.button == 1) {
       sendAsyncMessage("Content:Click", json);
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -960,16 +960,17 @@ nsContextMenu.prototype = {
 
   _isProprietaryDRM: function() {
     return this.target.isEncrypted && this.target.mediaKeys &&
            this.target.mediaKeys.keySystem != "org.w3.clearkey";
   },
 
   _openLinkInParameters : function (extra) {
     let params = { charset: gContextMenuContentData.charSet,
+                   originPrincipal: this.principal,
                    referrerURI: gContextMenuContentData.documentURIObject,
                    referrerPolicy: gContextMenuContentData.referrerPolicy,
                    noReferrer: this.linkHasNoReferrer };
     for (let p in extra) {
       params[p] = extra[p];
     }
 
     // If we want to change userContextId, we must be sure that we don't
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1495,16 +1495,17 @@
             var aFromExternal;
             var aRelatedToCurrent;
             var aAllowMixedContent;
             var aSkipAnimation;
             var aForceNotRemote;
             var aNoReferrer;
             var aUserContextId;
             var aRelatedBrowser;
+            var aOriginPrincipal;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aReferrerURI          = params.referrerURI;
               aReferrerPolicy       = params.referrerPolicy;
               aCharset              = params.charset;
               aPostData             = params.postData;
@@ -1513,16 +1514,17 @@
               aFromExternal         = params.fromExternal;
               aRelatedToCurrent     = params.relatedToCurrent;
               aAllowMixedContent    = params.allowMixedContent;
               aSkipAnimation        = params.skipAnimation;
               aForceNotRemote       = params.forceNotRemote;
               aNoReferrer           = params.noReferrer;
               aUserContextId        = params.userContextId;
               aRelatedBrowser       = params.relatedBrowser;
+              aOriginPrincipal      = params.originPrincipal;
             }
 
             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
             var owner = bgLoad ? null : this.selectedTab;
             var tab = this.addTab(aURI, {
                                   referrerURI: aReferrerURI,
                                   referrerPolicy: aReferrerPolicy,
@@ -1532,16 +1534,17 @@
                                   allowThirdPartyFixup: aAllowThirdPartyFixup,
                                   fromExternal: aFromExternal,
                                   relatedToCurrent: aRelatedToCurrent,
                                   skipAnimation: aSkipAnimation,
                                   allowMixedContent: aAllowMixedContent,
                                   forceNotRemote: aForceNotRemote,
                                   noReferrer: aNoReferrer,
                                   userContextId: aUserContextId,
+                                  originPrincipal: aOriginPrincipal,
                                   relatedBrowser: aRelatedBrowser });
             if (!bgLoad)
               this.selectedTab = tab;
 
             return tab;
          ]]>
         </body>
       </method>
@@ -2038,16 +2041,17 @@
             var aRelatedToCurrent;
             var aSkipAnimation;
             var aAllowMixedContent;
             var aForceNotRemote;
             var aNoReferrer;
             var aUserContextId;
             var aEventDetail;
             var aRelatedBrowser;
+            var aOriginPrincipal;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aReferrerURI          = params.referrerURI;
               aReferrerPolicy       = params.referrerPolicy;
               aCharset              = params.charset;
               aPostData             = params.postData;
@@ -2057,16 +2061,17 @@
               aRelatedToCurrent     = params.relatedToCurrent;
               aSkipAnimation        = params.skipAnimation;
               aAllowMixedContent    = params.allowMixedContent;
               aForceNotRemote       = params.forceNotRemote;
               aNoReferrer           = params.noReferrer;
               aUserContextId        = params.userContextId;
               aEventDetail          = params.eventDetail;
               aRelatedBrowser       = params.relatedBrowser;
+              aOriginPrincipal      = params.originPrincipal;
             }
 
             // if we're adding tabs, we're past interrupt mode, ditch the owner
             if (this.mCurrentTab.owner)
               this.mCurrentTab.owner = null;
 
             var t = document.createElementNS(NS_XUL, "tab");
 
@@ -2136,16 +2141,20 @@
 
             // Dispatch a new tab notification.  We do this once we're
             // entirely done, so that things are in a consistent state
             // even if the event listener opens or closes tabs.
             var detail = aEventDetail || {};
             var evt = new CustomEvent("TabOpen", { bubbles: true, detail });
             t.dispatchEvent(evt);
 
+            if (!usingPreloadedContent && aOriginPrincipal) {
+              b.createAboutBlankContentViewer(aOriginPrincipal);
+            }
+
             // If we didn't swap docShells with a preloaded browser
             // then let's just continue loading the page normally.
             if (!usingPreloadedContent && !uriIsAboutBlank) {
               // pretend the user typed this so it'll be available till
               // the document successfully loads
               if (aURI && gInitialPages.indexOf(aURI) == -1)
                 b.userTypedValue = aURI;
 
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -337,16 +337,17 @@ skip-if = toolkit == "windows" # Disable
 skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
 [browser_middleMouse_noJSPaste.js]
 subsuite = clipboard
 [browser_minimize.js]
 [browser_misused_characters_in_strings.js]
 [browser_mixed_content_cert_override.js]
 [browser_mixedcontent_securityflags.js]
 tags = mcb
+[browser_modifiedclick_inherit_principal.js]
 [browser_offlineQuotaNotification.js]
 skip-if = buildapp == 'mulet'
 [browser_feed_discovery.js]
 support-files = feed_discovery.html
 [browser_gZipOfflineChild.js]
 skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
 support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^
 [browser_overflowScroll.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js
@@ -0,0 +1,30 @@
+"use strict";
+
+const kURL =
+  "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+  "data:text/html,<a href=''>Middle-click me</a>";
+
+/*
+ * Check that when manually opening content JS links in new tabs/windows,
+ * we use the correct principal, and we don't clear the URL bar.
+ */
+add_task(function* () {
+ yield BrowserTestUtils.withNewTab(kURL, function* (browser) {
+   let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+   yield ContentTask.spawn(browser, null, function* () {
+     let a = content.document.createElement("a");
+     a.href = "javascript:document.write('spoof'); void(0);";
+     a.textContent = "Some link";
+     content.document.body.appendChild(a);
+   });
+   info("Added element");
+   yield BrowserTestUtils.synthesizeMouseAtCenter("a", {button: 1}, browser);
+   let newTab = yield newTabPromise;
+   is(newTab.linkedBrowser.contentPrincipal.origin, "http://example.com",
+      "Principal should be for example.com");
+   yield BrowserTestUtils.switchTab(gBrowser, newTab);
+   info(gURLBar.value);
+   isnot(gURLBar.value, "", "URL bar should not be empty.");
+   yield BrowserTestUtils.removeTab(newTab);
+ });
+});
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -217,16 +217,19 @@ function openLinkIn(url, where, params) 
   var aInitiatingDoc        = params.initiatingDoc;
   var aIsPrivate            = params.private;
   var aSkipTabAnimation     = params.skipTabAnimation;
   var aAllowPinnedTabHostChange = !!params.allowPinnedTabHostChange;
   var aNoReferrer           = params.noReferrer;
   var aAllowPopups          = !!params.allowPopups;
   var aUserContextId        = params.userContextId;
   var aIndicateErrorPageLoad = params.indicateErrorPageLoad;
+  var aPrincipal            = params.originPrincipal;
+  var aForceAboutBlankViewerInCurrent =
+      params.forceAboutBlankViewerInCurrent;
 
   if (where == "save") {
     // TODO(1073187): propagate referrerPolicy.
 
     // ContentClick.jsm passes isContentWindowPrivate for saveURL instead of passing a CPOW initiatingDoc
     if ("isContentWindowPrivate" in params) {
       saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, null, params.isContentWindowPrivate);
     }
@@ -285,16 +288,17 @@ function openLinkIn(url, where, params) 
 
     sa.AppendElement(wuri);
     sa.AppendElement(charset);
     sa.AppendElement(referrerURISupports);
     sa.AppendElement(aPostData);
     sa.AppendElement(allowThirdPartyFixupSupports);
     sa.AppendElement(referrerPolicySupports);
     sa.AppendElement(userContextIdSupports);
+    sa.AppendElement(aPrincipal);
 
     let features = "chrome,dialog=no,all";
     if (aIsPrivate) {
       features += ",private";
     }
 
     Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
     return;
@@ -352,16 +356,20 @@ function openLinkIn(url, where, params) 
 
     if (aAllowPopups) {
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
     }
     if (aIndicateErrorPageLoad) {
       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
     }
 
+    if (aForceAboutBlankViewerInCurrent) {
+      w.gBrowser.selectedBrowser.createAboutBlankContentViewer(aPrincipal);
+    }
+
     w.gBrowser.loadURIWithFlags(url, {
       flags: flags,
       referrerURI: aNoReferrer ? null : aReferrerURI,
       referrerPolicy: aReferrerPolicy,
       postData: aPostData,
       userContextId: aUserContextId
     });
     break;
@@ -375,17 +383,18 @@ function openLinkIn(url, where, params) 
       charset: aCharset,
       postData: aPostData,
       inBackground: loadInBackground,
       allowThirdPartyFixup: aAllowThirdPartyFixup,
       relatedToCurrent: aRelatedToCurrent,
       skipAnimation: aSkipTabAnimation,
       allowMixedContent: aAllowMixedContent,
       noReferrer: aNoReferrer,
-      userContextId: aUserContextId
+      userContextId: aUserContextId,
+      originPrincipal: aPrincipal,
     });
     break;
   }
 
   w.gBrowser.selectedBrowser.focus();
 
   if (!loadInBackground && w.isBlankPageURL(url)) {
     w.focusAndSelectUrlBar();
--- a/browser/modules/ContentClick.jsm
+++ b/browser/modules/ContentClick.jsm
@@ -72,22 +72,25 @@ var ContentClick = {
 
     // This part is based on handleLinkClick.
     var where = window.whereToOpenLink(json);
     if (where == "current")
       return;
 
     // Todo(903022): code for where == save
 
-    let params = { charset: browser.characterSet,
-                   referrerURI: browser.documentURI,
-                   referrerPolicy: json.referrerPolicy,
-                   noReferrer: json.noReferrer,
-                   allowMixedContent: json.allowMixedContent,
-                   isContentWindowPrivate: json.isContentWindowPrivate};
+    let params = {
+      charset: browser.characterSet,
+      referrerURI: browser.documentURI,
+      referrerPolicy: json.referrerPolicy,
+      noReferrer: json.noReferrer,
+      allowMixedContent: json.allowMixedContent,
+      isContentWindowPrivate: json.isContentWindowPrivate,
+      originPrincipal: json.originPrincipal,
+    };
 
     // The new tab/window must use the same userContextId.
     if (json.originAttributes.userContextId) {
       params.userContextId = json.originAttributes.userContextId;
     }
 
     window.openLinkIn(json.href, where, params);
   }
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cr = Components.results;
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/BrowserUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import("resource://gre/modules/RemoteAddonsChild.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
   "resource://gre/modules/PageThumbUtils.jsm");
 
@@ -560,16 +561,28 @@ addMessageListener("Browser:Thumbnail:Ge
     originalURL = channel.originalURI.spec;
   } catch (ex) {}
   sendAsyncMessage("Browser:Thumbnail:GetOriginalURL:Response", {
     channelError: channelError,
     originalURL: originalURL,
   });
 });
 
+/**
+ * Remote createAboutBlankContentViewer request handler.
+ */
+addMessageListener("Browser:CreateAboutBlank", function(aMessage) {
+  if (!content.document || content.document.documentURI != "about:blank") {
+    throw new Error("Can't create a content viewer unless on about:blank");
+  }
+  let principal = aMessage.data;
+  principal = BrowserUtils.principalWithMatchingOA(principal, content.document.nodePrincipal);
+  docShell.createAboutBlankContentViewer(principal);
+});
+
 // The AddonsChild needs to be rooted so that it stays alive as long as
 // the tab.
 var AddonsChild = RemoteAddonsChild.init(this);
 if (AddonsChild) {
   addEventListener("unload", () => {
     RemoteAddonsChild.uninit(AddonsChild);
   });
 }
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -1033,16 +1033,26 @@
       <method name="purgeSessionHistory">
         <body>
           <![CDATA[
             this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
           ]]>
         </body>
       </method>
 
+      <method name="createAboutBlankContentViewer">
+        <parameter name="aPrincipal"/>
+        <body>
+          <![CDATA[
+            let principal = BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
+            this.docShell.createAboutBlankContentViewer(principal);
+          ]]>
+        </body>
+      </method>
+
       <field name="_AUTOSCROLL_SNAP">10</field>
       <field name="_scrolling">false</field>
       <field name="_startX">null</field>
       <field name="_startY">null</field>
       <field name="_autoScrollPopup">null</field>
       <field name="_autoScrollNeedsCleanup">false</field>
 
       <method name="stopScroll">
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -556,16 +556,25 @@
                 throw ex;
               }
             }
             this._remoteWebNavigationImpl.canGoBack = false;
             this._remoteWebNavigationImpl.canGoForward = false;
           ]]>
         </body>
       </method>
+
+      <method name="createAboutBlankContentViewer">
+        <parameter name="aPrincipal"/>
+        <body>
+          <![CDATA[
+            this.messageManager.sendAsyncMessage("Browser:CreateAboutBlank", aPrincipal);
+          ]]>
+        </body>
+      </method>
     </implementation>
     <handlers>
       <handler event="dragstart">
       <![CDATA[
         // If we're a remote browser dealing with a dragstart, stop it
         // from propagating up, since our content process should be dealing
         // with the mouse movement.
         event.stopPropagation();
--- a/toolkit/modules/BrowserUtils.jsm
+++ b/toolkit/modules/BrowserUtils.jsm
@@ -78,16 +78,52 @@ this.BrowserUtils = {
       }
       catch (e2) { }
 
       throw "Load of " + aURL + principalStr + " denied.";
     }
   },
 
   /**
+   * Return or create a principal with the codebase of one, and the originAttributes
+   * of an existing principal (e.g. on a docshell, where the originAttributes ought
+   * not to change, that is, we should keep the userContextId, privateBrowsingId,
+   * etc. the same when changing the principal).
+   *
+   * @param principal
+   *        The principal whose codebase/null/system-ness we want.
+   * @param existingPrincipal
+   *        The principal whose originAttributes we want, usually the current
+   *        principal of a docshell.
+   * @return an nsIPrincipal that matches the codebase/null/system-ness of the first
+   *         param, and the originAttributes of the second.
+   */
+  principalWithMatchingOA(principal, existingPrincipal) {
+    // Don't care about system principals:
+    if (principal.isSystemPrincipal) {
+      return principal;
+    }
+
+    // If the originAttributes already match, just return the principal as-is.
+    if (existingPrincipal.originSuffix == principal.originSuffix) {
+      return principal;
+    }
+
+    let secMan = Services.scriptSecurityManager;
+    if (principal.isCodebasePrincipal) {
+      return secMan.createCodebasePrincipal(principal.URI, existingPrincipal.originAttributes);
+    }
+
+    if (principal.isNullPrincipal) {
+      return secMan.createNullPrincipal(existingPrincipal.originAttributes);
+    }
+    throw new Error("Can't change the originAttributes of an expanded principal!");
+  },
+
+  /**
    * Constructs a new URI, using nsIIOService.
    * @param aURL The URI spec.
    * @param aOriginCharset The charset of the URI.
    * @param aBaseURI Base URI to resolve aURL, or null.
    * @return an nsIURI object based on aURL.
    */
   makeURI: function(aURL, aOriginCharset, aBaseURI) {
     return Services.io.newURI(aURL, aOriginCharset, aBaseURI);