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