Bug 1031264 - make opening links in new {tabs,windows} honor rel="noreferrer"; r=mconley
authorNathan Froyd <froydnj@mozilla.com>
Wed, 10 Dec 2014 12:42:18 -0500
changeset 220152 430a23c7454258f4b1d1042b496481725716eba5
parent 220151 9bfe7b78b4c5f3953b3e6e4930d6c77963d6d32e
child 220153 8277e519297292cf4dff0ced0807a737676a32b7
push id53029
push usernfroyd@mozilla.com
push dateWed, 17 Dec 2014 20:56:11 +0000
treeherdermozilla-inbound@430a23c74542 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1031264
milestone37.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 1031264 - make opening links in new {tabs,windows} honor rel="noreferrer"; r=mconley
browser/base/content/browser.js
browser/base/content/content.js
browser/base/content/nsContextMenu.js
browser/modules/ContentClick.jsm
toolkit/modules/BrowserUtils.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5679,19 +5679,21 @@ function handleLinkClick(event, href, li
       var targetURI = makeURI(href);
       sm.checkSameOriginURI(referrerURI, targetURI, false);
       persistAllowMixedContentInChildTab = true;
     }
     catch (e) { }
   }
 
   urlSecurityCheck(href, doc.nodePrincipal);
-  openLinkIn(href, where, { referrerURI: referrerURI,
-                            charset: doc.characterSet,
-                            allowMixedContent: persistAllowMixedContentInChildTab });
+  let params = { charset: doc.characterSet,
+                 allowMixedContent: persistAllowMixedContentInChildTab };
+  if (!BrowserUtils.linkHasNoReferrer(linkNode))
+    params.referrerURI = referrerURI;
+  openLinkIn(href, where, params);
   event.preventDefault();
   return true;
 }
 
 function middleMousePaste(event) {
   let clipboard = readFromClipboard();
   if (!clipboard)
     return;
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -574,16 +574,17 @@ let ClickEventHandler = {
         json.title = node.getAttribute("title");
         if (event.button == 0 && !event.ctrlKey && !event.shiftKey &&
             !event.altKey && !event.metaKey) {
           json.bookmark = node.getAttribute("rel") == "sidebar";
           if (json.bookmark) {
             event.preventDefault(); // Need to prevent the pageload.
           }
         }
+        json.noReferrer = BrowserUtils.linkHasNoReferrer(node)
       }
 
       sendAsyncMessage("Content:Click", json);
       return;
     }
 
     // This might be middle mouse navigation.
     if (event.button == 1) {
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1,15 +1,16 @@
 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
 # 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/.
 
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
+Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
 
 var gContextMenuContentData = null;
 
 function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
   this.initMenu(aXulMenu, aIsShift);
 }
 
@@ -852,33 +853,38 @@ nsContextMenu.prototype = {
     }
     if (!editable) {
       return false;
     }
     // Otherwise make sure that nothing in the parent chain disables spellchecking
     return aNode.spellcheck;
   },
 
+  _openLinkInParameters : function (doc, extra) {
+    let params = { charset: doc.characterSet };
+    if (!BrowserUtils.linkHasNoReferrer(this.link))
+      params.referrerURI = document.documentURIObject;
+    for (let p in extra)
+      params[p] = extra[p];
+    return params;
+  },
+
   // Open linked-to URL in a new window.
   openLink : function () {
     var doc = this.target.ownerDocument;
     urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
-    openLinkIn(this.linkURL, "window",
-               { charset: doc.characterSet,
-                 referrerURI: doc.documentURIObject });
+    openLinkIn(this.linkURL, "window", this._openLinkInParameters(doc));
   },
 
   // Open linked-to URL in a new private window.
   openLinkInPrivateWindow : function () {
     var doc = this.target.ownerDocument;
     urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
     openLinkIn(this.linkURL, "window",
-               { charset: doc.characterSet,
-                 referrerURI: doc.documentURIObject,
-                 private: true });
+               this._openLinkInParameters(doc, { private: true }));
   },
 
   // Open linked-to URL in a new tab.
   openLinkInTab: function() {
     var doc = this.target.ownerDocument;
     urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
     var referrerURI = doc.documentURIObject;
 
@@ -892,29 +898,27 @@ nsContextMenu.prototype = {
       try {
         var targetURI = this.linkURI;
         sm.checkSameOriginURI(referrerURI, targetURI, false);
         persistAllowMixedContentInChildTab = true;
       }
       catch (e) { }
     }
 
-    openLinkIn(this.linkURL, "tab",
-               { charset: doc.characterSet,
-                 referrerURI: referrerURI,
-                 allowMixedContent: persistAllowMixedContentInChildTab });
+    let params = this._openLinkInParameters(doc, {
+      allowMixedContent: persistAllowMixedContentInChildTab,
+    });
+    openLinkIn(this.linkURL, "tab", params);
   },
 
   // open URL in current tab
   openLinkInCurrent: function() {
     var doc = this.target.ownerDocument;
     urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
-    openLinkIn(this.linkURL, "current",
-               { charset: doc.characterSet,
-                 referrerURI: doc.documentURIObject });
+    openLinkIn(this.linkURL, "current", this._openLinkInParameters(doc));
   },
 
   // Open frame in a new tab.
   openFrameInTab: function() {
     var doc = this.target.ownerDocument;
     var frameURL = doc.location.href;
     var referrer = doc.referrer;
     openLinkIn(frameURL, "tab",
--- a/browser/modules/ContentClick.jsm
+++ b/browser/modules/ContentClick.jsm
@@ -1,8 +1,9 @@
+/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
@@ -62,18 +63,20 @@ let ContentClick = {
 
     // This part is based on handleLinkClick.
     var where = window.whereToOpenLink(json);
     if (where == "current")
       return false;
 
     // Todo(903022): code for where == save
 
-    window.openLinkIn(json.href, where, { referrerURI: browser.documentURI,
-                                          charset: browser.characterSet });
+    let params = { charset: browser.characterSet };
+    if (!json.noReferrer)
+      params.referrerURI = browser.documentURI;
+    window.openLinkIn(json.href, where, params);
 
     // Mark the page as a user followed link.  This is done so that history can
     // distinguish automatic embed visits from user activated ones.  For example
     // pages loaded in frames are embed visits and lost with the session, while
     // visits across frames should be preserved.
     try {
       if (!PrivateBrowsingUtils.isWindowPrivate(window))
         PlacesUIUtils.markPageAsFollowedLink(href);
--- a/toolkit/modules/BrowserUtils.jsm
+++ b/toolkit/modules/BrowserUtils.jsm
@@ -1,8 +1,9 @@
+/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "BrowserUtils" ];
 
@@ -199,9 +200,29 @@ this.BrowserUtils = {
     // Do this by first stripping the numbers, etc. off the end, and then
     // removing "Plugin" (and then trimming to get rid of any whitespace).
     // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
     let newName = aName.replace(/\(.*?\)/g, "").
                         replace(/[\s\d\.\-\_\(\)]+$/, "").
                         replace(/\bplug-?in\b/i, "").trim();
     return newName;
   },
+
+  /**
+   * Return true if linkNode has a rel="noreferrer" attribute.
+   *
+   * @param linkNode The <a> element, or null.
+   * @return a boolean indicating if linkNode has a rel="noreferrer" attribute.
+   */
+  linkHasNoReferrer: function (linkNode) {
+    if (!linkNode)
+      return false;
+
+    let rel = linkNode.getAttribute("rel");
+    if (!rel)
+      return false;
+
+    // The HTML spec says that rel should be split on spaces before looking
+    // for particular rel values.
+    let values = rel.split(/[ \t\r\n\f]/);
+    return values.indexOf('noreferrer') != -1;
+  },
 };