// Copyright (c) 2012 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 "ui/views/controls/webview/webview.h" #include #include #include "base/no_destructor.h" #include "build/build_config.h" #include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/navigation_controller.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/web_contents.h" #include "ipc/ipc_message.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/platform/ax_platform_node.h" #include "ui/events/event.h" #include "ui/views/focus/focus_manager.h" #include "ui/views/metadata/metadata_impl_macros.h" #include "ui/views/views_delegate.h" namespace views { namespace { // A testing stub that creates web contents. WebView::WebContentsCreator* GetCreatorForTesting() { static base::NoDestructor creator; return creator.get(); } // Updates the parent accessible object on the NativeView. As WebView overrides // GetNativeViewAccessible() to return the accessible from the WebContents, it // needs to ensure the accessible from the parent is set on the NativeView. void UpdateNativeViewHostAccessibleParent(NativeViewHost* holder, View* parent) { if (!parent) return; holder->SetParentAccessible(parent->GetNativeViewAccessible()); } } // namespace WebView::ScopedWebContentsCreatorForTesting::ScopedWebContentsCreatorForTesting( WebContentsCreator creator) { DCHECK(!*GetCreatorForTesting()); *GetCreatorForTesting() = creator; } WebView::ScopedWebContentsCreatorForTesting:: ~ScopedWebContentsCreatorForTesting() { *GetCreatorForTesting() = WebView::WebContentsCreator(); } //////////////////////////////////////////////////////////////////////////////// // WebView, public: WebView::WebView(content::BrowserContext* browser_context) { ui::AXPlatformNode::AddAXModeObserver(this); SetBrowserContext(browser_context); } WebView::~WebView() { ui::AXPlatformNode::RemoveAXModeObserver(this); SetWebContents(nullptr); // Make sure all necessary tear-down takes place. } content::WebContents* WebView::GetWebContents() { if (!web_contents()) { if (!browser_context_) return nullptr; wc_owner_ = CreateWebContents(browser_context_); wc_owner_->SetDelegate(this); SetWebContents(wc_owner_.get()); } return web_contents(); } void WebView::SetWebContents(content::WebContents* replacement) { TRACE_EVENT0("views", "WebView::SetWebContents"); if (replacement == web_contents()) return; SetCrashedOverlayView(nullptr); DetachWebContentsNativeView(); WebContentsObserver::Observe(replacement); // web_contents() now returns |replacement| from here onwards. UpdateCrashedOverlayView(); if (wc_owner_.get() != replacement) wc_owner_.reset(); AttachWebContentsNativeView(); NotifyAccessibilityWebContentsChanged(); MaybeEnableAutoResize(); } content::BrowserContext* WebView::GetBrowserContext() { return browser_context_; } void WebView::SetBrowserContext(content::BrowserContext* browser_context) { browser_context_ = browser_context; } void WebView::LoadInitialURL(const GURL& url) { // Loading requires a valid WebContents. DCHECK(GetWebContents()); GetWebContents()->GetController().LoadURL(url, content::Referrer(), ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string()); } void WebView::SetFastResize(bool fast_resize) { holder_->set_fast_resize(fast_resize); } void WebView::EnableSizingFromWebContents(const gfx::Size& min_size, const gfx::Size& max_size) { DCHECK(!max_size.IsEmpty()); min_size_ = min_size; max_size_ = max_size; MaybeEnableAutoResize(); } void WebView::SetCrashedOverlayView(View* crashed_overlay_view) { if (crashed_overlay_view_ == crashed_overlay_view) return; if (crashed_overlay_view_) { RemoveChildView(crashed_overlay_view_); // Show the hosted web contents view iff the crashed // overlay is NOT showing, to ensure hit testing is // correct on Mac. See https://crbug.com/896508 holder_->SetVisible(true); if (!crashed_overlay_view_->owned_by_client()) delete crashed_overlay_view_; } crashed_overlay_view_ = crashed_overlay_view; if (crashed_overlay_view_) { AddChildView(crashed_overlay_view_); holder_->SetVisible(false); crashed_overlay_view_->SetBoundsRect(gfx::Rect(size())); } UpdateCrashedOverlayView(); } //////////////////////////////////////////////////////////////////////////////// // WebView, View overrides: void WebView::OnBoundsChanged(const gfx::Rect& previous_bounds) { if (crashed_overlay_view_) crashed_overlay_view_->SetBoundsRect(gfx::Rect(size())); // In most cases, the holder is simply sized to fill this WebView's bounds. // Only WebContentses that are in fullscreen mode and being screen-captured // will engage the special layout/sizing behavior. gfx::Rect holder_bounds = GetContentsBounds(); if (!web_contents() || !web_contents()->IsBeingCaptured() || web_contents()->GetPreferredSize().IsEmpty() || !(web_contents()->GetDelegate() && web_contents()->GetDelegate()->IsFullscreenForTabOrPending( web_contents()))) { // Reset the native view size. holder_->SetNativeViewSize(gfx::Size()); holder_->SetBoundsRect(holder_bounds); if (is_letterboxing_) { is_letterboxing_ = false; OnLetterboxingChanged(); } return; } // For screen-captured fullscreened content, scale the |holder_| to fit within // this View and center it. const gfx::Size capture_size = web_contents()->GetPreferredSize(); const int64_t x = static_cast(capture_size.width()) * holder_bounds.height(); const int64_t y = static_cast(capture_size.height()) * holder_bounds.width(); if (y < x) { holder_bounds.ClampToCenteredSize(gfx::Size( holder_bounds.width(), static_cast(y / capture_size.width()))); } else { holder_bounds.ClampToCenteredSize(gfx::Size( static_cast(x / capture_size.height()), holder_bounds.height())); } if (!is_letterboxing_) { is_letterboxing_ = true; OnLetterboxingChanged(); } holder_->SetNativeViewSize(capture_size); holder_->SetBoundsRect(holder_bounds); } void WebView::ViewHierarchyChanged(const ViewHierarchyChangedDetails& details) { if (details.is_add) AttachWebContentsNativeView(); } bool WebView::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) { if (allow_accelerators_) return FocusManager::IsTabTraversalKeyEvent(event); // Don't look-up accelerators or tab-traversal if we are showing a non-crashed // TabContents. // We'll first give the page a chance to process the key events. If it does // not process them, they'll be returned to us and we'll treat them as // accelerators then. return web_contents() && !web_contents()->IsCrashed(); } bool WebView::OnMousePressed(const ui::MouseEvent& event) { // A left-click within WebView is a request to focus. The area within the // native view child is excluded since it will be handling mouse pressed // events itself (http://crbug.com/436192). if (event.IsOnlyLeftMouseButton() && HitTestPoint(event.location())) { gfx::Point location_in_holder = event.location(); ConvertPointToTarget(this, holder_, &location_in_holder); if (!holder_->HitTestPoint(location_in_holder)) { RequestFocus(); return true; } } return View::OnMousePressed(event); } void WebView::OnFocus() { if (web_contents() && !web_contents()->IsCrashed()) web_contents()->Focus(); } void WebView::AboutToRequestFocusFromTabTraversal(bool reverse) { if (web_contents() && !web_contents()->IsCrashed()) web_contents()->FocusThroughTabTraversal(reverse); } void WebView::GetAccessibleNodeData(ui::AXNodeData* node_data) { node_data->role = ax::mojom::Role::kWebView; // A webview does not need an accessible name as the document title is // provided via other means. Providing it here would be redundant. // Mark the name as explicitly empty so that accessibility_checks pass. node_data->SetNameExplicitlyEmpty(); if (child_ax_tree_id_ != ui::AXTreeIDUnknown()) { node_data->AddStringAttribute(ax::mojom::StringAttribute::kChildTreeId, child_ax_tree_id_.ToString()); } } void WebView::AddedToWidget() { // If added to a widget hierarchy and |holder_| already has a NativeView // attached, update the accessible parent here to support reparenting the // WebView. if (web_contents() && holder_->native_view()) UpdateNativeViewHostAccessibleParent(holder_, parent()); } gfx::NativeViewAccessible WebView::GetNativeViewAccessible() { if (web_contents() && !web_contents()->IsCrashed()) { content::RenderWidgetHostView* host_view = web_contents()->GetRenderWidgetHostView(); if (host_view) { gfx::NativeViewAccessible accessible = host_view->GetNativeViewAccessible(); // |accessible| needs to know whether this is the primary WebContents. if (auto* ax_platform_node = ui::AXPlatformNode::FromNativeViewAccessible(accessible)) { ax_platform_node->SetIsPrimaryWebContentsForWindow( is_primary_web_contents_for_window_); } return accessible; } } return View::GetNativeViewAccessible(); } void WebView::OnAXModeAdded(ui::AXMode mode) { if (!web_contents()) return; // Normally, it is set during AttachWebContentsNativeView when the WebView is // created but this may not happen on some platforms as the accessible object // may not have been present when this WebView was created. So, update it when // AX mode is added. UpdateNativeViewHostAccessibleParent(holder(), parent()); } //////////////////////////////////////////////////////////////////////////////// // WebView, content::WebContentsDelegate implementation: //////////////////////////////////////////////////////////////////////////////// // WebView, content::WebContentsObserver implementation: void WebView::RenderViewCreated(content::RenderViewHost* render_view_host) { MaybeEnableAutoResize(); } void WebView::RenderViewReady() { UpdateCrashedOverlayView(); NotifyAccessibilityWebContentsChanged(); } void WebView::RenderViewDeleted(content::RenderViewHost* render_view_host) { UpdateCrashedOverlayView(); NotifyAccessibilityWebContentsChanged(); } void WebView::RenderViewHostChanged(content::RenderViewHost* old_host, content::RenderViewHost* new_host) { MaybeEnableAutoResize(); if (HasFocus()) OnFocus(); NotifyAccessibilityWebContentsChanged(); } void WebView::WebContentsDestroyed() { NotifyAccessibilityWebContentsChanged(); } void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen, bool will_cause_resize) { // Notify a bounds change on fullscreen change even though it actually // doesn't change. Cast needs this see https://crbug.com/1144255. OnBoundsChanged(bounds()); NotifyAccessibilityWebContentsChanged(); } void WebView::OnWebContentsFocused( content::RenderWidgetHost* render_widget_host) { RequestFocus(); } void WebView::RenderProcessGone(base::TerminationStatus status) { UpdateCrashedOverlayView(); NotifyAccessibilityWebContentsChanged(); } void WebView::AXTreeIDForMainFrameHasChanged() { NotifyAccessibilityWebContentsChanged(); } void WebView::ResizeDueToAutoResize(content::WebContents* source, const gfx::Size& new_size) { if (source != web_contents()) return; SetPreferredSize(new_size); } //////////////////////////////////////////////////////////////////////////////// // WebView, private: void WebView::AttachWebContentsNativeView() { TRACE_EVENT0("views", "WebView::AttachWebContentsNativeView"); // Prevents attachment if the WebView isn't already in a Widget, or it's // already attached. if (!GetWidget() || !web_contents()) return; gfx::NativeView view_to_attach = web_contents()->GetNativeView(); OnBoundsChanged(bounds()); if (holder_->native_view() == view_to_attach) return; holder_->Attach(view_to_attach); // We set the parent accessible of the native view to be our parent. UpdateNativeViewHostAccessibleParent(holder(), parent()); // The WebContents is not focused automatically when attached, so we need to // tell the WebContents it has focus if this has focus. if (HasFocus()) OnFocus(); OnWebContentsAttached(); } void WebView::DetachWebContentsNativeView() { TRACE_EVENT0("views", "WebView::DetachWebContentsNativeView"); if (web_contents()) holder_->Detach(); } void WebView::UpdateCrashedOverlayView() { if (web_contents() && web_contents()->IsCrashed() && crashed_overlay_view_) { SetFocusBehavior(FocusBehavior::NEVER); crashed_overlay_view_->SetVisible(true); return; } SetFocusBehavior(web_contents() ? FocusBehavior::ALWAYS : FocusBehavior::NEVER); if (crashed_overlay_view_) crashed_overlay_view_->SetVisible(false); } void WebView::NotifyAccessibilityWebContentsChanged() { content::RenderFrameHost* rfh = web_contents() ? web_contents()->GetMainFrame() : nullptr; child_ax_tree_id_ = rfh ? rfh->GetAXTreeID() : ui::AXTreeIDUnknown(); NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, false); } std::unique_ptr WebView::CreateWebContents( content::BrowserContext* browser_context) { std::unique_ptr contents; if (*GetCreatorForTesting()) { contents = GetCreatorForTesting()->Run(browser_context); } if (!contents) { content::WebContents::CreateParams create_params(browser_context, nullptr); return content::WebContents::Create(create_params); } return contents; } void WebView::MaybeEnableAutoResize() { if (max_size_.IsEmpty() || !web_contents() || !web_contents()->GetRenderWidgetHostView()) { return; } content::RenderWidgetHostView* render_widget_host_view = web_contents()->GetRenderWidgetHostView(); render_widget_host_view->EnableAutoResize(min_size_, max_size_); } BEGIN_METADATA(WebView, View) END_METADATA } // namespace views