Prevent universal link from opening native application in off the record.
Tapping a universal link while in off the record mode should not open a
native application because it shares the private state with the launched
app.
Bug: 861752
Change-Id: I578caf10e6ea0cf3978d1d7177ec01c71631e5ce
Reviewed-on: https://chromium-review.googlesource.com/c/1436489
Commit-Queue: Mike Dougherty <[email protected]>
Reviewed-by: Eugene But <[email protected]>
Reviewed-by: Peter Lee <[email protected]>
Cr-Commit-Position: refs/heads/master@{#632841}
diff --git a/ios/features.gni b/ios/features.gni
index 050b0f81..0c8e398e 100644
--- a/ios/features.gni
+++ b/ios/features.gni
@@ -7,4 +7,8 @@
# components/cronet/tools/cr_cronet.py as cronet requires specific
# gn args to build correctly).
is_cronet_build = false
+
+ # Controls whether universal links are blocked from opening native apps
+ # when the user is browsing in off the record mode.
+ block_universal_links_in_off_the_record_mode = true
}
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn
index 4f4bf51..cbe3ee8 100644
--- a/ios/web/BUILD.gn
+++ b/ios/web/BUILD.gn
@@ -490,6 +490,7 @@
"//ios/web/web_state:wk_web_view_security_util",
"//ios/web/web_state/js",
"//ios/web/web_state/js:script_util",
+ "//ios/web/web_state/ui:block_universal_links_buildflags",
"//ios/web/web_state/ui:crw_context_menu_controller",
"//ios/web/web_state/ui:crw_wk_script_message_router",
"//ios/web/web_state/ui:favicon_util",
@@ -513,6 +514,7 @@
"web_state/ui/html_element_fetch_request_unittest.mm",
"web_state/ui/web_view_js_utils_unittest.mm",
"web_state/ui/wk_back_forward_list_item_holder_unittest.mm",
+ "web_state/ui/wk_navigation_action_policy_util_unittest.mm",
"web_state/ui/wk_navigation_action_util_unittest.mm",
"web_state/ui/wk_web_view_configuration_provider_unittest.mm",
]
diff --git a/ios/web/features.mm b/ios/web/features.mm
index 6f55e193..f1210031 100644
--- a/ios/web/features.mm
+++ b/ios/web/features.mm
@@ -30,5 +30,8 @@
const base::Feature kHistoryClobberWorkaround{
"WKWebViewHistoryClobberWorkaround", base::FEATURE_ENABLED_BY_DEFAULT};
+
+const base::Feature kBlockUniversalLinksInOffTheRecordMode{
+ "BlockUniversalLinksInOffTheRecord", base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace features
} // namespace web
diff --git a/ios/web/public/features.h b/ios/web/public/features.h
index f66145a..1a5ab17 100644
--- a/ios/web/public/features.h
+++ b/ios/web/public/features.h
@@ -36,6 +36,10 @@
// (crbug.com/887497).
extern const base::Feature kHistoryClobberWorkaround;
+// Used to prevent native apps from being opened when a universal link is tapped
+// and the user is browsing in off the record mode.
+extern const base::Feature kBlockUniversalLinksInOffTheRecordMode;
+
} // namespace features
} // namespace web
diff --git a/ios/web/web_state/ui/BUILD.gn b/ios/web/web_state/ui/BUILD.gn
index 30341c0..d573a00 100644
--- a/ios/web/web_state/ui/BUILD.gn
+++ b/ios/web/web_state/ui/BUILD.gn
@@ -2,10 +2,18 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//build/buildflag_header.gni")
import("//ios/build/config.gni")
+import("//ios/features.gni")
+
+buildflag_header("block_universal_links_buildflags") {
+ header = "block_universal_links_buildflags.h"
+ flags = [ "BLOCK_UNIVERSAL_LINKS_IN_OFF_THE_RECORD_MODE=$block_universal_links_in_off_the_record_mode" ]
+}
source_set("ui") {
deps = [
+ ":block_universal_links_buildflags",
":crw_context_menu_controller",
":crw_web_view_navigation_proxy",
":crw_wk_script_message_router",
@@ -57,6 +65,8 @@
"web_kit_constants.h",
"wk_back_forward_list_item_holder.h",
"wk_back_forward_list_item_holder.mm",
+ "wk_navigation_action_policy_util.h",
+ "wk_navigation_action_policy_util.mm",
"wk_navigation_action_util.h",
"wk_navigation_action_util.mm",
]
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 9cbaa97..7d0d593 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -102,6 +102,7 @@
#import "ios/web/web_state/ui/favicon_util.h"
#include "ios/web/web_state/ui/web_kit_constants.h"
#import "ios/web/web_state/ui/wk_back_forward_list_item_holder.h"
+#import "ios/web/web_state/ui/wk_navigation_action_policy_util.h"
#import "ios/web/web_state/ui/wk_navigation_action_util.h"
#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
#import "ios/web/web_state/web_frame_impl.h"
@@ -4701,8 +4702,9 @@
}));
}
- decisionHandler(allowLoad ? WKNavigationActionPolicyAllow
- : WKNavigationActionPolicyCancel);
+ WKNavigationActionPolicy allowPolicy = web::GetAllowNavigationActionPolicy(
+ self.webState->GetBrowserState()->IsOffTheRecord());
+ decisionHandler(allowLoad ? allowPolicy : WKNavigationActionPolicyCancel);
}
- (void)webView:(WKWebView*)webView
diff --git a/ios/web/web_state/ui/crw_web_controller_unittest.mm b/ios/web/web_state/ui/crw_web_controller_unittest.mm
index 068c83b4..3c0adc5 100644
--- a/ios/web/web_state/ui/crw_web_controller_unittest.mm
+++ b/ios/web/web_state/ui/crw_web_controller_unittest.mm
@@ -26,6 +26,7 @@
#include "ios/web/public/features.h"
#include "ios/web/public/referrer.h"
#include "ios/web/public/test/fakes/fake_download_controller_delegate.h"
+#include "ios/web/public/test/fakes/test_browser_state.h"
#import "ios/web/public/test/fakes/test_native_content.h"
#import "ios/web/public/test/fakes/test_native_content_provider.h"
#import "ios/web/public/test/fakes/test_web_client.h"
@@ -45,8 +46,10 @@
#include "ios/web/test/test_url_constants.h"
#import "ios/web/test/web_test_with_web_controller.h"
#import "ios/web/test/wk_web_view_crash_utils.h"
+#include "ios/web/web_state/ui/block_universal_links_buildflags.h"
#import "ios/web/web_state/ui/crw_web_controller_container_view.h"
#import "ios/web/web_state/ui/web_view_js_utils.h"
+#import "ios/web/web_state/ui/wk_navigation_action_policy_util.h"
#import "ios/web/web_state/web_state_impl.h"
#import "ios/web/web_state/wk_web_view_security_util.h"
#import "net/base/mac/url_conversions.h"
@@ -782,6 +785,11 @@
});
return policy_match;
}
+
+ // Return an owned BrowserState in order to set off the record state.
+ BrowserState* GetBrowserState() override { return &browser_state_; }
+
+ TestBrowserState browser_state_;
};
// Tests that App specific URLs in iframes are allowed if the main frame is App
@@ -798,6 +806,44 @@
app_url_request, WKNavigationActionPolicyAllow));
}
+// Tests that URL is allowed in OffTheRecord mode when the
+// |kBlockUniversalLinksInOffTheRecordMode| feature is disabled.
+TEST_P(CRWWebControllerPolicyDeciderTest, AllowOffTheRecordNavigation) {
+ browser_state_.SetOffTheRecord(true);
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndDisableFeature(
+ web::features::kBlockUniversalLinksInOffTheRecordMode);
+
+ NSURL* url = [NSURL URLWithString:@(kTestURLString)];
+ NSMutableURLRequest* url_request = [NSMutableURLRequest requestWithURL:url];
+ url_request.mainDocumentURL = url;
+ EXPECT_TRUE(VerifyDecidePolicyForNavigationAction(
+ url_request, WKNavigationActionPolicyAllow));
+}
+
+// Tests that URL is allowed in OffTheRecord mode and that universal links are
+// blocked when the |kBlockUniversalLinksInOffTheRecordMode| feature is enabled
+// and the BLOCK_UNIVERSAL_LINKS_IN_OFF_THE_RECORD_MODE buildflag is set.
+TEST_P(CRWWebControllerPolicyDeciderTest,
+ AllowOffTheRecordNavigationBlockUniversalLinks) {
+ browser_state_.SetOffTheRecord(true);
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(
+ web::features::kBlockUniversalLinksInOffTheRecordMode);
+
+ NSURL* url = [NSURL URLWithString:@(kTestURLString)];
+ NSMutableURLRequest* url_request = [NSMutableURLRequest requestWithURL:url];
+ url_request.mainDocumentURL = url;
+
+ WKNavigationActionPolicy expected_policy = WKNavigationActionPolicyAllow;
+#if BUILDFLAG(BLOCK_UNIVERSAL_LINKS_IN_OFF_THE_RECORD_MODE)
+ expected_policy = kNavigationActionPolicyAllowAndBlockUniversalLinks;
+#endif // BUILDFLAG(BLOCK_UNIVERSAL_LINKS_IN_OFF_THE_RECORD_MODE)
+
+ EXPECT_TRUE(
+ VerifyDecidePolicyForNavigationAction(url_request, expected_policy));
+}
+
// Tests that App specific URLs in iframes are not allowed if the main frame is
// not App specific URL.
TEST_P(CRWWebControllerPolicyDeciderTest,
diff --git a/ios/web/web_state/ui/wk_navigation_action_policy_util.h b/ios/web/web_state/ui/wk_navigation_action_policy_util.h
new file mode 100644
index 0000000..db2510a4
--- /dev/null
+++ b/ios/web/web_state/ui/wk_navigation_action_policy_util.h
@@ -0,0 +1,23 @@
+// Copyright 2019 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.
+
+#ifndef IOS_WEB_WEB_STATE_UI_WK_NAVIGATION_ACTION_POLICY_UTIL_H_
+#define IOS_WEB_WEB_STATE_UI_WK_NAVIGATION_ACTION_POLICY_UTIL_H_
+
+#import <WebKit/WebKit.h>
+
+namespace web {
+
+// Navigation action policy which allows the load but prevents opening universal
+// links in native applications.
+extern const WKNavigationActionPolicy
+ kNavigationActionPolicyAllowAndBlockUniversalLinks;
+
+// Returns the WKNavigationActionPolicy for allowing navigations given the
+// |off_the_record| state for the associated BrowserState.
+WKNavigationActionPolicy GetAllowNavigationActionPolicy(bool off_the_record);
+
+} // namespace web
+
+#endif // IOS_WEB_WEB_STATE_UI_WK_NAVIGATION_ACTION_POLICY_UTIL_H_
diff --git a/ios/web/web_state/ui/wk_navigation_action_policy_util.mm b/ios/web/web_state/ui/wk_navigation_action_policy_util.mm
new file mode 100644
index 0000000..4ec2d5bf
--- /dev/null
+++ b/ios/web/web_state/ui/wk_navigation_action_policy_util.mm
@@ -0,0 +1,38 @@
+// Copyright 2019 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.
+
+#import "ios/web/web_state/ui/wk_navigation_action_policy_util.h"
+
+#include "base/feature_list.h"
+#include "ios/web/public/features.h"
+#include "ios/web/web_state/ui/block_universal_links_buildflags.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace web {
+
+const WKNavigationActionPolicy
+ kNavigationActionPolicyAllowAndBlockUniversalLinks =
+ static_cast<WKNavigationActionPolicy>(WKNavigationActionPolicyAllow +
+ 2);
+
+WKNavigationActionPolicy GetAllowNavigationActionPolicy(bool off_the_record) {
+ // When both the |block_universal_links_in_off_the_record| gn arg and the
+ // |web::features::kBlockUniversalLinksInOffTheRecordMode| feature flag are
+ // enabled, the returned value will block opening native applications if
+ // |off_the_record| is true to prevent sharing off the record state.
+#if BUILDFLAG(BLOCK_UNIVERSAL_LINKS_IN_OFF_THE_RECORD_MODE)
+ bool block_universal_links_enabled = base::FeatureList::IsEnabled(
+ web::features::kBlockUniversalLinksInOffTheRecordMode);
+ if (off_the_record && block_universal_links_enabled) {
+ return kNavigationActionPolicyAllowAndBlockUniversalLinks;
+ }
+#endif // BUILDFLAG(BLOCK_UNIVERSAL_LINKS_IN_OFF_THE_RECORD_MODE)
+
+ return WKNavigationActionPolicyAllow;
+}
+
+} // namespace web
diff --git a/ios/web/web_state/ui/wk_navigation_action_policy_util_unittest.mm b/ios/web/web_state/ui/wk_navigation_action_policy_util_unittest.mm
new file mode 100644
index 0000000..f59c749
--- /dev/null
+++ b/ios/web/web_state/ui/wk_navigation_action_policy_util_unittest.mm
@@ -0,0 +1,56 @@
+// Copyright 2019 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.
+
+#import "ios/web/web_state/ui/wk_navigation_action_policy_util.h"
+
+#import <WebKit/WebKit.h>
+
+#include "base/test/scoped_feature_list.h"
+#include "ios/web/public/features.h"
+#include "ios/web/web_state/ui/block_universal_links_buildflags.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace web {
+
+using WKNavigationActionPolicyUtilTest = PlatformTest;
+
+// Tests GetAllowNavigationActionPolicy for normal browsing mode.
+TEST_F(WKNavigationActionPolicyUtilTest, AllowNavigationActionPolicy) {
+ WKNavigationActionPolicy policy = GetAllowNavigationActionPolicy(false);
+ EXPECT_EQ(WKNavigationActionPolicyAllow, policy);
+}
+
+// Tests GetAllowNavigationActionPolicy for off the record browsing mode with
+// the |kBlockUniversalLinksInOffTheRecordMode| feature disabled.
+TEST_F(WKNavigationActionPolicyUtilTest,
+ AllowNavigationActionPolicyForOffTheRecord) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndDisableFeature(
+ web::features::kBlockUniversalLinksInOffTheRecordMode);
+
+ WKNavigationActionPolicy policy = GetAllowNavigationActionPolicy(true);
+ EXPECT_EQ(WKNavigationActionPolicyAllow, policy);
+}
+
+// Tests GetAllowNavigationActionPolicy for off the record browsing mode with
+// the |kBlockUniversalLinksInOffTheRecordMode| feature enabled.
+TEST_F(WKNavigationActionPolicyUtilTest, BlockUniversalLinksForOffTheRecord) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(
+ web::features::kBlockUniversalLinksInOffTheRecordMode);
+
+ WKNavigationActionPolicy expected_policy = WKNavigationActionPolicyAllow;
+#if BUILDFLAG(BLOCK_UNIVERSAL_LINKS_IN_OFF_THE_RECORD_MODE)
+ expected_policy = kNavigationActionPolicyAllowAndBlockUniversalLinks;
+#endif // BUILDFLAG(BLOCK_UNIVERSAL_LINKS_IN_OFF_THE_RECORD_MODE)
+
+ EXPECT_EQ(expected_policy, GetAllowNavigationActionPolicy(true));
+}
+
+} // namespace web