Bug 1503681 - rel=noopener implicit for target=_blank in anchor and area elements when no rel attribute is set, r=nika
☠☠ backed out by 70abf246cac8 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 27 Nov 2018 09:31:47 +0100
changeset 448270 99ae47766ba9732664c0f828126beadb37220be4
parent 448269 434b3eacae093f3a29d032cd5e964d39fc0175b6
child 448271 c7483919d6be908019b331c09e8ed09394b6b4ae
push id35108
push userrmaries@mozilla.com
push dateTue, 27 Nov 2018 17:33:10 +0000
treeherdermozilla-central@f83dcdf69769 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika
bugs1503681
milestone65.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 1503681 - rel=noopener implicit for target=_blank in anchor and area elements when no rel attribute is set, r=nika In case anchor and area elements have target=_blank and no rel=opener/noopener, this patch makes so that rel=noopener is implied. This feature is behind pref 'dom_targetBlankNoOpener_enabled'. See: https://github.com/whatwg/html/issues/4078
docshell/base/nsDocShell.cpp
dom/html/test/browser.ini
dom/html/test/browser_targetBlankNoOpener.js
dom/html/test/empty.html
dom/html/test/image_yellow.png
modules/libpref/init/StaticPrefList.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -13284,28 +13284,53 @@ nsDocShell::OnLinkClickSync(nsIContent* 
   }
 
   uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
   if (IsElementAnchorOrArea(aContent)) {
     MOZ_ASSERT(aContent->IsHTMLElement());
     nsAutoString referrer;
     aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, referrer);
     nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(referrer);
+
+    bool targetBlank = aTargetSpec.LowerCaseEqualsLiteral("_blank");
+    bool explicitOpenerSet = false;
+
+    // The opener behaviour follows a hierarchy, such that if a higher priority
+    // behaviour is specified, it always takes priority. That priority is
+    // currently: norefrerer > noopener > opener > default
+
     while (tok.hasMoreTokens()) {
       const nsAString& token = tok.nextToken();
       if (token.LowerCaseEqualsLiteral("noreferrer")) {
         flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER |
                  INTERNAL_LOAD_FLAGS_NO_OPENER;
-        // We now have all the flags we could possibly have, so just stop.
+        // noreferrer cannot be overwritten by a 'rel=opener'.
+        explicitOpenerSet = true;
         break;
       }
+
       if (token.LowerCaseEqualsLiteral("noopener")) {
         flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
-      }
-    }
+        explicitOpenerSet = true;
+      }
+
+      if (targetBlank &&
+          StaticPrefs::dom_targetBlankNoOpener_enabled() &&
+          token.LowerCaseEqualsLiteral("opener") &&
+          !explicitOpenerSet) {
+        explicitOpenerSet = true;
+      }
+    }
+
+    if (targetBlank &&
+        StaticPrefs::dom_targetBlankNoOpener_enabled() &&
+        !explicitOpenerSet) {
+      flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+    }
+
     if (aNoOpenerImplied) {
       flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
     }
   }
 
   // Get the owner document of the link that was clicked, this will be
   // the document that the link is in, or the last document that the
   // link was in. From that document, we'll get the URI to use as the
--- a/dom/html/test/browser.ini
+++ b/dom/html/test/browser.ini
@@ -32,8 +32,12 @@ tags = fullscreen
 [browser_fullscreen-newtab.js]
 tags = fullscreen
 support-files = file_fullscreen-newtab.html
 skip-if = os == 'mac' # bug 1494843
 [browser_submission_flush.js]
 [browser_refresh_wyciwyg_url.js]
 support-files =
   file_refresh_wyciwyg_url.html
+[browser_targetBlankNoOpener.js]
+support-files =
+  empty.html
+  image_yellow.png
new file mode 100644
--- /dev/null
+++ b/dom/html/test/browser_targetBlankNoOpener.js
@@ -0,0 +1,83 @@
+const TEST_URL = "http://mochi.test:8888/browser/dom/html/test/empty.html";
+
+async function checkOpener(browser, elm, name, rel) {
+  let p = BrowserTestUtils.waitForNewTab(gBrowser, null, true, true);
+
+  await ContentTask.spawn(browser, {url: TEST_URL, name, rel, elm }, async obj => {
+    let element;
+
+    if (obj.elm == "anchor") {
+      element = content.document.createElement("a");
+      content.document.body.appendChild(element);
+      element.appendChild(content.document.createTextNode(obj.name));
+    } else {
+      let img = content.document.createElement('img');
+      img.src = "image_yellow.png";
+      content.document.body.appendChild(img);
+
+      element = content.document.createElement("area");
+      img.appendChild(element);
+
+      element.setAttribute("shape", "rect");
+      element.setAttribute("coords", "0,0,100,100");
+    }
+
+    element.setAttribute("target", "_blank");
+    element.setAttribute("href", obj.url);
+
+    if (obj.rel) {
+      element.setAttribute("rel", obj.rel);
+    }
+
+    element.click();
+  });
+
+  let newTab = await p;
+  let newBrowser = gBrowser.getBrowserForTab(newTab);
+
+  let hasOpener = await ContentTask.spawn(newTab.linkedBrowser, null, _ => !!content.window.opener);
+
+  BrowserTestUtils.removeTab(newTab);
+  return hasOpener;
+}
+
+async function runTests(browser, elm) {
+  info("Creating an " + elm + " with target=_blank rel=opener");
+  ok(!!(await checkOpener(browser, elm, "rel=opener", "opener")), "We want the opener with rel=opener");
+
+  info("Creating an " + elm + " with target=_blank rel=noopener");
+  ok(!(await checkOpener(browser, elm, "rel=noopener", "noopener")), "We don't want the opener with rel=noopener");
+
+  info("Creating an " + elm + " with target=_blank");
+  ok(!(await checkOpener(browser, elm, "no rel", null)), "We don't want the opener with no rel is passed");
+
+  info("Creating an " + elm + " with target=_blank rel='noopener opener'");
+  ok(!(await checkOpener(browser, elm, "rel=noopener+opener", "noopener opener")), "noopener wins with rel=noopener+opener");
+
+  info("Creating an " + elm + " with target=_blank rel='noreferrer opener'");
+  ok(!(await checkOpener(browser, elm, "noreferrer wins", "noreferrer opener")), "We don't want the opener with rel=noreferrer+opener");
+
+  info("Creating an " + elm + " with target=_blank rel='opener noreferrer'");
+  ok(!(await checkOpener(browser, elm, "noreferrer wins again", "noreferrer opener")), "We don't want the opener with rel=opener+noreferrer");
+}
+
+add_task(async _ => {
+  await SpecialPowers.flushPrefEnv();
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["dom.block_multiple_popups", false],
+    ["dom.disable_open_during_load", true],
+    ["dom.targetBlankNoOpener.enabled", true],
+  ]});
+
+  let tab = BrowserTestUtils.addTab(gBrowser, TEST_URL);
+  gBrowser.selectedTab = tab;
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  await BrowserTestUtils.browserLoaded(browser);
+
+  await runTests(browser, 'anchor');
+  await runTests(browser, 'area');
+
+  info("Removing the tab");
+  BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/dom/html/test/empty.html
@@ -0,0 +1,1 @@
+<html><body></body></html>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..51e8aaf38c1b3a105fc6038c93b5648a4bf9efd4
GIT binary patch
literal 95
zc%17D@N?(olHy`uVBq!ia0vp^DL`z*$P6SW{C@KnNHGWagt-3y&(L-3iz|>T?&;zf
qQW5v|03$Du=V0(R_;h0NA~Qy&Jq(P_g*_*Mf()LnelF{r5}E*PXBuY!
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -447,16 +447,30 @@ VARCACHE_PREF(
 
 // Block multiple window.open() per single event.
 VARCACHE_PREF(
   "dom.block_multiple_popups",
    dom_block_multiple_popups,
   bool, true
 )
 
+// For area and anchor elements with target=_blank and no rel set to
+// opener/noopener, this pref sets noopener by default.
+#ifdef EARLY_BETA_OR_EARLIER
+#define PREF_VALUE false
+#else
+#define PREF_VALUE true
+#endif
+VARCACHE_PREF(
+  "dom.targetBlankNoOpener.enabled",
+   dom_targetBlankNoOpener_enabled,
+  bool, PREF_VALUE
+)
+#undef PREF_VALUE
+
 //---------------------------------------------------------------------------
 // Clear-Site-Data prefs
 //---------------------------------------------------------------------------
 
 VARCACHE_PREF(
   "dom.clearSiteData.enabled",
    dom_clearSiteData_enabled,
   bool, true