| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_web_contents.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "ash/keyboard/ui/resources/keyboard_resource_util.h" |
| #include "ash/style/color_util.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/trace_event/trace_event.h" |
| #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" |
| #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_bounds_observer.h" |
| #include "content/public/browser/host_zoom_map.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/view_type_utils.h" |
| #include "extensions/common/api/virtual_keyboard_private.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/mojom/view_type.mojom.h" |
| #include "third_party/blink/public/common/input/web_gesture_event.h" |
| #include "ui/accessibility/aura/aura_window_properties.h" |
| #include "ui/accessibility/ax_enums.mojom.h" |
| #include "ui/aura/window.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace { |
| |
| // Deletes itself when the associated WebContents is destroyed. |
| class ChromeKeyboardContentsDelegate : public content::WebContentsDelegate, |
| public content::WebContentsObserver { |
| public: |
| ChromeKeyboardContentsDelegate() = default; |
| |
| ChromeKeyboardContentsDelegate(const ChromeKeyboardContentsDelegate&) = |
| delete; |
| ChromeKeyboardContentsDelegate& operator=( |
| const ChromeKeyboardContentsDelegate&) = delete; |
| |
| ~ChromeKeyboardContentsDelegate() override = default; |
| |
| private: |
| // content::WebContentsDelegate: |
| content::WebContents* OpenURLFromTab( |
| content::WebContents* source, |
| const content::OpenURLParams& params, |
| base::OnceCallback<void(content::NavigationHandle&)> |
| navigation_handle_callback) override { |
| auto navigation_handle = source->GetController().LoadURLWithParams( |
| content::NavigationController::LoadURLParams(params)); |
| if (navigation_handle_callback && navigation_handle) { |
| std::move(navigation_handle_callback).Run(*navigation_handle); |
| } |
| Observe(source); |
| return source; |
| } |
| |
| bool CanDragEnter(content::WebContents* source, |
| const content::DropData& data, |
| blink::DragOperationsMask operations_allowed) override { |
| return false; |
| } |
| |
| bool IsWebContentsCreationOverridden( |
| content::RenderFrameHost* opener, |
| content::SiteInstance* source_site_instance, |
| content::mojom::WindowContainerType window_container_type, |
| const GURL& opener_url, |
| const std::string& frame_name, |
| const GURL& target_url) override { |
| return true; |
| } |
| |
| void SetContentsBounds(content::WebContents* source, |
| const gfx::Rect& bounds) override { |
| VLOG(1) << "SetContentsBounds: " << bounds.ToString(); |
| aura::Window* keyboard_window = source->GetNativeView(); |
| // keyboard window must have been added to keyboard container window at this |
| // point. Otherwise, wrong keyboard bounds is used and may cause problem as |
| // described in https://crbug.com/367788. |
| DCHECK(keyboard_window->parent()); |
| // keyboard window bounds may not set to |pos| after this call. If keyboard |
| // is in FULL_WIDTH mode, only the height of keyboard window will be |
| // changed. |
| keyboard_window->SetBounds(bounds); |
| } |
| |
| // content::WebContentsDelegate: |
| void RequestMediaAccessPermission( |
| content::WebContents* web_contents, |
| const content::MediaStreamRequest& request, |
| content::MediaResponseCallback callback) override { |
| const extensions::Extension* extension = nullptr; |
| GURL origin(request.security_origin); |
| if (origin.SchemeIs(extensions::kExtensionScheme)) { |
| const extensions::ExtensionRegistry* registry = |
| extensions::ExtensionRegistry::Get(web_contents->GetBrowserContext()); |
| extension = registry->enabled_extensions().GetByID(origin.GetHost()); |
| DCHECK(extension); |
| } |
| MediaCaptureDevicesDispatcher::GetInstance()->ProcessMediaAccessRequest( |
| web_contents, request, std::move(callback), extension); |
| } |
| |
| bool PreHandleGestureEvent(content::WebContents* source, |
| const blink::WebGestureEvent& event) override { |
| switch (event.GetType()) { |
| // Scroll events are not suppressed because the menu to select IME should |
| // be scrollable. |
| case blink::WebInputEvent::Type::kGestureScrollBegin: |
| case blink::WebInputEvent::Type::kGestureScrollEnd: |
| case blink::WebInputEvent::Type::kGestureScrollUpdate: |
| case blink::WebInputEvent::Type::kGestureFlingStart: |
| case blink::WebInputEvent::Type::kGestureFlingCancel: |
| return false; |
| // Allow tap events to allow browser scrollbars to be draggable by touch. |
| case blink::WebInputEvent::Type::kGestureTapDown: |
| case blink::WebInputEvent::Type::kGestureTap: |
| case blink::WebInputEvent::Type::kGestureTapCancel: |
| return false; |
| default: |
| // Stop gesture events from being passed to renderer to suppress the |
| // context menu. https://crbug.com/685140 |
| return true; |
| } |
| } |
| |
| // content::WebContentsObserver: |
| void WebContentsDestroyed() override { delete this; } |
| }; |
| |
| } // namespace |
| |
| ChromeKeyboardWebContents::ChromeKeyboardWebContents( |
| content::BrowserContext* context, |
| const GURL& url, |
| LoadCallback load_callback, |
| UnembedCallback unembed_callback) |
| : load_callback_(std::move(load_callback)), |
| unembed_callback_(std::move(unembed_callback)) { |
| VLOG(1) << "ChromeKeyboardWebContents: " << url; |
| DCHECK(context); |
| content::WebContents::CreateParams web_contents_params( |
| context, content::SiteInstance::CreateForURL(context, url)); |
| // The WebContents is initially hidden and shown later on. |
| web_contents_params.initially_hidden = true; |
| web_contents_ = content::WebContents::Create(web_contents_params); |
| web_contents_->SetDelegate(new ChromeKeyboardContentsDelegate()); |
| web_contents_->SetColorProviderSource(&color_provider_source_); |
| |
| extensions::SetViewType(web_contents_.get(), |
| extensions::mojom::ViewType::kComponent); |
| Observe(web_contents_.get()); |
| LoadContents(url); |
| |
| aura::Window* keyboard_window = web_contents_->GetNativeView(); |
| keyboard_window->set_owned_by_parent(false); |
| |
| // Set the background to be transparent for custom keyboard window shape. |
| content::RenderWidgetHostView* view = |
| web_contents_->GetPrimaryMainFrame()->GetView(); |
| view->SetBackgroundColor(SK_ColorTRANSPARENT); |
| view->GetNativeView()->SetTransparent(true); |
| |
| // By default, layers in WebContents are clipped at the window bounds, |
| // but this causes the shadows to be clipped too, so clipping needs to |
| // be disabled. |
| keyboard_window->layer()->SetMasksToBounds(false); |
| keyboard_window->SetProperty(ui::kAXRoleOverride, ax::mojom::Role::kKeyboard); |
| |
| keyboard_window->AddObserver(this); |
| |
| window_bounds_observer_ = |
| std::make_unique<ChromeKeyboardBoundsObserver>(keyboard_window); |
| } |
| |
| ChromeKeyboardWebContents::~ChromeKeyboardWebContents() { |
| window_bounds_observer_.reset(); |
| if (web_contents_) { |
| web_contents_->ClosePage(); |
| web_contents_->GetNativeView()->RemoveObserver(this); |
| web_contents_.reset(); |
| } |
| } |
| |
| void ChromeKeyboardWebContents::SetKeyboardUrl(const GURL& new_url) { |
| GURL old_url = web_contents_->GetURL(); |
| if (old_url == new_url) { |
| return; |
| } |
| |
| if (old_url.DeprecatedGetOriginAsURL() != |
| new_url.DeprecatedGetOriginAsURL()) { |
| // Sets keyboard window rectangle to 0 and closes the current page before |
| // navigating to a keyboard in a different extension. This keeps the UX the |
| // same as Android. Note we need to explicitly close the current page as it |
| // might try to resize the keyboard window in javascript on a resize event. |
| TRACE_EVENT0("vk", "ReloadKeyboardIfNeeded"); |
| web_contents_->GetNativeView()->SetBounds(gfx::Rect()); |
| web_contents_->ClosePage(); |
| } |
| |
| LoadContents(new_url); |
| } |
| |
| void ChromeKeyboardWebContents::SetInitialContentsSize(const gfx::Size& size) { |
| if (!contents_size_.IsEmpty()) { |
| return; |
| } |
| gfx::Rect bounds = web_contents_->GetNativeView()->bounds(); |
| bounds.set_size(size); |
| web_contents_->GetNativeView()->SetBounds(bounds); |
| } |
| |
| void ChromeKeyboardWebContents::RenderFrameCreated( |
| content::RenderFrameHost* frame_host) { |
| if (!frame_host->IsInPrimaryMainFrame()) { |
| return; |
| } |
| content::HostZoomMap* zoom_map = |
| content::HostZoomMap::GetDefaultForBrowserContext( |
| frame_host->GetBrowserContext()); |
| zoom_map->SetTemporaryZoomLevel(frame_host->GetGlobalId(), 0 /* level */); |
| } |
| |
| void ChromeKeyboardWebContents::DidStopLoading() { |
| // TODO(crbug.com/40577582): Change this to a DCHECK when we change |
| // ReloadKeyboardIfNeeded to also have a callback. |
| if (!load_callback_.is_null()) { |
| std::move(load_callback_).Run(); |
| } |
| } |
| |
| void ChromeKeyboardWebContents::OnColorProviderChanged() { |
| if (!web_contents_) { |
| return; |
| } |
| |
| auto* browser_context = web_contents_->GetBrowserContext(); |
| |
| if (!browser_context) { |
| return; |
| } |
| |
| auto* router = extensions::EventRouter::Get(browser_context); |
| |
| if (!router || |
| !router->HasEventListener(extensions::api::virtual_keyboard_private:: |
| OnColorProviderChanged::kEventName)) { |
| return; |
| } |
| |
| auto event = std::make_unique<extensions::Event>( |
| extensions::events::VIRTUAL_KEYBOARD_PRIVATE_ON_COLOR_PROVIDER_CHANGED, |
| extensions::api::virtual_keyboard_private::OnColorProviderChanged:: |
| kEventName, |
| base::Value::List(), browser_context); |
| |
| router->BroadcastEvent(std::move(event)); |
| } |
| |
| void ChromeKeyboardWebContents::LoadContents(const GURL& url) { |
| TRACE_EVENT0("vk", "LoadContents"); |
| content::OpenURLParams params(url, content::Referrer(), |
| WindowOpenDisposition::SINGLETON_TAB, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, false); |
| web_contents_->OpenURL(params, /*navigation_handle_callback=*/{}); |
| } |
| |
| void ChromeKeyboardWebContents::OnWindowBoundsChanged( |
| aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds, |
| ui::PropertyChangeReason reason) { |
| VLOG(1) << "OnWindowBoundsChanged: " << new_bounds.ToString(); |
| contents_size_ = new_bounds.size(); |
| } |