WebView: add hid permission request

This CL adds hid permission to WebView to enable WebViews sending
permission requests for HID to the embedding Chrome App.

Design doc: http://goto.google.com/webhid-in-webview

BUG=b:281855555

Change-Id: I555542c2191e558d14083efd77320b9dafc9e130
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5136796
Commit-Queue: Viktoriia Kovalova <vkovalova@google.com>
Reviewed-by: Juliet Lévesque <julietlevesque@google.com>
Reviewed-by: Kevin McNee <mcnee@chromium.org>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Reviewed-by: Finnur Thorarinsson <finnur@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1246432}
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 4c3248c..506bfab5 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -22,6 +22,7 @@
 #include "base/test/bind.h"
 #include "base/test/run_until.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_restrictions.h"
@@ -3151,6 +3152,56 @@
              NEEDS_TEST_SERVER);
 }
 
+class WebHidWebViewTest : public WebViewTest {
+ public:
+  WebHidWebViewTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        extensions_features::kEnableWebHidInWebView);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebHidWebViewTest,
+                       PermissionsAPIEmbedderHasAccessAllowHid) {
+  TestHelper("testAllowHid",
+             "web_view/permissions_test/embedder_has_permission",
+             NEEDS_TEST_SERVER);
+
+  auto* guest = GetGuestViewManager()->GetLastGuestViewCreated();
+  auto* web_view = extensions::WebViewGuest::FromGuestViewBase(guest);
+  ASSERT_TRUE(web_view);
+
+  base::test::TestFuture<bool> allowed_future;
+  // TODO(b/281855555): Replace this C++ call with an actual call to WebHID from
+  // webview script, when WebHID support is fully implemented.
+  web_view->web_view_permission_helper()->RequestHidPermission(
+      guest->GetGuestMainFrame()->GetLastCommittedURL(),
+      allowed_future.GetCallback());
+
+  ASSERT_TRUE(allowed_future.Take());
+}
+
+IN_PROC_BROWSER_TEST_F(WebHidWebViewTest,
+                       PermissionsAPIEmbedderHasAccessDenyHid) {
+  TestHelper("testDenyHid", "web_view/permissions_test/embedder_has_permission",
+             NEEDS_TEST_SERVER);
+
+  auto* guest = GetGuestViewManager()->GetLastGuestViewCreated();
+  auto* web_view = extensions::WebViewGuest::FromGuestViewBase(guest);
+  ASSERT_TRUE(web_view);
+
+  base::test::TestFuture<bool> allowed_future;
+  // TODO(b/281855555): Replace this C++ call with an actual call to WebHID from
+  // webview script, when WebHID support is fully implemented.
+  web_view->web_view_permission_helper()->RequestHidPermission(
+      guest->GetGuestMainFrame()->GetLastCommittedURL(),
+      allowed_future.GetCallback());
+
+  ASSERT_FALSE(allowed_future.Take());
+}
+
 IN_PROC_BROWSER_TEST_F(WebViewTest,
                        PermissionsAPIEmbedderHasNoAccessAllowGeolocation) {
   TestHelper("testAllowGeolocation",
diff --git a/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.cc b/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.cc
index 2870a0a..12890d2 100644
--- a/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.cc
+++ b/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.cc
@@ -194,6 +194,28 @@
           std::move(callback));
 }
 
+void ChromeWebViewPermissionHelperDelegate::RequestHidPermission(
+    const GURL& requesting_frame_url,
+    base::OnceCallback<void(bool)> callback) {
+  auto request_info =
+      base::Value::Dict().Set(guest_view::kUrl, requesting_frame_url.spec());
+
+  WebViewPermissionHelper::PermissionResponseCallback permission_callback =
+      base::BindOnce(
+          &ChromeWebViewPermissionHelperDelegate::OnHidPermissionResponse,
+          weak_factory_.GetWeakPtr(), std::move(callback));
+  web_view_permission_helper()->RequestPermission(
+      WEB_VIEW_PERMISSION_TYPE_HID, std::move(request_info),
+      std::move(permission_callback), false /* allowed_by_default */);
+}
+
+void ChromeWebViewPermissionHelperDelegate::OnHidPermissionResponse(
+    base::OnceCallback<void(bool)> callback,
+    bool allow,
+    const std::string& user_input) {
+  std::move(callback).Run(allow && web_view_guest()->attached());
+}
+
 void ChromeWebViewPermissionHelperDelegate::RequestFileSystemPermission(
     const GURL& url,
     bool allowed_by_default,
diff --git a/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.h b/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.h
index 30f968b..5db1cd5 100644
--- a/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.h
+++ b/chrome/browser/guest_view/web_view/chrome_web_view_permission_helper_delegate.h
@@ -55,6 +55,8 @@
       const GURL& requesting_frame,
       bool user_gesture,
       base::OnceCallback<void(bool)> callback) override;
+  void RequestHidPermission(const GURL& requesting_frame_url,
+                            base::OnceCallback<void(bool)> callback) override;
   void RequestFileSystemPermission(
       const GURL& url,
       bool allowed_by_default,
@@ -80,6 +82,10 @@
       bool allow,
       const std::string& user_input);
 
+  void OnHidPermissionResponse(base::OnceCallback<void(bool)> callback,
+                               bool allow,
+                               const std::string& user_input);
+
   void OnFileSystemPermissionResponse(base::OnceCallback<void(bool)> callback,
                                       bool allow,
                                       const std::string& user_input);
diff --git a/chrome/test/data/extensions/platform_apps/web_view/permissions_test/embedder_has_permission/embedder.js b/chrome/test/data/extensions/platform_apps/web_view/permissions_test/embedder_has_permission/embedder.js
index b13d3cca..7a4aedb 100644
--- a/chrome/test/data/extensions/platform_apps/web_view/permissions_test/embedder_has_permission/embedder.js
+++ b/chrome/test/data/extensions/platform_apps/web_view/permissions_test/embedder_has_permission/embedder.js
@@ -235,6 +235,32 @@
   embedder.registerAndWaitForPostMessage_('testMedia', 'access-denied');
 }
 
+function testAllowHid() {
+  const webview = embedder.setUpGuest_();
+  const onPermissionRequest = function(e) {
+    e.request.allow();
+  };
+  webview.addEventListener('permissionrequest', onPermissionRequest);
+
+  const onWebViewLoadStop = function(e) {
+    embedder.test.succeed();
+  }
+  webview.addEventListener('loadstop', onWebViewLoadStop);
+}
+
+function testDenyHid() {
+  var webview = embedder.setUpGuest_();
+  var onPermissionRequest = function(e) {
+    e.request.deny();
+  };
+  webview.addEventListener('permissionrequest', onPermissionRequest);
+
+  const onWebViewLoadStop = function(e) {
+    embedder.test.succeed();
+  }
+  webview.addEventListener('loadstop', onWebViewLoadStop);
+}
+
 embedder.test.testList = {
   'testAllowGeolocation': testAllowGeolocation,
   'testDenyGeolocation': testDenyGeolocation,
@@ -243,7 +269,9 @@
   'testAllowMicrophone': testAllowMicrophone,
   'testDenyMicrophone': testDenyMicrophone,
   'testAllowMedia': testAllowMedia,
-  'testDenyMedia': testDenyMedia
+  'testDenyMedia': testDenyMedia,
+  'testAllowHid': testAllowHid,
+  'testDenyHid': testDenyHid,
 };
 
 onload = function() {
diff --git a/extensions/browser/guest_view/web_view/web_view_constants.cc b/extensions/browser/guest_view/web_view/web_view_constants.cc
index 81d1335..82bf861 100644
--- a/extensions/browser/guest_view/web_view/web_view_constants.cc
+++ b/extensions/browser/guest_view/web_view/web_view_constants.cc
@@ -59,6 +59,7 @@
 const char kPermissionTypeFileSystem[] = "filesystem";
 const char kPermissionTypeFullscreen[] = "fullscreen";
 const char kPermissionTypeGeolocation[] = "geolocation";
+const char kPermissionTypeHid[] = "hid";
 const char kPermissionTypeLoadPlugin[] = "loadplugin";
 const char kPermissionTypeMedia[] = "media";
 const char kPermissionTypeNewWindow[] = "newwindow";
diff --git a/extensions/browser/guest_view/web_view/web_view_constants.h b/extensions/browser/guest_view/web_view/web_view_constants.h
index a6d77b6..92c442c1 100644
--- a/extensions/browser/guest_view/web_view/web_view_constants.h
+++ b/extensions/browser/guest_view/web_view/web_view_constants.h
@@ -64,6 +64,7 @@
 extern const char kPermissionTypeFileSystem[];
 extern const char kPermissionTypeFullscreen[];
 extern const char kPermissionTypeGeolocation[];
+extern const char kPermissionTypeHid[];
 extern const char kPermissionTypeLoadPlugin[];
 extern const char kPermissionTypeMedia[];
 extern const char kPermissionTypeNewWindow[];
diff --git a/extensions/browser/guest_view/web_view/web_view_permission_helper.cc b/extensions/browser/guest_view/web_view/web_view_permission_helper.cc
index 9a9a5017..bf7ca86 100644
--- a/extensions/browser/guest_view/web_view/web_view_permission_helper.cc
+++ b/extensions/browser/guest_view/web_view/web_view_permission_helper.cc
@@ -6,9 +6,11 @@
 
 #include <utility>
 
+#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/location.h"
 #include "base/metrics/user_metrics.h"
+#include "base/metrics/user_metrics_action.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/values.h"
 #include "components/guest_view/browser/guest_view_event.h"
@@ -18,6 +20,7 @@
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
 #include "extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h"
 #include "extensions/browser/guest_view/web_view/web_view_permission_types.h"
+#include "extensions/common/extension_features.h"
 #include "ppapi/buildflags/buildflags.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
@@ -39,6 +42,8 @@
       return webview::kPermissionTypeFullscreen;
     case WEB_VIEW_PERMISSION_TYPE_GEOLOCATION:
       return webview::kPermissionTypeGeolocation;
+    case WEB_VIEW_PERMISSION_TYPE_HID:
+      return webview::kPermissionTypeHid;
     case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
       return webview::kPermissionTypeDialog;
     case WEB_VIEW_PERMISSION_TYPE_LOAD_PLUGIN:
@@ -81,6 +86,9 @@
         base::RecordAction(
             UserMetricsAction("WebView.PermissionAllow.Geolocation"));
         break;
+      case WEB_VIEW_PERMISSION_TYPE_HID:
+        base::RecordAction(UserMetricsAction("WebView.PermissionAllow.HID"));
+        break;
       case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
         base::RecordAction(
             UserMetricsAction("WebView.PermissionAllow.JSDialog"));
@@ -121,6 +129,9 @@
         base::RecordAction(
             UserMetricsAction("WebView.PermissionDeny.Geolocation"));
         break;
+      case WEB_VIEW_PERMISSION_TYPE_HID:
+        base::RecordAction(UserMetricsAction("WebView.PermissionDeny.HID"));
+        break;
       case WEB_VIEW_PERMISSION_TYPE_JAVASCRIPT_DIALOG:
         base::RecordAction(
             UserMetricsAction("WebView.PermissionDeny.JSDialog"));
@@ -252,11 +263,24 @@
 }
 
 void WebViewPermissionHelper::RequestGeolocationPermission(
-    const GURL& requesting_frame,
+    const GURL& requesting_frame_url,
     bool user_gesture,
     base::OnceCallback<void(bool)> callback) {
   web_view_permission_helper_delegate_->RequestGeolocationPermission(
-      requesting_frame, user_gesture, std::move(callback));
+      requesting_frame_url, user_gesture, std::move(callback));
+}
+
+void WebViewPermissionHelper::RequestHidPermission(
+    const GURL& requesting_frame_url,
+    base::OnceCallback<void(bool)> callback) {
+  if (!base::FeatureList::IsEnabled(
+          extensions_features::kEnableWebHidInWebView)) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  web_view_permission_helper_delegate_->RequestHidPermission(
+      requesting_frame_url, std::move(callback));
 }
 
 void WebViewPermissionHelper::RequestFileSystemPermission(
diff --git a/extensions/browser/guest_view/web_view/web_view_permission_helper.h b/extensions/browser/guest_view/web_view/web_view_permission_helper.h
index e5f02cf..958364a6 100644
--- a/extensions/browser/guest_view/web_view/web_view_permission_helper.h
+++ b/extensions/browser/guest_view/web_view/web_view_permission_helper.h
@@ -81,6 +81,10 @@
   void RequestGeolocationPermission(const GURL& requesting_frame,
                                     bool user_gesture,
                                     base::OnceCallback<void(bool)> callback);
+  // Requests permission from the embedder to request access to Human
+  // Interface Devices.
+  void RequestHidPermission(const GURL& requesting_frame,
+                            base::OnceCallback<void(bool)> callback);
 
   void RequestFileSystemPermission(const GURL& url,
                                    bool allowed_by_default,
diff --git a/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h b/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h
index ac42ac6f..31d15cc 100644
--- a/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h
+++ b/extensions/browser/guest_view/web_view/web_view_permission_helper_delegate.h
@@ -37,10 +37,13 @@
 
   // Requests Geolocation Permission from the embedder.
   virtual void RequestGeolocationPermission(
-      const GURL& requesting_frame,
+      const GURL& requesting_frame_url,
       bool user_gesture,
       base::OnceCallback<void(bool)> callback) {}
 
+  virtual void RequestHidPermission(const GURL& requesting_frame_url,
+                                    base::OnceCallback<void(bool)> callback) {}
+
   virtual void RequestFileSystemPermission(
       const GURL& url,
       bool allowed_by_default,
diff --git a/extensions/browser/guest_view/web_view/web_view_permission_types.h b/extensions/browser/guest_view/web_view/web_view_permission_types.h
index d3e01418b..e121c7a 100644
--- a/extensions/browser/guest_view/web_view/web_view_permission_types.h
+++ b/extensions/browser/guest_view/web_view/web_view_permission_types.h
@@ -17,6 +17,8 @@
   WEB_VIEW_PERMISSION_TYPE_FULLSCREEN,
 
   WEB_VIEW_PERMISSION_TYPE_GEOLOCATION,
+  // Permission to request access to Human Interface Devices.
+  WEB_VIEW_PERMISSION_TYPE_HID,
 
   // JavaScript Dialogs: prompt, alert, confirm
   // Note: Even through dialogs do not use the permission API, the dialog API
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index 4908555..8efd82be 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -57,6 +57,10 @@
              "EMF_NO_EXTENSION_ID_FOR_EXTENSION_SOURCE",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kEnableWebHidInWebView,
+             "EnableWebHidInWebView",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kExtensionDynamicURLRedirection,
              "ExtensionDynamicURLRedirection",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index a5438d9b..72ba75af 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -79,6 +79,10 @@
 // extension).
 BASE_DECLARE_FEATURE(kCheckingNoExtensionIdInExtensionIpcs);
 
+// If enabled, <webview>s will be allowed to request permission from an
+// embedding Chrome App to request access to Human Interface Devices.
+BASE_DECLARE_FEATURE(kEnableWebHidInWebView);
+
 // Determine if dynamic extension URLs are handled and redirected.
 BASE_DECLARE_FEATURE(kExtensionDynamicURLRedirection);
 
diff --git a/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js b/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js
index 99ac9e1..b7e549b 100644
--- a/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js
+++ b/extensions/renderer/resources/guest_view/web_view/web_view_action_requests.js
@@ -16,7 +16,11 @@
                         'download',
                         'loadplugin',
                         'filesystem',
-                        'fullscreen'];
+                        'fullscreen',
+                        // TODO(b/319100930): update the the documentation in
+                        // chrome/common/extensions/api/webview_tag.json once
+                        // the feature launches.
+                        'hid'];
 
 // The browser will kill us if we send it a bad instance ID.
 // TODO(780728): Remove once the cause of the bad ID is known.
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 0f26cba..8e7ccbc 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -40126,6 +40126,14 @@
   </description>
 </action>
 
+<action name="WebView.PermissionAllow.HID">
+  <owner>vkovalova@google.com</owner>
+  <description>
+    Tracks when showing the HID selection dialog is explicitly allowed on a
+    webview.
+  </description>
+</action>
+
 <action name="WebView.PermissionAllow.JSDialog">
   <owner>fsamuel@chromium.org</owner>
   <owner>lazyboy@chromium.org</owner>
@@ -40182,6 +40190,14 @@
   </description>
 </action>
 
+<action name="WebView.PermissionDeny.HID">
+  <owner>vkovalova@google.com</owner>
+  <description>
+    Tracks when showing the HID selection dialog is explicitly denied on a
+    webview.
+  </description>
+</action>
+
 <action name="WebView.PermissionDeny.JSDialog">
   <owner>fsamuel@chromium.org</owner>
   <owner>lazyboy@chromium.org</owner>