Anchor target=_blank implies rel=noopener
To mitigate "tab-napping" attacks, in which a new tab/window opened by
a victim context may navigate that opener context, the HTML standard
changed to specify that anchors that target _blank should behave as if
|rel="noopener"| is set. A page wishing to opt out of this behavior may
set |rel="opener"|.
Bug: 898942
Change-Id: Id34bbc480e96cb2cc8e922388a9a5bc4161b03b1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1630010
Reviewed-by: Julian Pastarmov <[email protected]>
Reviewed-by: John Abd-El-Malek <[email protected]>
Reviewed-by: Marijn Kruisselbrink <[email protected]>
Reviewed-by: Mike West <[email protected]>
Reviewed-by: Arthur Sonzogni <[email protected]>
Commit-Queue: Eric Lawrence [MSFT] <[email protected]>
Cr-Commit-Position: refs/heads/master@{#825022}
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index f140422..c7e9d29 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -2326,6 +2326,12 @@
blink::switches::kUserAgentClientHintDisable);
}
+ if (!local_state->GetBoolean(
+ policy::policy_prefs::kTargetBlankImpliesNoOpener)) {
+ command_line->AppendSwitch(
+ switches::kDisableTargetBlankImpliesNoOpener);
+ }
+
#if defined(OS_ANDROID)
// Communicating to content/ for BackForwardCache.
if (prefs->HasPrefPath(policy::policy_prefs::kBackForwardCacheEnabled) &&
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index b4bd7c6..90debc9 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -1300,6 +1300,9 @@
{ key::kLookalikeWarningAllowlistDomains,
prefs::kLookalikeWarningAllowlistDomains,
base::Value::Type::LIST },
+ { key::kTargetBlankImpliesNoOpener,
+ policy::policy_prefs::kTargetBlankImpliesNoOpener,
+ base::Value::Type::BOOLEAN },
#if defined(OS_ANDROID)
{ key::kTosDialogBehavior,
diff --git a/chrome/browser/policy/window_opener_policy_browsertest.cc b/chrome/browser/policy/window_opener_policy_browsertest.cc
new file mode 100644
index 0000000..3cbf1ac
--- /dev/null
+++ b/chrome/browser/policy/window_opener_policy_browsertest.cc
@@ -0,0 +1,71 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// TODO(crbug.com/898942): Remove this in Chrome 95.
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/policy/policy_test_utils.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace policy {
+
+class PolicyTestWindowOpener : public PolicyTest {
+ void SetUpInProcessBrowserTestFixture() override {
+ PolicyTest::SetUpInProcessBrowserTestFixture();
+ PolicyMap policies;
+ // Configure the policy to disable the new behavior.
+ SetPolicy(&policies, policy::key::kTargetBlankImpliesNoOpener,
+ base::Value(false));
+ provider_.UpdateChromePolicy(policies);
+ }
+};
+
+// Check that when the TargetBlankImpliesNoOpener policy is configured and set
+// to false, windows targeting _blank do not have their opener cleared.
+IN_PROC_BROWSER_TEST_F(PolicyTestWindowOpener, CheckWindowOpenerNonNull) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ PrefService* local_state = g_browser_process->local_state();
+ EXPECT_FALSE(local_state->GetBoolean(
+ policy::policy_prefs::kTargetBlankImpliesNoOpener));
+
+ GURL url(
+ "data:text/html,<a href='about:blank' target='_blank' "
+ "id='link'>popup</a>");
+ ui_test_utils::NavigateToURL(browser(), url);
+
+ ASSERT_EQ(browser()->tab_strip_model()->count(), 1);
+ content::WebContents* tab_1 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ ui_test_utils::TabAddedWaiter tab_Added_waiter(browser());
+ SimulateMouseClickOrTapElementWithId(tab_1, "link");
+ tab_Added_waiter.Wait();
+ content::WebContents* tab_2 =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_NE(tab_1, tab_2);
+
+ constexpr char kScript[] =
+ R"({ window.domAutomationController.send(window.opener === null); })";
+ content::ExecuteScriptAsync(tab_2, kScript);
+
+ content::DOMMessageQueue message_queue;
+ std::string message;
+ EXPECT_TRUE(message_queue.WaitForMessage(&message));
+ EXPECT_EQ("false", message);
+}
+
+} // namespace policy
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 43fe63e..b1817554 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -604,6 +604,8 @@
policy::policy_prefs::kIntensiveWakeUpThrottlingEnabled, false);
registry->RegisterBooleanPref(
policy::policy_prefs::kUserAgentClientHintsEnabled, true);
+ registry->RegisterBooleanPref(
+ policy::policy_prefs::kTargetBlankImpliesNoOpener, true);
#if defined(OS_ANDROID)
registry->RegisterBooleanPref(policy::policy_prefs::kBackForwardCacheEnabled,
true);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index afaefac..5c48ad6 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1238,6 +1238,7 @@
"../browser/policy/signed_exchange_browsertest.cc",
"../browser/policy/site_isolation_policy_browsertest.cc",
"../browser/policy/url_blacklist_policy_browsertest.cc",
+ "../browser/policy/window_opener_policy_browsertest.cc",
"../browser/portal/portal_browsertest.cc",
"../browser/portal/portal_recently_audible_browsertest.cc",
"../browser/predictors/loading_predictor_browsertest.cc",
diff --git a/chrome/test/data/chromedriver/page_test.html b/chrome/test/data/chromedriver/page_test.html
index e0f7a43d..ebfa0620 100644
--- a/chrome/test/data/chromedriver/page_test.html
+++ b/chrome/test/data/chromedriver/page_test.html
@@ -4,6 +4,6 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
-<a id="link" target="_blank" href="empty.html">Link to empty.html</a>
+<a id="link" rel="opener" target="_blank" href="empty.html">Link to empty.html</a>
</body>
</html>
diff --git a/chrome/test/data/extensions/api_test/protocol_handler/test_registration.js b/chrome/test/data/extensions/api_test/protocol_handler/test_registration.js
index 039137b..6b3d86c 100644
--- a/chrome/test/data/extensions/api_test/protocol_handler/test_registration.js
+++ b/chrome/test/data/extensions/api_test/protocol_handler/test_registration.js
@@ -26,6 +26,7 @@
const url = `${scheme}:path`;
const a = document.body.appendChild(document.createElement('a'));
a.href = url;
+ a.rel = 'opener';
a.target = '_blank';
return new Promise((resolve, reject) => {
window.addEventListener('message', function(event) {
diff --git a/chrome/test/data/extensions/api_test/webnavigation/targetBlank/a.html b/chrome/test/data/extensions/api_test/webnavigation/targetBlank/a.html
index 5c9cff1..7e1d526 100644
--- a/chrome/test/data/extensions/api_test/webnavigation/targetBlank/a.html
+++ b/chrome/test/data/extensions/api_test/webnavigation/targetBlank/a.html
@@ -6,6 +6,6 @@
</style>
</head>
<body>
-<a target="_blank" href="b.html">link</a>
+<a target="_blank" rel="opener" href="b.html">link</a>
</body>
</html>
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 999b798..d45afbf 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -3726,6 +3726,21 @@
"policy_pref_mapping_tests": { "scroll_to_text_fragment_enabled": {} }
},
+ "TargetBlankImpliesNoOpener": {
+ "os": ["win", "linux", "mac", "chromeos", "android"],
+ "policy_pref_mapping_test": [
+ {
+ "policies": { "TargetBlankImpliesNoOpener": false },
+ "prefs": {
+ "policy.target_blank_implies_noopener": {
+ "local_state": true,
+ "value": false
+ }
+ }
+ }
+ ]
+ },
+
"GloballyScopeHTTPAuthCacheEnabled": {
"os": ["win", "linux", "mac", "chromeos", "android"],
"test_policy": { "GloballyScopeHTTPAuthCacheEnabled": true },
diff --git a/chrome/test/data/popup_blocker/popup-in-href.html b/chrome/test/data/popup_blocker/popup-in-href.html
index 39223ab3..9fd8bbf5 100644
--- a/chrome/test/data/popup_blocker/popup-in-href.html
+++ b/chrome/test/data/popup_blocker/popup-in-href.html
@@ -1,6 +1,7 @@
<!doctype html>
<html>
<body>
-<a id="link" href="javascript:window.open('https://example.com');" target="_blank">open</a>.
+<a id="link" rel="opener" href="javascript:window.open('https://example.com');"
+ target="_blank">open</a>.
</body>
</html>
diff --git a/chrome/test/data/protocol_handler/service_workers/test_protocol_handler_and_service_workers.js b/chrome/test/data/protocol_handler/service_workers/test_protocol_handler_and_service_workers.js
index dc91a4c..a47b4290 100644
--- a/chrome/test/data/protocol_handler/service_workers/test_protocol_handler_and_service_workers.js
+++ b/chrome/test/data/protocol_handler/service_workers/test_protocol_handler_and_service_workers.js
@@ -24,6 +24,7 @@
async function handledByServiceWorker(url) {
const a = document.body.appendChild(document.createElement('a'));
a.href = url;
+ a.rel = 'opener';
a.target = '_blank';
let handled_by_service_worker;
await new Promise(resolve => {