Bug 1503681 - rel=noopener implicit for target=_blank in anchor and area elements when no rel attribute is set, r=nika
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 28 Nov 2018 08:22:05 +0100
changeset 507714 3451e101fd67496305b366e5d60744317a60a54f
parent 507713 b526d511169a3ba8ea647d6de9183a62df0da1c8
child 507715 45e6f25036c57ad42140e749bb2927e5365d038c
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [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
@@ -13276,28 +13276,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
@@ -455,16 +455,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 true
+#else
+#define PREF_VALUE false
+#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