From d9a7422346ed3ed8bc4a9bfd3b7345d116b93ff4 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Thu, 7 Jul 2022 10:01:24 +0000 Subject: [PATCH] Add CefPermissionHandler callbacks for permission prompts (see issue #3352) --- BUILD.gn | 4 +- cef_paths.gypi | 6 +- cef_paths2.gypi | 2 + include/capi/cef_permission_handler_capi.h | 64 ++- include/cef_api_hash.h | 8 +- include/cef_permission_handler.h | 63 ++- include/internal/cef_types.h | 56 ++ libcef/browser/alloy/alloy_browser_context.cc | 7 +- libcef/browser/alloy/alloy_browser_context.h | 2 - libcef/browser/alloy/alloy_browser_main.cc | 2 + .../browser/alloy/alloy_permission_manager.cc | 115 ---- .../browser/alloy/alloy_permission_manager.h | 68 --- .../alloy/browser_platform_delegate_alloy.cc | 2 + .../alloy/chrome_browser_process_alloy.cc | 4 + .../chrome_browser_main_extra_parts_cef.cc | 2 + libcef/browser/permission_prompt.cc | 296 +++++++++++ libcef/browser/permission_prompt.h | 15 + libcef/browser/prefs/browser_prefs.cc | 4 + .../cpptoc/permission_handler_cpptoc.cc | 75 ++- .../permission_prompt_callback_cpptoc.cc | 66 +++ .../permission_prompt_callback_cpptoc.h | 38 ++ .../ctocpp/permission_handler_ctocpp.cc | 74 ++- libcef_dll/ctocpp/permission_handler_ctocpp.h | 14 +- .../permission_prompt_callback_ctocpp.cc | 60 +++ .../permission_prompt_callback_ctocpp.h | 41 ++ libcef_dll/wrapper_types.h | 3 +- patch/patch.cfg | 5 + .../chrome_browser_permission_prompt.patch | 73 +++ tests/cefclient/browser/client_handler.cc | 2 +- tests/cefclient/browser/client_handler.h | 2 +- tests/ceftests/media_access_unittest.cc | 49 +- tests/ceftests/permission_prompt_unittest.cc | 493 ++++++++++++++++++ 32 files changed, 1472 insertions(+), 243 deletions(-) delete mode 100644 libcef/browser/alloy/alloy_permission_manager.cc delete mode 100644 libcef/browser/alloy/alloy_permission_manager.h create mode 100644 libcef/browser/permission_prompt.cc create mode 100644 libcef/browser/permission_prompt.h create mode 100644 libcef_dll/cpptoc/permission_prompt_callback_cpptoc.cc create mode 100644 libcef_dll/cpptoc/permission_prompt_callback_cpptoc.h create mode 100644 libcef_dll/ctocpp/permission_prompt_callback_ctocpp.cc create mode 100644 libcef_dll/ctocpp/permission_prompt_callback_ctocpp.h create mode 100644 patch/patches/chrome_browser_permission_prompt.patch create mode 100644 tests/ceftests/permission_prompt_unittest.cc diff --git a/BUILD.gn b/BUILD.gn index d8fe2f653..4b17a4b92 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -419,8 +419,6 @@ static_library("libcef_static") { "libcef/browser/alloy/alloy_content_browser_client.h", "libcef/browser/alloy/alloy_download_util.cc", "libcef/browser/alloy/alloy_download_util.h", - "libcef/browser/alloy/alloy_permission_manager.cc", - "libcef/browser/alloy/alloy_permission_manager.h", "libcef/browser/alloy/alloy_web_contents_view_delegate.cc", "libcef/browser/alloy/alloy_web_contents_view_delegate.h", "libcef/browser/alloy/browser_platform_delegate_alloy.cc", @@ -662,6 +660,8 @@ static_library("libcef_static") { "libcef/browser/osr/web_contents_view_osr.cc", "libcef/browser/osr/web_contents_view_osr.h", "libcef/browser/path_util_impl.cc", + "libcef/browser/permission_prompt.cc", + "libcef/browser/permission_prompt.h", "libcef/browser/prefs/browser_prefs.cc", "libcef/browser/prefs/browser_prefs.h", "libcef/browser/prefs/pref_store.cc", diff --git a/cef_paths.gypi b/cef_paths.gypi index 8701028fa..c0caf6ff8 100644 --- a/cef_paths.gypi +++ b/cef_paths.gypi @@ -8,7 +8,7 @@ # by hand. See the translator.README.txt file in the tools directory for # more information. # -# $hash=f374acb217db4183917195716d5522a9eb897cdf$ +# $hash=3ed1afd1b5f881884e6cfd0186fe41bb7b19fd38$ # { @@ -374,6 +374,8 @@ 'libcef_dll/ctocpp/pdf_print_callback_ctocpp.h', 'libcef_dll/ctocpp/permission_handler_ctocpp.cc', 'libcef_dll/ctocpp/permission_handler_ctocpp.h', + 'libcef_dll/cpptoc/permission_prompt_callback_cpptoc.cc', + 'libcef_dll/cpptoc/permission_prompt_callback_cpptoc.h', 'libcef_dll/cpptoc/post_data_cpptoc.cc', 'libcef_dll/cpptoc/post_data_cpptoc.h', 'libcef_dll/cpptoc/post_data_element_cpptoc.cc', @@ -690,6 +692,8 @@ 'libcef_dll/cpptoc/pdf_print_callback_cpptoc.h', 'libcef_dll/cpptoc/permission_handler_cpptoc.cc', 'libcef_dll/cpptoc/permission_handler_cpptoc.h', + 'libcef_dll/ctocpp/permission_prompt_callback_ctocpp.cc', + 'libcef_dll/ctocpp/permission_prompt_callback_ctocpp.h', 'libcef_dll/ctocpp/post_data_ctocpp.cc', 'libcef_dll/ctocpp/post_data_ctocpp.h', 'libcef_dll/ctocpp/post_data_element_ctocpp.cc', diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 34ec12759..eaa902ff0 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -493,6 +493,7 @@ 'tests/ceftests/osr_display_unittest.cc', 'tests/ceftests/parser_unittest.cc', 'tests/ceftests/pdf_viewer_unittest.cc', + 'tests/ceftests/permission_prompt_unittest.cc', 'tests/ceftests/preference_unittest.cc', 'tests/ceftests/print_unittest.cc', 'tests/ceftests/process_message_unittest.cc', @@ -584,6 +585,7 @@ 'tests/ceftests/message_router_unittest_utils.h', 'tests/ceftests/navigation_unittest.cc', 'tests/ceftests/pdf_viewer_unittest.cc', + 'tests/ceftests/permission_prompt_unittest.cc', 'tests/ceftests/preference_unittest.cc', 'tests/ceftests/process_message_unittest.cc', 'tests/ceftests/request_handler_unittest.cc', diff --git a/include/capi/cef_permission_handler_capi.h b/include/capi/cef_permission_handler_capi.h index a443b3d0b..b5b61fd0d 100644 --- a/include/capi/cef_permission_handler_capi.h +++ b/include/capi/cef_permission_handler_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=e25fb66e356e1f01d67cb86433382b3318e9778d$ +// $hash=5a39566f586c012271d96c7d42337a30bf98e6b8$ // #ifndef CEF_INCLUDE_CAPI_CEF_PERMISSION_HANDLER_CAPI_H_ @@ -74,6 +74,22 @@ typedef struct _cef_media_access_callback_t { void(CEF_CALLBACK* cancel)(struct _cef_media_access_callback_t* self); } cef_media_access_callback_t; +/// +// Callback structure used for asynchronous continuation of permission prompts. +/// +typedef struct _cef_permission_prompt_callback_t { + /// + // Base structure. + /// + cef_base_ref_counted_t base; + + /// + // Complete the permissions request with the specified |result|. + /// + void(CEF_CALLBACK* cont)(struct _cef_permission_prompt_callback_t* self, + cef_permission_request_result_t result); +} cef_permission_prompt_callback_t; + /// // Implement this structure to handle events related to permission requests. The // functions of this structure will be called on the browser process UI thread. @@ -85,11 +101,11 @@ typedef struct _cef_permission_handler_t { cef_base_ref_counted_t base; /// - // Called when a page requests permission to access media. |requesting_url| is - // the URL requesting permission. |requested_permissions| is a combination of - // values from cef_media_access_permission_types_t that represent the - // requested permissions. Return true (1) and call - // cef_media_access_callback_t::cont() either in this function or at a later + // Called when a page requests permission to access media. |requesting_origin| + // is the URL origin requesting permission. |requested_permissions| is a + // combination of values from cef_media_access_permission_types_t that + // represent the requested permissions. Return true (1) and call + // cef_media_access_callback_t functions either in this function or at a later // time to continue or cancel the request. Return false (0) to cancel the // request immediately. This function will not be called if the "--enable- // media-stream" command-line switch is used to grant all permissions. @@ -98,9 +114,43 @@ typedef struct _cef_permission_handler_t { struct _cef_permission_handler_t* self, struct _cef_browser_t* browser, struct _cef_frame_t* frame, - const cef_string_t* requesting_url, + const cef_string_t* requesting_origin, uint32 requested_permissions, struct _cef_media_access_callback_t* callback); + + /// + // Called when a page should show a permission prompt. |prompt_id| uniquely + // identifies the prompt. |requesting_origin| is the URL origin requesting + // permission. |requested_permissions| is a combination of values from + // cef_permission_request_types_t that represent the requested permissions. + // Return true (1) and call cef_permission_prompt_callback_t::Continue either + // in this function or at a later time to continue or cancel the request. + // Return false (0) to proceed with default handling. With the Chrome runtime, + // default handling will display the permission prompt UI. With the Alloy + // runtime, default handling is CEF_PERMISSION_RESULT_IGNORE. + /// + int(CEF_CALLBACK* on_show_permission_prompt)( + struct _cef_permission_handler_t* self, + struct _cef_browser_t* browser, + uint64 prompt_id, + const cef_string_t* requesting_origin, + uint32 requested_permissions, + struct _cef_permission_prompt_callback_t* callback); + + /// + // Called when a permission prompt handled via OnShowPermissionPrompt is + // dismissed. |prompt_id| will match the value that was passed to + // OnShowPermissionPrompt. |result| will be the value passed to + // cef_permission_prompt_callback_t::Continue or CEF_PERMISSION_RESULT_IGNORE + // if the dialog was dismissed for other reasons such as navigation, browser + // closure, etc. This function will not be called if OnShowPermissionPrompt + // returned false (0) for |prompt_id|. + /// + void(CEF_CALLBACK* on_dismiss_permission_prompt)( + struct _cef_permission_handler_t* self, + struct _cef_browser_t* browser, + uint64 prompt_id, + cef_permission_request_result_t result); } cef_permission_handler_t; #ifdef __cplusplus diff --git a/include/cef_api_hash.h b/include/cef_api_hash.h index cc6335616..2e3a665f7 100644 --- a/include/cef_api_hash.h +++ b/include/cef_api_hash.h @@ -42,13 +42,13 @@ // way that may cause binary incompatibility with other builds. The universal // hash value will change if any platform is affected whereas the platform hash // values will change only if that particular platform is affected. -#define CEF_API_HASH_UNIVERSAL "d3fdeba02acc73ac571a1be658789f2ff770f09c" +#define CEF_API_HASH_UNIVERSAL "1f35577ebd00c5e6cc03a172bb41e3c0d820f3d1" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "94db0746536862260c9b47d54128a744dbb29fcf" +#define CEF_API_HASH_PLATFORM "99f91193dce6b93011526269c4dc5d0c32569b70" #elif defined(OS_MAC) -#define CEF_API_HASH_PLATFORM "1c8d61e3bee1c974a2f71688bbdcfc0f6f01d457" +#define CEF_API_HASH_PLATFORM "316e120c0bf151de8145ee3b45ef3451e1ff60dc" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "6d9e52b9e54ded43a7e0dfcf80b6f40ec75f3215" +#define CEF_API_HASH_PLATFORM "e061aecbfbf09b9068391bdfcd80c9d5ed1faece" #endif #ifdef __cplusplus diff --git a/include/cef_permission_handler.h b/include/cef_permission_handler.h index 870e9d858..09b587308 100644 --- a/include/cef_permission_handler.h +++ b/include/cef_permission_handler.h @@ -66,6 +66,19 @@ class CefMediaAccessCallback : public virtual CefBaseRefCounted { virtual void Cancel() = 0; }; +/// +// Callback interface used for asynchronous continuation of permission prompts. +/// +/*--cef(source=library)--*/ +class CefPermissionPromptCallback : public virtual CefBaseRefCounted { + public: + /// + // Complete the permissions request with the specified |result|. + /// + /*--cef(capi_name=cont)--*/ + virtual void Continue(cef_permission_request_result_t result) = 0; +}; + /// // Implement this interface to handle events related to permission requests. The // methods of this class will be called on the browser process UI thread. @@ -74,12 +87,12 @@ class CefMediaAccessCallback : public virtual CefBaseRefCounted { class CefPermissionHandler : public virtual CefBaseRefCounted { public: /// - // Called when a page requests permission to access media. |requesting_url| is - // the URL requesting permission. |requested_permissions| is a combination of - // values from cef_media_access_permission_types_t that represent the - // requested permissions. Return true and call - // CefMediaAccessCallback::Continue() either in this method or at a later time - // to continue or cancel the request. Return false to cancel the request + // Called when a page requests permission to access media. |requesting_origin| + // is the URL origin requesting permission. |requested_permissions| is a + // combination of values from cef_media_access_permission_types_t that + // represent the requested permissions. Return true and call + // CefMediaAccessCallback methods either in this method or at a later time to + // continue or cancel the request. Return false to cancel the request // immediately. This method will not be called if the "--enable-media-stream" // command-line switch is used to grant all permissions. /// @@ -87,11 +100,47 @@ class CefPermissionHandler : public virtual CefBaseRefCounted { virtual bool OnRequestMediaAccessPermission( CefRefPtr browser, CefRefPtr frame, - const CefString& requesting_url, + const CefString& requesting_origin, uint32 requested_permissions, CefRefPtr callback) { return false; } + + /// + // Called when a page should show a permission prompt. |prompt_id| uniquely + // identifies the prompt. |requesting_origin| is the URL origin requesting + // permission. |requested_permissions| is a combination of values from + // cef_permission_request_types_t that represent the requested permissions. + // Return true and call CefPermissionPromptCallback::Continue either in this + // method or at a later time to continue or cancel the request. Return false + // to proceed with default handling. With the Chrome runtime, default handling + // will display the permission prompt UI. With the Alloy runtime, default + // handling is CEF_PERMISSION_RESULT_IGNORE. + /// + /*--cef()--*/ + virtual bool OnShowPermissionPrompt( + CefRefPtr browser, + uint64 prompt_id, + const CefString& requesting_origin, + uint32 requested_permissions, + CefRefPtr callback) { + return false; + } + + /// + // Called when a permission prompt handled via OnShowPermissionPrompt is + // dismissed. |prompt_id| will match the value that was passed to + // OnShowPermissionPrompt. |result| will be the value passed to + // CefPermissionPromptCallback::Continue or CEF_PERMISSION_RESULT_IGNORE if + // the dialog was dismissed for other reasons such as navigation, browser + // closure, etc. This method will not be called if OnShowPermissionPrompt + // returned false for |prompt_id|. + /// + /*--cef()--*/ + virtual void OnDismissPermissionPrompt( + CefRefPtr browser, + uint64 prompt_id, + cef_permission_request_result_t result) {} }; #endif // CEF_INCLUDE_CEF_PERMISSION_HANDLER_H_ diff --git a/include/internal/cef_types.h b/include/internal/cef_types.h index 5af1f0746..35308316e 100644 --- a/include/internal/cef_types.h +++ b/include/internal/cef_types.h @@ -3317,6 +3317,62 @@ typedef enum { CEF_MEDIA_PERMISSION_DESKTOP_VIDEO_CAPTURE = 1 << 3, } cef_media_access_permission_types_t; +/// +// Permission types used with OnShowPermissionPrompt. Some types are +// platform-specific or only supported with the Chrome runtime. Should be kept +// in sync with Chromium's permissions::RequestType type. +/// +typedef enum { + CEF_PERMISSION_TYPE_NONE = 0, + CEF_PERMISSION_TYPE_ACCESSIBILITY_EVENTS = 1 << 0, + CEF_PERMISSION_TYPE_AR_SESSION = 1 << 1, + CEF_PERMISSION_TYPE_CAMERA_PAN_TILT_ZOOM = 1 << 2, + CEF_PERMISSION_TYPE_CAMERA_STREAM = 1 << 3, + CEF_PERMISSION_TYPE_CLIPBOARD = 1 << 4, + CEF_PERMISSION_TYPE_DISK_QUOTA = 1 << 5, + CEF_PERMISSION_TYPE_LOCAL_FONTS = 1 << 6, + CEF_PERMISSION_TYPE_GEOLOCATION = 1 << 7, + CEF_PERMISSION_TYPE_IDLE_DETECTION = 1 << 8, + CEF_PERMISSION_TYPE_MIC_STREAM = 1 << 9, + CEF_PERMISSION_TYPE_MIDI_SYSEX = 1 << 10, + CEF_PERMISSION_TYPE_MULTIPLE_DOWNLOADS = 1 << 11, + CEF_PERMISSION_TYPE_NOTIFICATIONS = 1 << 12, + CEF_PERMISSION_TYPE_PROTECTED_MEDIA_IDENTIFIER = 1 << 13, + CEF_PERMISSION_TYPE_REGISTER_PROTOCOL_HANDLER = 1 << 14, + CEF_PERMISSION_TYPE_SECURITY_ATTESTATION = 1 << 15, + CEF_PERMISSION_TYPE_STORAGE_ACCESS = 1 << 16, + CEF_PERMISSION_TYPE_U2F_API_REQUEST = 1 << 17, + CEF_PERMISSION_TYPE_VR_SESSION = 1 << 18, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT = 1 << 19, +} cef_permission_request_types_t; + +/// +// Permission request results. +/// +typedef enum { + /// + // Accept the permission request as an explicit user action. + /// + CEF_PERMISSION_RESULT_ACCEPT, + + /// + // Deny the permission request as an explicit user action. + /// + CEF_PERMISSION_RESULT_DENY, + + /// + // Dismiss the permission request as an explicit user action. + /// + CEF_PERMISSION_RESULT_DISMISS, + + /// + // Ignore the permission request. If the prompt remains unhandled (e.g. + // OnShowPermissionPrompt returns false and there is no default permissions + // UI) then any related promises may remain unresolved. + /// + CEF_PERMISSION_RESULT_IGNORE, +} cef_permission_request_result_t; + #ifdef __cplusplus } #endif diff --git a/libcef/browser/alloy/alloy_browser_context.cc b/libcef/browser/alloy/alloy_browser_context.cc index 933b82e14..1b9f2832a 100644 --- a/libcef/browser/alloy/alloy_browser_context.cc +++ b/libcef/browser/alloy/alloy_browser_context.cc @@ -7,7 +7,6 @@ #include #include -#include "libcef/browser/alloy/alloy_permission_manager.h" #include "libcef/browser/download_manager_delegate.h" #include "libcef/browser/extensions/extension_system.h" #include "libcef/browser/prefs/browser_prefs.h" @@ -23,6 +22,7 @@ #include "base/strings/string_util.h" #include "chrome/browser/font_family_cache.h" #include "chrome/browser/media/media_device_id_salt.h" +#include "chrome/browser/permissions/permission_manager_factory.h" #include "chrome/browser/plugins/chrome_plugin_service_filter.h" #include "chrome/browser/profiles/profile_key.h" #include "chrome/browser/ui/zoom/chrome_zoom_level_prefs.h" @@ -31,6 +31,7 @@ #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/keyed_service/core/simple_dependency_manager.h" #include "components/keyed_service/core/simple_key_map.h" +#include "components/permissions/permission_manager.h" #include "components/prefs/pref_service.h" #include "components/proxy_config/pref_proxy_config_tracker_impl.h" #include "components/user_prefs/user_prefs.h" @@ -381,9 +382,7 @@ content::SSLHostStateDelegate* AlloyBrowserContext::GetSSLHostStateDelegate() { content::PermissionControllerDelegate* AlloyBrowserContext::GetPermissionControllerDelegate() { - if (!permission_manager_.get()) - permission_manager_ = std::make_unique(); - return permission_manager_.get(); + return PermissionManagerFactory::GetForProfile(this); } content::BackgroundFetchDelegate* diff --git a/libcef/browser/alloy/alloy_browser_context.h b/libcef/browser/alloy/alloy_browser_context.h index fe4d7bb61..d7756b258 100644 --- a/libcef/browser/alloy/alloy_browser_context.h +++ b/libcef/browser/alloy/alloy_browser_context.h @@ -145,8 +145,6 @@ class AlloyBrowserContext : public ChromeProfileAlloy, std::unique_ptr resource_context_; scoped_refptr media_device_id_salt_; - - std::unique_ptr permission_manager_; }; #endif // CEF_LIBCEF_BROWSER_ALLOY_ALLOY_BROWSER_CONTEXT_H_ diff --git a/libcef/browser/alloy/alloy_browser_main.cc b/libcef/browser/alloy/alloy_browser_main.cc index 02dc89b39..cf7681e40 100644 --- a/libcef/browser/alloy/alloy_browser_main.cc +++ b/libcef/browser/alloy/alloy_browser_main.cc @@ -16,6 +16,7 @@ #include "libcef/browser/extensions/extension_system_factory.h" #include "libcef/browser/file_dialog_runner.h" #include "libcef/browser/net/chrome_scheme_handler.h" +#include "libcef/browser/permission_prompt.h" #include "libcef/browser/thread_util.h" #include "libcef/common/app_manager.h" #include "libcef/common/extensions/extensions_util.h" @@ -322,6 +323,7 @@ int AlloyBrowserMainParts::PreMainMessageLoopRun() { scheme::RegisterWebUIControllerFactory(); file_dialog_runner::RegisterFactory(); + permission_prompt::RegisterCreateCallback(); #if BUILDFLAG(ENABLE_MEDIA_FOUNDATION_WIDEVINE_CDM) || \ BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) diff --git a/libcef/browser/alloy/alloy_permission_manager.cc b/libcef/browser/alloy/alloy_permission_manager.cc deleted file mode 100644 index 2c2f031d4..000000000 --- a/libcef/browser/alloy/alloy_permission_manager.cc +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2022 The Chromium Embedded Framework Authors. Portions copyright -// 2015 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. - -#include "libcef/browser/alloy/alloy_permission_manager.h" - -#include "base/callback.h" -#include "content/public/browser/permission_controller.h" -#include "content/public/browser/render_frame_host.h" -#include "third_party/blink/public/common/permissions/permission_utils.h" - -namespace { - -bool IsAllowed(blink::PermissionType permission) { - return permission == blink::PermissionType::WINDOW_PLACEMENT; -} - -blink::mojom::PermissionStatus GetPermissionStatusFromType( - blink::PermissionType permission) { - return IsAllowed(permission) ? blink::mojom::PermissionStatus::GRANTED - : blink::mojom::PermissionStatus::DENIED; -} - -} // namespace - -void AlloyPermissionManager::RequestPermission( - blink::PermissionType permission, - content::RenderFrameHost* render_frame_host, - const GURL& requesting_origin, - bool user_gesture, - base::OnceCallback callback) { - if (render_frame_host->IsNestedWithinFencedFrame()) { - std::move(callback).Run(blink::mojom::PermissionStatus::DENIED); - return; - } - std::move(callback).Run(GetPermissionStatusFromType(permission)); -} - -void AlloyPermissionManager::RequestPermissions( - const std::vector& permissions, - content::RenderFrameHost* render_frame_host, - const GURL& requesting_origin, - bool user_gesture, - base::OnceCallback&)> - callback) { - if (render_frame_host->IsNestedWithinFencedFrame()) { - std::move(callback).Run(std::vector( - permissions.size(), blink::mojom::PermissionStatus::DENIED)); - return; - } - std::vector result; - for (const auto& permission : permissions) { - result.push_back(GetPermissionStatusFromType(permission)); - } - std::move(callback).Run(result); -} - -void AlloyPermissionManager::RequestPermissionsFromCurrentDocument( - const std::vector& permissions, - content::RenderFrameHost* render_frame_host, - bool user_gesture, - base::OnceCallback&)> - callback) { - if (render_frame_host->IsNestedWithinFencedFrame()) { - std::move(callback).Run(std::vector( - permissions.size(), blink::mojom::PermissionStatus::DENIED)); - return; - } - std::vector result; - for (const auto& permission : permissions) { - result.push_back(GetPermissionStatusFromType(permission)); - } - std::move(callback).Run(result); -} - -blink::mojom::PermissionStatus AlloyPermissionManager::GetPermissionStatus( - blink::PermissionType permission, - const GURL& requesting_origin, - const GURL& embedding_origin) { - return GetPermissionStatusFromType(permission); -} - -blink::mojom::PermissionStatus -AlloyPermissionManager::GetPermissionStatusForCurrentDocument( - blink::PermissionType permission, - content::RenderFrameHost* render_frame_host) { - if (render_frame_host->IsNestedWithinFencedFrame()) - return blink::mojom::PermissionStatus::DENIED; - return GetPermissionStatusFromType(permission); -} - -blink::mojom::PermissionStatus -AlloyPermissionManager::GetPermissionStatusForWorker( - blink::PermissionType permission, - content::RenderProcessHost* render_process_host, - const GURL& worker_origin) { - return GetPermissionStatusFromType(permission); -} - -void AlloyPermissionManager::ResetPermission(blink::PermissionType permission, - const GURL& requesting_origin, - const GURL& embedding_origin) {} - -AlloyPermissionManager::SubscriptionId -AlloyPermissionManager::SubscribePermissionStatusChange( - blink::PermissionType permission, - content::RenderProcessHost* render_process_host, - content::RenderFrameHost* render_frame_host, - const GURL& requesting_origin, - base::RepeatingCallback callback) { - return SubscriptionId(); -} - -void AlloyPermissionManager::UnsubscribePermissionStatusChange( - SubscriptionId subscription_id) {} \ No newline at end of file diff --git a/libcef/browser/alloy/alloy_permission_manager.h b/libcef/browser/alloy/alloy_permission_manager.h deleted file mode 100644 index 3d16e0ce6..000000000 --- a/libcef/browser/alloy/alloy_permission_manager.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2022 The Chromium Embedded Framework Authors. Portions copyright -// 2015 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 CEF_LIBCEF_BROWSER_ALLOY_ALLOY_PERMISSION_MANAGER_H_ -#define CEF_LIBCEF_BROWSER_ALLOY_ALLOY_PERMISSION_MANAGER_H_ -#pragma once - -#include "base/callback_forward.h" -#include "content/public/browser/permission_controller_delegate.h" - -// Permision manager implementations that only allows WINDOW_PLACEMENT API -class AlloyPermissionManager : public content::PermissionControllerDelegate { - public: - AlloyPermissionManager() = default; - - AlloyPermissionManager(const AlloyPermissionManager&) = delete; - AlloyPermissionManager& operator=(const AlloyPermissionManager&) = delete; - - // PermissionManager implementation. - void RequestPermission( - blink::PermissionType permission, - content::RenderFrameHost* render_frame_host, - const GURL& requesting_origin, - bool user_gesture, - base::OnceCallback callback) - override; - void RequestPermissions( - const std::vector& permission, - content::RenderFrameHost* render_frame_host, - const GURL& requesting_origin, - bool user_gesture, - base::OnceCallback< - void(const std::vector&)> callback) - override; - void RequestPermissionsFromCurrentDocument( - const std::vector& permissions, - content::RenderFrameHost* render_frame_host, - bool user_gesture, - base::OnceCallback< - void(const std::vector&)> callback) - override; - blink::mojom::PermissionStatus GetPermissionStatus( - blink::PermissionType permission, - const GURL& requesting_origin, - const GURL& embedding_origin) override; - blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument( - blink::PermissionType permission, - content::RenderFrameHost* render_frame_host) override; - blink::mojom::PermissionStatus GetPermissionStatusForWorker( - blink::PermissionType permission, - content::RenderProcessHost* render_process_host, - const GURL& worker_origin) override; - void ResetPermission(blink::PermissionType permission, - const GURL& requesting_origin, - const GURL& embedding_origin) override; - SubscriptionId SubscribePermissionStatusChange( - blink::PermissionType permission, - content::RenderProcessHost* render_process_host, - content::RenderFrameHost* render_frame_host, - const GURL& requesting_origin, - base::RepeatingCallback callback) - override; - void UnsubscribePermissionStatusChange( - SubscriptionId subscription_id) override; -}; - -#endif // CEF_LIBCEF_BROWSER_ALLOY_ALLOY_PERMISSION_MANAGER_H_ \ No newline at end of file diff --git a/libcef/browser/alloy/browser_platform_delegate_alloy.cc b/libcef/browser/alloy/browser_platform_delegate_alloy.cc index 8e5e79857..744fb3fa3 100644 --- a/libcef/browser/alloy/browser_platform_delegate_alloy.cc +++ b/libcef/browser/alloy/browser_platform_delegate_alloy.cc @@ -23,6 +23,7 @@ #include "components/find_in_page/find_tab_helper.h" #include "components/find_in_page/find_types.h" #include "components/javascript_dialogs/tab_modal_dialog_manager.h" +#include "components/permissions/permission_request_manager.h" #include "components/zoom/zoom_controller.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" @@ -178,6 +179,7 @@ void CefBrowserPlatformDelegateAlloy::BrowserCreated( DCHECK(!web_contents_->GetDelegate()); web_contents_->SetDelegate(static_cast(browser)); + permissions::PermissionRequestManager::CreateForWebContents(web_contents_); PrefsTabHelper::CreateForWebContents(web_contents_); printing::CefPrintViewManager::CreateForWebContents(web_contents_); diff --git a/libcef/browser/alloy/chrome_browser_process_alloy.cc b/libcef/browser/alloy/chrome_browser_process_alloy.cc index da6c61c43..7bf534517 100644 --- a/libcef/browser/alloy/chrome_browser_process_alloy.cc +++ b/libcef/browser/alloy/chrome_browser_process_alloy.cc @@ -18,6 +18,7 @@ #include "base/command_line.h" #include "chrome/browser/component_updater/chrome_component_updater_configurator.h" #include "chrome/browser/net/system_network_context_manager.h" +#include "chrome/browser/permissions/chrome_permissions_client.h" #include "chrome/browser/policy/chrome_browser_policy_connector.h" #include "chrome/browser/printing/background_printing_manager.h" #include "chrome/browser/printing/print_job_manager.h" @@ -67,6 +68,9 @@ void ChromeBrowserProcessAlloy::Initialize() { extensions::ExtensionsBrowserClient::Set(extensions_browser_client_.get()); } + // Make sure permissions client has been set. + ChromePermissionsClient::GetInstance(); + initialized_ = true; } diff --git a/libcef/browser/chrome/chrome_browser_main_extra_parts_cef.cc b/libcef/browser/chrome/chrome_browser_main_extra_parts_cef.cc index f2b99a5db..e37fd7861 100644 --- a/libcef/browser/chrome/chrome_browser_main_extra_parts_cef.cc +++ b/libcef/browser/chrome/chrome_browser_main_extra_parts_cef.cc @@ -8,6 +8,7 @@ #include "libcef/browser/context.h" #include "libcef/browser/file_dialog_runner.h" #include "libcef/browser/net/chrome_scheme_handler.h" +#include "libcef/browser/permission_prompt.h" #include "base/task/thread_pool.h" @@ -42,4 +43,5 @@ void ChromeBrowserMainExtraPartsCef::PreMainMessageLoopRun() { scheme::RegisterWebUIControllerFactory(); context_menu::RegisterMenuCreatedCallback(); file_dialog_runner::RegisterFactory(); + permission_prompt::RegisterCreateCallback(); } diff --git a/libcef/browser/permission_prompt.cc b/libcef/browser/permission_prompt.cc new file mode 100644 index 000000000..515cc079e --- /dev/null +++ b/libcef/browser/permission_prompt.cc @@ -0,0 +1,296 @@ +// Copyright 2022 The Chromium Embedded Framework Authors. Portions copyright +// 2016 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. + +#include "libcef/browser/permission_prompt.h" + +#include "libcef/browser/browser_host_base.h" +#include "libcef/features/runtime.h" + +#include "base/logging.h" +#include "base/memory/weak_ptr.h" +#include "base/notreached.h" +#include "chrome/browser/ui/permission_bubble/permission_prompt.h" + +namespace permission_prompt { + +namespace { + +uint64_t g_next_prompt_id = 0; + +using DelegateCallback = + base::OnceCallback; + +class CefPermissionPromptCallbackImpl : public CefPermissionPromptCallback { + public: + using CallbackType = base::OnceCallback; + + explicit CefPermissionPromptCallbackImpl(CallbackType&& callback) + : callback_(std::move(callback)) {} + + CefPermissionPromptCallbackImpl(const CefPermissionPromptCallbackImpl&) = + delete; + CefPermissionPromptCallbackImpl& operator=( + const CefPermissionPromptCallbackImpl&) = delete; + + // Don't need to execute the callback in this destructor because this object + // will always be kept alive until after the CefPermissionPrompt is destroyed, + // and that object will disconnect/execute the callback in its destructor. + ~CefPermissionPromptCallbackImpl() override = default; + + void Continue(cef_permission_request_result_t result) override { + if (CEF_CURRENTLY_ON_UIT()) { + if (!callback_.is_null()) { + auto callback = base::BindOnce(std::move(callback_), result, + /*notify_delegate=*/true); + if (safe_to_run_sync_) { + std::move(callback).Run(); + } else { + CEF_POST_TASK(CEF_UIT, std::move(callback)); + } + } + } else { + CEF_POST_TASK(CEF_UIT, + base::BindOnce(&CefPermissionPromptCallbackImpl::Continue, + this, result)); + } + } + + [[nodiscard]] CallbackType Disconnect() { return std::move(callback_); } + bool IsDisconnected() const { return callback_.is_null(); } + + void MarkSafeToRunSync() { safe_to_run_sync_ = true; } + + private: + // Callback execution from inside CreatePermissionPromptImpl must be async, + // otherwise PermissionRequestManager state will be incorrect. + bool safe_to_run_sync_ = false; + + CallbackType callback_; + + IMPLEMENT_REFCOUNTING(CefPermissionPromptCallbackImpl); +}; + +// Implementation based on PermissionPromptAndroid. +class CefPermissionPrompt : public permissions::PermissionPrompt { + public: + explicit CefPermissionPrompt(Delegate* delegate) : delegate_(delegate) { + DCHECK(delegate_); + } + + CefPermissionPrompt(const CefPermissionPrompt&) = delete; + CefPermissionPrompt& operator=(const CefPermissionPrompt&) = delete; + + // Expect to be destroyed (and the UI needs to go) when: + // 1. A navigation happens, tab/webcontents is being closed; with the current + // GetTabSwitchingBehavior() implementation, this instance survives the tab + // being backgrounded. + // 2. The permission request is resolved (accept, deny, dismiss). + // 3. A higher priority request comes in. + ~CefPermissionPrompt() override { + CEF_REQUIRE_UIT(); + if (callback_) { + // If the callback is non-null at this point then we still need to execute + // it in order to notify the client. + auto callback = callback_->Disconnect(); + if (!callback.is_null()) { + std::move(callback).Run(CEF_PERMISSION_RESULT_IGNORE, + /*notify_delegate=*/false); + } + } + } + + // Used to associate the client callback when OnShowPermissionPrompt is + // handled. + void AttachClientCallback( + CefRefPtr callback) { + DCHECK(callback); + callback_ = callback; + callback_->MarkSafeToRunSync(); + } + + // Used to tie Delegate access to this object's lifespan. + DelegateCallback MakeDelegateCallback() const { + return base::BindOnce(&CefPermissionPrompt::NotifyDelegate, + weak_ptr_factory_.GetWeakPtr()); + } + + // PermissionPrompt methods: + void UpdateAnchor() override { NOTIMPLEMENTED(); } + TabSwitchingBehavior GetTabSwitchingBehavior() override { + return TabSwitchingBehavior::kKeepPromptAlive; + } + permissions::PermissionPromptDisposition GetPromptDisposition() + const override { + return permissions::PermissionPromptDisposition::CUSTOM_MODAL_DIALOG; + } + + private: + // We don't expose AcceptThisTime() because it's a special case for + // Geolocation (see DCHECK in PrefProvider::SetWebsiteSetting). + void NotifyDelegate(cef_permission_request_result_t result) { + switch (result) { + case CEF_PERMISSION_RESULT_ACCEPT: + delegate_->Accept(); + break; + case CEF_PERMISSION_RESULT_DENY: + delegate_->Deny(); + break; + case CEF_PERMISSION_RESULT_DISMISS: + delegate_->Dismiss(); + break; + case CEF_PERMISSION_RESULT_IGNORE: + delegate_->Ignore(); + break; + } + } + + // |delegate_| is the PermissionRequestManager, which owns this object. + const raw_ptr delegate_; + + CefRefPtr callback_; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +// |notify_delegate| will be false if called from the CefPermissionPrompt +// destructor. +void ExecuteResult(CefRefPtr browser, + uint64_t prompt_id, + DelegateCallback delegate_callback, + cef_permission_request_result_t result, + bool notify_delegate) { + CEF_REQUIRE_UIT(); + + if (auto client = browser->GetClient()) { + if (auto handler = client->GetPermissionHandler()) { + handler->OnDismissPermissionPrompt(browser, prompt_id, result); + } + } + + if (notify_delegate) { + // Will be a no-op if this executes after the CefPermissionPrompt was + // destroyed. + std::move(delegate_callback).Run(result); + } +} + +cef_permission_request_types_t GetCefRequestType( + permissions::RequestType type) { + switch (type) { + case permissions::RequestType::kAccessibilityEvents: + return CEF_PERMISSION_TYPE_ACCESSIBILITY_EVENTS; + case permissions::RequestType::kArSession: + return CEF_PERMISSION_TYPE_AR_SESSION; + case permissions::RequestType::kCameraPanTiltZoom: + return CEF_PERMISSION_TYPE_CAMERA_PAN_TILT_ZOOM; + case permissions::RequestType::kCameraStream: + return CEF_PERMISSION_TYPE_CAMERA_STREAM; + case permissions::RequestType::kClipboard: + return CEF_PERMISSION_TYPE_CLIPBOARD; + case permissions::RequestType::kDiskQuota: + return CEF_PERMISSION_TYPE_DISK_QUOTA; + case permissions::RequestType::kLocalFonts: + return CEF_PERMISSION_TYPE_LOCAL_FONTS; + case permissions::RequestType::kGeolocation: + return CEF_PERMISSION_TYPE_GEOLOCATION; + case permissions::RequestType::kIdleDetection: + return CEF_PERMISSION_TYPE_IDLE_DETECTION; + case permissions::RequestType::kMicStream: + return CEF_PERMISSION_TYPE_MIC_STREAM; + case permissions::RequestType::kMidiSysex: + return CEF_PERMISSION_TYPE_MIDI_SYSEX; + case permissions::RequestType::kMultipleDownloads: + return CEF_PERMISSION_TYPE_MULTIPLE_DOWNLOADS; + case permissions::RequestType::kNotifications: + return CEF_PERMISSION_TYPE_NOTIFICATIONS; +#if BUILDFLAG(IS_WIN) + case permissions::RequestType::kProtectedMediaIdentifier: + return CEF_PERMISSION_TYPE_PROTECTED_MEDIA_IDENTIFIER; +#endif + case permissions::RequestType::kRegisterProtocolHandler: + return CEF_PERMISSION_TYPE_REGISTER_PROTOCOL_HANDLER; + case permissions::RequestType::kSecurityAttestation: + return CEF_PERMISSION_TYPE_SECURITY_ATTESTATION; + case permissions::RequestType::kStorageAccess: + return CEF_PERMISSION_TYPE_STORAGE_ACCESS; + case permissions::RequestType::kU2fApiRequest: + return CEF_PERMISSION_TYPE_U2F_API_REQUEST; + case permissions::RequestType::kVrSession: + return CEF_PERMISSION_TYPE_VR_SESSION; + case permissions::RequestType::kWindowPlacement: + return CEF_PERMISSION_TYPE_WINDOW_PLACEMENT; + } + + NOTREACHED(); + return CEF_PERMISSION_TYPE_NONE; +} + +uint32_t GetRequestedPermissions( + permissions::PermissionPrompt::Delegate* delegate) { + uint32_t permissions = CEF_PERMISSION_TYPE_NONE; + for (const auto* request : delegate->Requests()) { + permissions |= GetCefRequestType(request->request_type()); + } + return permissions; +} + +std::unique_ptr CreatePermissionPromptImpl( + content::WebContents* web_contents, + permissions::PermissionPrompt::Delegate* delegate, + bool* default_handling) { + CEF_REQUIRE_UIT(); + + if (auto browser = CefBrowserHostBase::GetBrowserForContents(web_contents)) { + if (auto client = browser->GetClient()) { + if (auto handler = client->GetPermissionHandler()) { + auto permission_prompt = + std::make_unique(delegate); + + const auto prompt_id = ++g_next_prompt_id; + auto callback = + base::BindOnce(&ExecuteResult, browser, prompt_id, + permission_prompt->MakeDelegateCallback()); + + CefRefPtr callbackImpl( + new CefPermissionPromptCallbackImpl(std::move(callback))); + bool handled = handler->OnShowPermissionPrompt( + browser, prompt_id, delegate->GetRequestingOrigin().spec(), + GetRequestedPermissions(delegate), callbackImpl.get()); + + if (callbackImpl->IsDisconnected() || handled) { + // Callback execution will be async. + LOG_IF(ERROR, !handled) + << "Should return true from OnShowPermissionPrompt when " + "executing the callback"; + *default_handling = false; + permission_prompt->AttachClientCallback(callbackImpl); + return permission_prompt; + } else { + // Proceed with default handling. |callback| is discarded without + // execution. + callback = callbackImpl->Disconnect(); + } + } + } + } + + if (cef::IsAlloyRuntimeEnabled()) { + LOG(INFO) << "Implement OnShowPermissionPrompt to override default IGNORE " + "handling of permission prompts."; + } + + // Proceed with default handling. This will be IGNORE with the Alloy runtime + // and default UI prompt with the Chrome runtime. + *default_handling = true; + return nullptr; +} + +} // namespace + +void RegisterCreateCallback() { + SetCreatePermissionPromptFunction(&CreatePermissionPromptImpl); +} + +} // namespace permission_prompt diff --git a/libcef/browser/permission_prompt.h b/libcef/browser/permission_prompt.h new file mode 100644 index 000000000..224eb045d --- /dev/null +++ b/libcef/browser/permission_prompt.h @@ -0,0 +1,15 @@ +// Copyright 2022 The Chromium Embedded Framework 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 CEF_LIBCEF_BROWSER_PERMISSION_PROMPT_H_ +#define CEF_LIBCEF_BROWSER_PERMISSION_PROMPT_H_ +#pragma once + +namespace permission_prompt { + +void RegisterCreateCallback(); + +} // namespace permission_prompt + +#endif // CEF_LIBCEF_BROWSER_PERMISSION_PROMPT_H_ diff --git a/libcef/browser/prefs/browser_prefs.cc b/libcef/browser/prefs/browser_prefs.cc index 76a6fbfee..e670b5fab 100644 --- a/libcef/browser/prefs/browser_prefs.cc +++ b/libcef/browser/prefs/browser_prefs.cc @@ -23,6 +23,7 @@ #include "chrome/browser/first_party_sets/first_party_sets_pref_names.h" #include "chrome/browser/media/media_device_id_salt.h" #include "chrome/browser/media/router/media_router_feature.h" +#include "chrome/browser/media/webrtc/permission_bubble_media_access_handler.h" #include "chrome/browser/net/profile_network_context_service.h" #include "chrome/browser/net/system_network_context_manager.h" #include "chrome/browser/plugins/plugin_info_host_impl.h" @@ -46,6 +47,7 @@ #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/language/core/browser/language_prefs.h" #include "components/language/core/browser/pref_names.h" +#include "components/permissions/permission_actions_history.h" #include "components/pref_registry/pref_registry_syncable.h" #include "components/prefs/json_pref_store.h" #include "components/prefs/pref_filter.h" @@ -224,6 +226,7 @@ std::unique_ptr CreatePrefService(Profile* profile, certificate_transparency::prefs::RegisterPrefs(registry.get()); flags_ui::PrefServiceFlagsStorage::RegisterPrefs(registry.get()); media_router::RegisterLocalStatePrefs(registry.get()); + permissions::PermissionActionsHistory::RegisterProfilePrefs(registry.get()); PluginInfoHostImpl::RegisterUserPrefs(registry.get()); PrefProxyConfigTrackerImpl::RegisterPrefs(registry.get()); ProfileNetworkContextService::RegisterLocalStatePrefs(registry.get()); @@ -269,6 +272,7 @@ std::unique_ptr CreatePrefService(Profile* profile, language::LanguagePrefs::RegisterProfilePrefs(registry.get()); media_router::RegisterProfilePrefs(registry.get()); MediaDeviceIDSalt::RegisterProfilePrefs(registry.get()); + PermissionBubbleMediaAccessHandler::RegisterProfilePrefs(registry.get()); prefetch::RegisterPredictionOptionsProfilePrefs(registry.get()); ProfileNetworkContextService::RegisterProfilePrefs(registry.get()); safe_browsing::RegisterProfilePrefs(registry.get()); diff --git a/libcef_dll/cpptoc/permission_handler_cpptoc.cc b/libcef_dll/cpptoc/permission_handler_cpptoc.cc index 3f8dd5ae4..adc9f68ca 100644 --- a/libcef_dll/cpptoc/permission_handler_cpptoc.cc +++ b/libcef_dll/cpptoc/permission_handler_cpptoc.cc @@ -9,13 +9,14 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=9c8d3c2295998f7a976967aa7f4b6a6da7c5c53d$ +// $hash=f137c0d7ae5fef1a6478f9416f6fe3d12a66d975$ // #include "libcef_dll/cpptoc/permission_handler_cpptoc.h" #include "libcef_dll/ctocpp/browser_ctocpp.h" #include "libcef_dll/ctocpp/frame_ctocpp.h" #include "libcef_dll/ctocpp/media_access_callback_ctocpp.h" +#include "libcef_dll/ctocpp/permission_prompt_callback_ctocpp.h" #include "libcef_dll/shutdown_checker.h" namespace { @@ -26,7 +27,7 @@ int CEF_CALLBACK permission_handler_on_request_media_access_permission( struct _cef_permission_handler_t* self, cef_browser_t* browser, cef_frame_t* frame, - const cef_string_t* requesting_url, + const cef_string_t* requesting_origin, uint32 requested_permissions, cef_media_access_callback_t* callback) { shutdown_checker::AssertNotShutdown(); @@ -44,9 +45,9 @@ int CEF_CALLBACK permission_handler_on_request_media_access_permission( DCHECK(frame); if (!frame) return 0; - // Verify param: requesting_url; type: string_byref_const - DCHECK(requesting_url); - if (!requesting_url) + // Verify param: requesting_origin; type: string_byref_const + DCHECK(requesting_origin); + if (!requesting_origin) return 0; // Verify param: callback; type: refptr_diff DCHECK(callback); @@ -57,13 +58,71 @@ int CEF_CALLBACK permission_handler_on_request_media_access_permission( bool _retval = CefPermissionHandlerCppToC::Get(self)->OnRequestMediaAccessPermission( CefBrowserCToCpp::Wrap(browser), CefFrameCToCpp::Wrap(frame), - CefString(requesting_url), requested_permissions, + CefString(requesting_origin), requested_permissions, CefMediaAccessCallbackCToCpp::Wrap(callback)); // Return type: bool return _retval; } +int CEF_CALLBACK permission_handler_on_show_permission_prompt( + struct _cef_permission_handler_t* self, + cef_browser_t* browser, + uint64 prompt_id, + const cef_string_t* requesting_origin, + uint32 requested_permissions, + cef_permission_prompt_callback_t* callback) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return 0; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return 0; + // Verify param: requesting_origin; type: string_byref_const + DCHECK(requesting_origin); + if (!requesting_origin) + return 0; + // Verify param: callback; type: refptr_diff + DCHECK(callback); + if (!callback) + return 0; + + // Execute + bool _retval = CefPermissionHandlerCppToC::Get(self)->OnShowPermissionPrompt( + CefBrowserCToCpp::Wrap(browser), prompt_id, CefString(requesting_origin), + requested_permissions, CefPermissionPromptCallbackCToCpp::Wrap(callback)); + + // Return type: bool + return _retval; +} + +void CEF_CALLBACK permission_handler_on_dismiss_permission_prompt( + struct _cef_permission_handler_t* self, + cef_browser_t* browser, + uint64 prompt_id, + cef_permission_request_result_t result) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) + return; + + // Execute + CefPermissionHandlerCppToC::Get(self)->OnDismissPermissionPrompt( + CefBrowserCToCpp::Wrap(browser), prompt_id, result); +} + } // namespace // CONSTRUCTOR - Do not edit by hand. @@ -71,6 +130,10 @@ int CEF_CALLBACK permission_handler_on_request_media_access_permission( CefPermissionHandlerCppToC::CefPermissionHandlerCppToC() { GetStruct()->on_request_media_access_permission = permission_handler_on_request_media_access_permission; + GetStruct()->on_show_permission_prompt = + permission_handler_on_show_permission_prompt; + GetStruct()->on_dismiss_permission_prompt = + permission_handler_on_dismiss_permission_prompt; } // DESTRUCTOR - Do not edit by hand. diff --git a/libcef_dll/cpptoc/permission_prompt_callback_cpptoc.cc b/libcef_dll/cpptoc/permission_prompt_callback_cpptoc.cc new file mode 100644 index 000000000..4dd6a7001 --- /dev/null +++ b/libcef_dll/cpptoc/permission_prompt_callback_cpptoc.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2022 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=e2297d672c66d4530e85d4c4761d1adeabe7134c$ +// + +#include "libcef_dll/cpptoc/permission_prompt_callback_cpptoc.h" +#include "libcef_dll/shutdown_checker.h" + +namespace { + +// MEMBER FUNCTIONS - Body may be edited by hand. + +void CEF_CALLBACK +permission_prompt_callback_cont(struct _cef_permission_prompt_callback_t* self, + cef_permission_request_result_t result) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + + // Execute + CefPermissionPromptCallbackCppToC::Get(self)->Continue(result); +} + +} // namespace + +// CONSTRUCTOR - Do not edit by hand. + +CefPermissionPromptCallbackCppToC::CefPermissionPromptCallbackCppToC() { + GetStruct()->cont = permission_prompt_callback_cont; +} + +// DESTRUCTOR - Do not edit by hand. + +CefPermissionPromptCallbackCppToC::~CefPermissionPromptCallbackCppToC() { + shutdown_checker::AssertNotShutdown(); +} + +template <> +CefRefPtr +CefCppToCRefCounted:: + UnwrapDerived(CefWrapperType type, cef_permission_prompt_callback_t* s) { + NOTREACHED() << "Unexpected class type: " << type; + return nullptr; +} + +template <> +CefWrapperType + CefCppToCRefCounted::kWrapperType = + WT_PERMISSION_PROMPT_CALLBACK; diff --git a/libcef_dll/cpptoc/permission_prompt_callback_cpptoc.h b/libcef_dll/cpptoc/permission_prompt_callback_cpptoc.h new file mode 100644 index 000000000..1f94631be --- /dev/null +++ b/libcef_dll/cpptoc/permission_prompt_callback_cpptoc.h @@ -0,0 +1,38 @@ +// Copyright (c) 2022 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=03956400a2d9931dd114105e8512b8e87d31f305$ +// + +#ifndef CEF_LIBCEF_DLL_CPPTOC_PERMISSION_PROMPT_CALLBACK_CPPTOC_H_ +#define CEF_LIBCEF_DLL_CPPTOC_PERMISSION_PROMPT_CALLBACK_CPPTOC_H_ +#pragma once + +#if !defined(BUILDING_CEF_SHARED) +#error This file can be included DLL-side only +#endif + +#include "include/capi/cef_permission_handler_capi.h" +#include "include/cef_permission_handler.h" +#include "libcef_dll/cpptoc/cpptoc_ref_counted.h" + +// Wrap a C++ class with a C structure. +// This class may be instantiated and accessed DLL-side only. +class CefPermissionPromptCallbackCppToC + : public CefCppToCRefCounted { + public: + CefPermissionPromptCallbackCppToC(); + virtual ~CefPermissionPromptCallbackCppToC(); +}; + +#endif // CEF_LIBCEF_DLL_CPPTOC_PERMISSION_PROMPT_CALLBACK_CPPTOC_H_ diff --git a/libcef_dll/ctocpp/permission_handler_ctocpp.cc b/libcef_dll/ctocpp/permission_handler_ctocpp.cc index e1ca9c19b..50e2339ad 100644 --- a/libcef_dll/ctocpp/permission_handler_ctocpp.cc +++ b/libcef_dll/ctocpp/permission_handler_ctocpp.cc @@ -9,13 +9,14 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=f9073ac6e5a3634a3bf3c919ac1b0a9c607b4398$ +// $hash=d7ce736678ba0f43fa3f556d22b8afa2409221fb$ // #include "libcef_dll/ctocpp/permission_handler_ctocpp.h" #include "libcef_dll/cpptoc/browser_cpptoc.h" #include "libcef_dll/cpptoc/frame_cpptoc.h" #include "libcef_dll/cpptoc/media_access_callback_cpptoc.h" +#include "libcef_dll/cpptoc/permission_prompt_callback_cpptoc.h" #include "libcef_dll/shutdown_checker.h" // VIRTUAL METHODS - Body may be edited by hand. @@ -24,7 +25,7 @@ NO_SANITIZE("cfi-icall") bool CefPermissionHandlerCToCpp::OnRequestMediaAccessPermission( CefRefPtr browser, CefRefPtr frame, - const CefString& requesting_url, + const CefString& requesting_origin, uint32 requested_permissions, CefRefPtr callback) { shutdown_checker::AssertNotShutdown(); @@ -43,9 +44,9 @@ bool CefPermissionHandlerCToCpp::OnRequestMediaAccessPermission( DCHECK(frame.get()); if (!frame.get()) return false; - // Verify param: requesting_url; type: string_byref_const - DCHECK(!requesting_url.empty()); - if (requesting_url.empty()) + // Verify param: requesting_origin; type: string_byref_const + DCHECK(!requesting_origin.empty()); + if (requesting_origin.empty()) return false; // Verify param: callback; type: refptr_diff DCHECK(callback.get()); @@ -55,13 +56,74 @@ bool CefPermissionHandlerCToCpp::OnRequestMediaAccessPermission( // Execute int _retval = _struct->on_request_media_access_permission( _struct, CefBrowserCppToC::Wrap(browser), CefFrameCppToC::Wrap(frame), - requesting_url.GetStruct(), requested_permissions, + requesting_origin.GetStruct(), requested_permissions, CefMediaAccessCallbackCppToC::Wrap(callback)); // Return type: bool return _retval ? true : false; } +NO_SANITIZE("cfi-icall") +bool CefPermissionHandlerCToCpp::OnShowPermissionPrompt( + CefRefPtr browser, + uint64 prompt_id, + const CefString& requesting_origin, + uint32 requested_permissions, + CefRefPtr callback) { + shutdown_checker::AssertNotShutdown(); + + cef_permission_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_show_permission_prompt)) + return false; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return false; + // Verify param: requesting_origin; type: string_byref_const + DCHECK(!requesting_origin.empty()); + if (requesting_origin.empty()) + return false; + // Verify param: callback; type: refptr_diff + DCHECK(callback.get()); + if (!callback.get()) + return false; + + // Execute + int _retval = _struct->on_show_permission_prompt( + _struct, CefBrowserCppToC::Wrap(browser), prompt_id, + requesting_origin.GetStruct(), requested_permissions, + CefPermissionPromptCallbackCppToC::Wrap(callback)); + + // Return type: bool + return _retval ? true : false; +} + +NO_SANITIZE("cfi-icall") +void CefPermissionHandlerCToCpp::OnDismissPermissionPrompt( + CefRefPtr browser, + uint64 prompt_id, + cef_permission_request_result_t result) { + shutdown_checker::AssertNotShutdown(); + + cef_permission_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_dismiss_permission_prompt)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) + return; + + // Execute + _struct->on_dismiss_permission_prompt( + _struct, CefBrowserCppToC::Wrap(browser), prompt_id, result); +} + // CONSTRUCTOR - Do not edit by hand. CefPermissionHandlerCToCpp::CefPermissionHandlerCToCpp() {} diff --git a/libcef_dll/ctocpp/permission_handler_ctocpp.h b/libcef_dll/ctocpp/permission_handler_ctocpp.h index 9daff0564..717d2b79e 100644 --- a/libcef_dll/ctocpp/permission_handler_ctocpp.h +++ b/libcef_dll/ctocpp/permission_handler_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=1c8392555b99119b866989c5461ad48a0ac662b5$ +// $hash=5a55c2f36920a4ab385570e9be0f146d99477edb$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_PERMISSION_HANDLER_CTOCPP_H_ @@ -38,9 +38,19 @@ class CefPermissionHandlerCToCpp bool OnRequestMediaAccessPermission( CefRefPtr browser, CefRefPtr frame, - const CefString& requesting_url, + const CefString& requesting_origin, uint32 requested_permissions, CefRefPtr callback) override; + bool OnShowPermissionPrompt( + CefRefPtr browser, + uint64 prompt_id, + const CefString& requesting_origin, + uint32 requested_permissions, + CefRefPtr callback) override; + void OnDismissPermissionPrompt( + CefRefPtr browser, + uint64 prompt_id, + cef_permission_request_result_t result) override; }; #endif // CEF_LIBCEF_DLL_CTOCPP_PERMISSION_HANDLER_CTOCPP_H_ diff --git a/libcef_dll/ctocpp/permission_prompt_callback_ctocpp.cc b/libcef_dll/ctocpp/permission_prompt_callback_ctocpp.cc new file mode 100644 index 000000000..fca22dd6d --- /dev/null +++ b/libcef_dll/ctocpp/permission_prompt_callback_ctocpp.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2022 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=b149d327d3972d670f974dc92900acfbdfffff87$ +// + +#include "libcef_dll/ctocpp/permission_prompt_callback_ctocpp.h" +#include "libcef_dll/shutdown_checker.h" + +// VIRTUAL METHODS - Body may be edited by hand. + +NO_SANITIZE("cfi-icall") +void CefPermissionPromptCallbackCToCpp::Continue( + cef_permission_request_result_t result) { + shutdown_checker::AssertNotShutdown(); + + cef_permission_prompt_callback_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, cont)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + _struct->cont(_struct, result); +} + +// CONSTRUCTOR - Do not edit by hand. + +CefPermissionPromptCallbackCToCpp::CefPermissionPromptCallbackCToCpp() {} + +// DESTRUCTOR - Do not edit by hand. + +CefPermissionPromptCallbackCToCpp::~CefPermissionPromptCallbackCToCpp() { + shutdown_checker::AssertNotShutdown(); +} + +template <> +cef_permission_prompt_callback_t* +CefCToCppRefCounted:: + UnwrapDerived(CefWrapperType type, CefPermissionPromptCallback* c) { + NOTREACHED() << "Unexpected class type: " << type; + return nullptr; +} + +template <> +CefWrapperType + CefCToCppRefCounted::kWrapperType = + WT_PERMISSION_PROMPT_CALLBACK; diff --git a/libcef_dll/ctocpp/permission_prompt_callback_ctocpp.h b/libcef_dll/ctocpp/permission_prompt_callback_ctocpp.h new file mode 100644 index 000000000..836db6b8e --- /dev/null +++ b/libcef_dll/ctocpp/permission_prompt_callback_ctocpp.h @@ -0,0 +1,41 @@ +// Copyright (c) 2022 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +// +// --------------------------------------------------------------------------- +// +// This file was generated by the CEF translator tool. If making changes by +// hand only do so within the body of existing method and function +// implementations. See the translator.README.txt file in the tools directory +// for more information. +// +// $hash=bdf5b8df2a3d5555f42982cd741ae13f3d1a67d1$ +// + +#ifndef CEF_LIBCEF_DLL_CTOCPP_PERMISSION_PROMPT_CALLBACK_CTOCPP_H_ +#define CEF_LIBCEF_DLL_CTOCPP_PERMISSION_PROMPT_CALLBACK_CTOCPP_H_ +#pragma once + +#if !defined(WRAPPING_CEF_SHARED) +#error This file can be included wrapper-side only +#endif + +#include "include/capi/cef_permission_handler_capi.h" +#include "include/cef_permission_handler.h" +#include "libcef_dll/ctocpp/ctocpp_ref_counted.h" + +// Wrap a C structure with a C++ class. +// This class may be instantiated and accessed wrapper-side only. +class CefPermissionPromptCallbackCToCpp + : public CefCToCppRefCounted { + public: + CefPermissionPromptCallbackCToCpp(); + virtual ~CefPermissionPromptCallbackCToCpp(); + + // CefPermissionPromptCallback methods. + void Continue(cef_permission_request_result_t result) override; +}; + +#endif // CEF_LIBCEF_DLL_CTOCPP_PERMISSION_PROMPT_CALLBACK_CTOCPP_H_ diff --git a/libcef_dll/wrapper_types.h b/libcef_dll/wrapper_types.h index 10695227e..2e7cbb728 100644 --- a/libcef_dll/wrapper_types.h +++ b/libcef_dll/wrapper_types.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=27716558f027af36498437317407384392a34b71$ +// $hash=5e9ff0d723683c16009ad0c7c697ea461b653aaf$ // #ifndef CEF_LIBCEF_DLL_WRAPPER_TYPES_H_ @@ -97,6 +97,7 @@ enum CefWrapperType { WT_PANEL_DELEGATE, WT_PDF_PRINT_CALLBACK, WT_PERMISSION_HANDLER, + WT_PERMISSION_PROMPT_CALLBACK, WT_POST_DATA, WT_POST_DATA_ELEMENT, WT_PRINT_DIALOG_CALLBACK, diff --git a/patch/patch.cfg b/patch/patch.cfg index 79768977a..9971640bb 100644 --- a/patch/patch.cfg +++ b/patch/patch.cfg @@ -253,6 +253,11 @@ patches = [ # https://bitbucket.org/chromiumembedded/cef/issues/2830 'name': 'chrome_browser_net_proxy', }, + { + # Support override of CreatePermissionPrompt. + # https://bitbucket.org/chromiumembedded/cef/issues/3352 + 'name': 'chrome_browser_permission_prompt', + }, { # alloy: Don't initialize ExtensionSystemFactory when extensions are # disabled. diff --git a/patch/patches/chrome_browser_permission_prompt.patch b/patch/patches/chrome_browser_permission_prompt.patch new file mode 100644 index 000000000..e6394254b --- /dev/null +++ b/patch/patches/chrome_browser_permission_prompt.patch @@ -0,0 +1,73 @@ +diff --git chrome/browser/permissions/chrome_permissions_client.cc chrome/browser/permissions/chrome_permissions_client.cc +index 894a7424580ac..fa78c8dabbdd2 100644 +--- chrome/browser/permissions/chrome_permissions_client.cc ++++ chrome/browser/permissions/chrome_permissions_client.cc +@@ -12,6 +12,7 @@ + #include "base/strings/string_util.h" + #include "build/build_config.h" + #include "build/chromeos_buildflags.h" ++#include "cef/libcef/features/runtime.h" + #include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h" + #include "chrome/browser/content_settings/cookie_settings_factory.h" + #include "chrome/browser/content_settings/host_content_settings_map_factory.h" +@@ -212,6 +213,9 @@ permissions::PermissionManager* ChromePermissionsClient::GetPermissionManager( + double ChromePermissionsClient::GetSiteEngagementScore( + content::BrowserContext* browser_context, + const GURL& origin) { ++ // No SiteEngagementService with the Alloy runtime. ++ if (cef::IsAlloyRuntimeEnabled()) ++ return 0.0; + return site_engagement::SiteEngagementService::Get( + Profile::FromBrowserContext(browser_context)) + ->GetScore(origin); +diff --git chrome/browser/ui/permission_bubble/permission_prompt.h chrome/browser/ui/permission_bubble/permission_prompt.h +index c2836d15eba30..0c03c2b4666a6 100644 +--- chrome/browser/ui/permission_bubble/permission_prompt.h ++++ chrome/browser/ui/permission_bubble/permission_prompt.h +@@ -11,6 +11,13 @@ namespace content { + class WebContents; + } + ++using CreatePermissionPromptFunctionPtr = ++ std::unique_ptr (*)( ++ content::WebContents* web_contents, ++ permissions::PermissionPrompt::Delegate* delegate, ++ bool* default_handling); ++void SetCreatePermissionPromptFunction(CreatePermissionPromptFunctionPtr); ++ + // Factory function to create permission prompts for chrome. + std::unique_ptr CreatePermissionPrompt( + content::WebContents* web_contents, +diff --git chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc +index 70e37336a5001..a2df1bd28c994 100644 +--- chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc ++++ chrome/browser/ui/views/permission_bubble/permission_prompt_impl.cc +@@ -100,11 +100,28 @@ bool ShouldBubbleStartOpen(permissions::PermissionPrompt::Delegate* delegate) { + return false; + } + ++CreatePermissionPromptFunctionPtr g_create_permission_prompt_ptr = nullptr; ++ + } // namespace + ++void SetCreatePermissionPromptFunction( ++ CreatePermissionPromptFunctionPtr ptr) { ++ g_create_permission_prompt_ptr = ptr; ++} ++ + std::unique_ptr CreatePermissionPrompt( + content::WebContents* web_contents, + permissions::PermissionPrompt::Delegate* delegate) { ++ if (g_create_permission_prompt_ptr) { ++ bool default_handling = true; ++ auto prompt = g_create_permission_prompt_ptr(web_contents, delegate, ++ &default_handling); ++ if (prompt) ++ return prompt; ++ if (!default_handling) ++ return nullptr; ++ } ++ + Browser* browser = chrome::FindBrowserWithWebContents(web_contents); + if (!browser) { + DLOG(WARNING) << "Permission prompt suppressed because the WebContents is " diff --git a/tests/cefclient/browser/client_handler.cc b/tests/cefclient/browser/client_handler.cc index c6d30eca8..d0e813411 100644 --- a/tests/cefclient/browser/client_handler.cc +++ b/tests/cefclient/browser/client_handler.cc @@ -864,7 +864,7 @@ void ClientHandler::OnLoadError(CefRefPtr browser, bool ClientHandler::OnRequestMediaAccessPermission( CefRefPtr browser, CefRefPtr frame, - const CefString& requesting_url, + const CefString& requesting_origin, uint32 requested_permissions, CefRefPtr callback) { callback->Continue(media_handling_disabled_ ? CEF_MEDIA_PERMISSION_NONE diff --git a/tests/cefclient/browser/client_handler.h b/tests/cefclient/browser/client_handler.h index 9e5d73c01..80272c92c 100644 --- a/tests/cefclient/browser/client_handler.h +++ b/tests/cefclient/browser/client_handler.h @@ -237,7 +237,7 @@ class ClientHandler : public CefClient, bool OnRequestMediaAccessPermission( CefRefPtr browser, CefRefPtr frame, - const CefString& requesting_url, + const CefString& requesting_origin, uint32 requested_permissions, CefRefPtr callback) override; diff --git a/tests/ceftests/media_access_unittest.cc b/tests/ceftests/media_access_unittest.cc index 4978934a1..afa2f2fd8 100644 --- a/tests/ceftests/media_access_unittest.cc +++ b/tests/ceftests/media_access_unittest.cc @@ -20,6 +20,7 @@ namespace { // Media access requires HTTPS. const char kMediaUrl[] = "https://media-access-test/media.html"; +const char kMediaOrigin[] = "https://media-access-test/"; // Browser-side app delegate. class MediaAccessBrowserTest : public client::ClientAppBrowser::Delegate, @@ -46,6 +47,7 @@ class TestSetup { bool deny_implicitly = false; bool continue_async = false; + TrackCallback got_request; TrackCallback got_success; TrackCallback got_audio; TrackCallback got_video; @@ -64,10 +66,10 @@ class MediaAccessTestHandler : public TestHandler, public CefPermissionHandler { CefRefPtr callback) override { std::string newUrl = request->GetURL(); if (newUrl.find("tests/exit") != std::string::npos) { - CefURLParts url_parts; - CefParseURL(newUrl, url_parts); if (newUrl.find("SUCCESS") != std::string::npos) { + EXPECT_FALSE(test_setup_->got_success); test_setup_->got_success.yes(); + std::string data_string = newUrl.substr(newUrl.find("&data=") + std::string("&data=").length()); std::string data_string_decoded = CefURIDecode( @@ -122,7 +124,7 @@ class MediaAccessTestHandler : public TestHandler, public CefPermissionHandler { "> 0, got_video_track: stream.getVideoTracks().length > 0});" "})" ".catch(function(err) {" - "console.log(err);" + "console.log(err.toString());" "onResult(`FAILURE`);" "});" "" @@ -146,31 +148,25 @@ class MediaAccessTestHandler : public TestHandler, public CefPermissionHandler { return this; } - void CompleteTest() { - if (!CefCurrentlyOn(TID_UI)) { - CefPostTask(TID_UI, - base::BindOnce(&MediaAccessTestHandler::CompleteTest, this)); - return; - } - - DestroyTest(); - } - bool OnRequestMediaAccessPermission( CefRefPtr browser, CefRefPtr frame, - const CefString& requesting_url, + const CefString& requesting_origin, uint32 requested_permissions, CefRefPtr callback) override { EXPECT_UI_THREAD(); EXPECT_TRUE(frame->IsMain()); + EXPECT_EQ(requested_permissions, request_); + EXPECT_STREQ(kMediaOrigin, requesting_origin.ToString().c_str()); + + EXPECT_FALSE(test_setup_->got_request); + test_setup_->got_request.yes(); + if (test_setup_->deny_implicitly) { return false; } - EXPECT_EQ(requested_permissions, request_); - if (test_setup_->continue_async) { CefPostTask(TID_UI, base::BindOnce(&CefMediaAccessCallback::Continue, callback, response_)); @@ -238,6 +234,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningFalse) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -255,6 +252,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningNoPermission) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -273,6 +271,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningNoPermissionAsync) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -288,6 +287,7 @@ TEST(MediaAccessTest, DeviceFailureWhenRequestingAudioButReturningVideo) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -303,6 +303,7 @@ TEST(MediaAccessTest, DeviceFailureWhenRequestingVideoButReturningAudio) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -320,6 +321,7 @@ TEST(MediaAccessTest, DevicePartialFailureReturningVideo) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -337,6 +339,7 @@ TEST(MediaAccessTest, DevicePartialFailureReturningAudio) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -354,6 +357,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture1) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -371,6 +375,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture2) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -386,6 +391,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture3) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -401,6 +407,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture4) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -416,6 +423,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture5) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -431,6 +439,7 @@ TEST(MediaAccessTest, DeviceFailureWhenReturningScreenCapture6) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -446,6 +455,7 @@ TEST(MediaAccessTest, DeviceSuccessAudioOnly) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_TRUE(test_setup.got_success); EXPECT_TRUE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -461,6 +471,7 @@ TEST(MediaAccessTest, DeviceSuccessVideoOnly) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_TRUE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_TRUE(test_setup.got_video); @@ -479,6 +490,7 @@ TEST(MediaAccessTest, DeviceSuccessAudioVideo) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_TRUE(test_setup.got_success); EXPECT_TRUE(test_setup.got_audio); EXPECT_TRUE(test_setup.got_video); @@ -498,6 +510,7 @@ TEST(MediaAccessTest, DeviceSuccessAudioVideoAsync) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_TRUE(test_setup.got_success); EXPECT_TRUE(test_setup.got_audio); EXPECT_TRUE(test_setup.got_video); @@ -516,6 +529,7 @@ TEST(MediaAccessTest, DesktopFailureWhenReturningNoPermission) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -531,6 +545,7 @@ TEST(MediaAccessTest, DesktopFailureWhenRequestingVideoButReturningAudio) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); @@ -548,6 +563,7 @@ TEST(MediaAccessTest, DesktopPartialSuccessReturningVideo) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_TRUE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_TRUE(test_setup.got_video); @@ -564,6 +580,7 @@ TEST(MediaAccessTest, DesktopPartialFailureReturningAudio) { handler->ExecuteTest(); ReleaseAndWaitForDestructor(handler); + EXPECT_TRUE(test_setup.got_request); EXPECT_FALSE(test_setup.got_success); EXPECT_FALSE(test_setup.got_audio); EXPECT_FALSE(test_setup.got_video); diff --git a/tests/ceftests/permission_prompt_unittest.cc b/tests/ceftests/permission_prompt_unittest.cc new file mode 100644 index 000000000..593012445 --- /dev/null +++ b/tests/ceftests/permission_prompt_unittest.cc @@ -0,0 +1,493 @@ +// Copyright 2022 The Chromium Embedded Framework Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +#include +#include + +#include "include/base/cef_bind.h" +#include "include/cef_parser.h" +#include "include/cef_permission_handler.h" +#include "include/cef_request_context_handler.h" +#include "include/wrapper/cef_closure_task.h" +#include "include/wrapper/cef_stream_resource_handler.h" +#include "tests/ceftests/test_handler.h" +#include "tests/ceftests/test_suite.h" +#include "tests/gtest/include/gtest/gtest.h" +#include "tests/shared/browser/client_app_browser.h" + +namespace { + +// Most permissions require HTTPS. +constexpr char kPromptUrl[] = "https://permission-prompt-test/prompt.html"; +constexpr char kPromptOrigin[] = "https://permission-prompt-test/"; + +constexpr char kPromptNavUrl[] = "https://permission-prompt-test/nav.html"; + +class TestSetup { + public: + TestSetup() {} + + // CONFIGURATION + + // Deny the prompt by returning false in OnShowPermissionPrompt. + bool deny_implicitly = false; + + // Deny the prompt (implicitly) by not triggering it via a user gesture to + // begin with. + bool deny_no_gesture = false; + + // Deny the prompt by returning true in OnShowPermissionPrompt but then never + // calling CefPermissionPromptCallback::Continue. + bool deny_with_navigation = false; + + // Don't synchronously execute the callback in OnShowPermissionPrompt. + bool continue_async = false; + + // RESULTS + + // Method callbacks. + TrackCallback got_prompt; + TrackCallback got_dismiss; + + // JS success state. + TrackCallback got_js_success; + TrackCallback got_js_success_data; + + // JS error state. + TrackCallback got_js_error; + std::string js_error_str; + + // JS timeout state. + TrackCallback got_js_timeout; +}; + +class PermissionPromptTestHandler : public TestHandler, + public CefPermissionHandler { + public: + PermissionPromptTestHandler(TestSetup* tr, + uint32 request, + cef_permission_request_result_t result) + : test_setup_(tr), request_(request), result_(result) {} + + cef_return_value_t OnBeforeResourceLoad( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr callback) override { + std::string newUrl = request->GetURL(); + if (newUrl.find("tests/exit") != std::string::npos) { + if (newUrl.find("SUCCESS") != std::string::npos) { + EXPECT_FALSE(test_setup_->got_js_success); + test_setup_->got_js_success.yes(); + + auto dict = ParseURLData(newUrl); + if (dict->GetBool("got_data")) { + test_setup_->got_js_success_data.yes(); + } + } else if (newUrl.find("ERROR") != std::string::npos) { + EXPECT_FALSE(test_setup_->got_js_error); + test_setup_->got_js_error.yes(); + + auto dict = ParseURLData(newUrl); + test_setup_->js_error_str = dict->GetString("error_str"); + } else if (newUrl.find("TIMEOUT") != std::string::npos) { + EXPECT_FALSE(test_setup_->got_js_timeout); + test_setup_->got_js_timeout.yes(); + } + + DestroyTest(); + return RV_CANCEL; + } + + return RV_CONTINUE; + } + + void RunTest() override { + std::string page = + "" + "" + ""; + + if (test_setup_->deny_no_gesture) { + // Expect this request to be blocked. See comments on OnLoadEnd. + page += ""; + } else { + page += "CLICK ME"; + } + + page += ""; + + // Create the request context that will use an in-memory cache. + CefRequestContextSettings settings; + CefRefPtr request_context = + CefRequestContext::CreateContext(settings, nullptr); + + AddResource(kPromptUrl, page, "text/html"); + + if (test_setup_->deny_with_navigation) { + AddResource(kPromptNavUrl, "Navigated", + "text/html"); + } + + // Create the browser. + CreateBrowser(kPromptUrl, request_context); + + // Time out the test after a reasonable period of time. + SetTestTimeout(); + } + + CefRefPtr GetPermissionHandler() override { + return this; + } + + void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) override { + if (test_setup_->deny_no_gesture) + return; + + if (test_setup_->deny_with_navigation) { + if (frame->GetURL().ToString() == kPromptNavUrl) { + DestroyTest(); + return; + } + } + + // Begin the permissions request by clicking a link. This is necessary + // because some prompts may be blocked without a transient user activation + // (HasTransientUserActivation returning true in Chromium). + SendClick(browser); + } + + bool OnShowPermissionPrompt( + CefRefPtr browser, + uint64 prompt_id, + const CefString& requesting_origin, + uint32 requested_permissions, + CefRefPtr callback) override { + EXPECT_UI_THREAD(); + + prompt_id_ = prompt_id; + EXPECT_GT(prompt_id, 0U); + + EXPECT_EQ(request_, requested_permissions); + EXPECT_STREQ(kPromptOrigin, requesting_origin.ToString().c_str()); + + EXPECT_FALSE(test_setup_->got_prompt); + test_setup_->got_prompt.yes(); + + if (test_setup_->deny_implicitly) { + // Causes implicit IGNORE result for the permission request. + return false; + } + + if (test_setup_->deny_with_navigation) { + // Handle the permission request, but never execute the callback. + return true; + } + + if (test_setup_->continue_async) { + CefPostTask(TID_UI, base::BindOnce(&CefPermissionPromptCallback::Continue, + callback, result_)); + } else { + callback->Continue(result_); + } + return true; + } + + void OnDismissPermissionPrompt( + CefRefPtr browser, + uint64 prompt_id, + cef_permission_request_result_t result) override { + EXPECT_UI_THREAD(); + EXPECT_EQ(prompt_id_, prompt_id); + EXPECT_EQ(result_, result); + EXPECT_FALSE(test_setup_->got_dismiss); + test_setup_->got_dismiss.yes(); + } + + void DestroyTest() override { + const size_t js_outcome_ct = test_setup_->got_js_success + + test_setup_->got_js_error + + test_setup_->got_js_timeout; + if (test_setup_->deny_with_navigation) { + // Expect no JS outcome. + EXPECT_EQ(0U, js_outcome_ct); + } else { + // Expect a single JS outcome. + EXPECT_EQ(1U, js_outcome_ct); + } + + TestHandler::DestroyTest(); + } + + private: + void SendClick(CefRefPtr browser) { + CefMouseEvent mouse_event; + mouse_event.x = 20; + mouse_event.y = 20; + + // Add some delay to avoid having events dropped or rate limited. + CefPostDelayedTask( + TID_UI, + base::BindOnce(&CefBrowserHost::SendMouseClickEvent, browser->GetHost(), + mouse_event, MBT_LEFT, false, 1), + 50); + CefPostDelayedTask( + TID_UI, + base::BindOnce(&CefBrowserHost::SendMouseClickEvent, browser->GetHost(), + mouse_event, MBT_LEFT, true, 1), + 100); + } + + CefRefPtr ParseURLData(const std::string& url) { + const std::string& find_str = "&data="; + const std::string& data_string = + url.substr(url.find(find_str) + std::string(find_str).length()); + const std::string& data_string_decoded = CefURIDecode( + data_string, false, + static_cast( + UU_SPACES | UU_URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS)); + auto obj = + CefParseJSON(data_string_decoded, JSON_PARSER_ALLOW_TRAILING_COMMAS); + return obj->GetDictionary(); + } + + TestSetup* const test_setup_; + const uint32 request_; + const cef_permission_request_result_t result_; + uint64 prompt_id_ = 0U; + + IMPLEMENT_REFCOUNTING(PermissionPromptTestHandler); +}; + +} // namespace + +// Window placement permission requests. +TEST(PermissionPromptTest, WindowPlacementReturningFalse) { + TestSetup test_setup; + test_setup.deny_implicitly = true; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_IGNORE); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + // No OnDismissPermissionPrompt callback for default handling. + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_timeout); + EXPECT_FALSE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementNoGesture) { + TestSetup test_setup; + test_setup.deny_no_gesture = true; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_IGNORE); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + // No OnShowPermissionPrompt or OnDismissPermissionPrompt callbacks for + // prompts that are blocked. + EXPECT_FALSE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_error); + EXPECT_STREQ("NotAllowedError: Permission decision deferred.", + test_setup.js_error_str.c_str()); + EXPECT_FALSE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementNoContinue) { + TestSetup test_setup; + test_setup.deny_with_navigation = true; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_IGNORE); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + // Callbacks but no JS result. + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementResultAccept) { + TestSetup test_setup; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_ACCEPT); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_success); + EXPECT_TRUE(test_setup.got_js_success_data); + EXPECT_TRUE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementResultAcceptAsync) { + TestSetup test_setup; + test_setup.continue_async = true; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_ACCEPT); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_success); + EXPECT_TRUE(test_setup.got_js_success_data); + EXPECT_TRUE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementResultDeny) { + TestSetup test_setup; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_DENY); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_error); + EXPECT_STREQ("NotAllowedError: Permission denied.", + test_setup.js_error_str.c_str()); + EXPECT_TRUE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementResultDenyAsync) { + TestSetup test_setup; + test_setup.continue_async = true; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_DENY); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_error); + EXPECT_STREQ("NotAllowedError: Permission denied.", + test_setup.js_error_str.c_str()); + EXPECT_TRUE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementResultDismiss) { + TestSetup test_setup; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_DISMISS); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_error); + EXPECT_STREQ("NotAllowedError: Permission decision deferred.", + test_setup.js_error_str.c_str()); + EXPECT_TRUE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementResultDismissAsync) { + TestSetup test_setup; + test_setup.continue_async = true; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_DISMISS); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_error); + EXPECT_STREQ("NotAllowedError: Permission decision deferred.", + test_setup.js_error_str.c_str()); + EXPECT_TRUE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementResultIgnore) { + TestSetup test_setup; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_IGNORE); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_error); + EXPECT_STREQ("NotAllowedError: Permission decision deferred.", + test_setup.js_error_str.c_str()); + EXPECT_TRUE(test_setup.got_dismiss); +} + +TEST(PermissionPromptTest, WindowPlacementResultIgnoreAsync) { + TestSetup test_setup; + test_setup.continue_async = true; + + CefRefPtr handler = + new PermissionPromptTestHandler(&test_setup, + CEF_PERMISSION_TYPE_WINDOW_PLACEMENT, + CEF_PERMISSION_RESULT_IGNORE); + handler->ExecuteTest(); + ReleaseAndWaitForDestructor(handler); + + EXPECT_TRUE(test_setup.got_prompt); + EXPECT_TRUE(test_setup.got_js_error); + EXPECT_STREQ("NotAllowedError: Permission decision deferred.", + test_setup.js_error_str.c_str()); + EXPECT_TRUE(test_setup.got_dismiss); +}