#include "ui/views/controls/webview/webview.h"
#include <string>
#include <utility>
#include "base/no_destructor.h"
#include "base/trace_event/trace_event.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_widget_host_view.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.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/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/events/event.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/webview/web_contents_set_background_color.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/views_delegate.h"
#include "ui/views/views_features.h"
namespace views {
namespace {
const void* const kIsWebViewContentsKey = &kIsWebViewContentsKey;
WebView::WebContentsCreator* GetCreatorForTesting() {
static base::NoDestructor<WebView::WebContentsCreator> creator;
return creator.get();
}
void UpdateNativeViewHostAccessibleParent(NativeViewHost* holder,
View* parent) {
if (!parent) {
return;
}
holder->SetParentAccessible(parent->GetNativeViewAccessible());
}
}
WebView::ScopedWebContentsCreatorForTesting::ScopedWebContentsCreatorForTesting(
WebContentsCreator creator) {
DCHECK(!*GetCreatorForTesting());
*GetCreatorForTesting() = creator;
}
WebView::ScopedWebContentsCreatorForTesting::
~ScopedWebContentsCreatorForTesting() {
*GetCreatorForTesting() = WebView::WebContentsCreator();
}
WebView::WebView(content::BrowserContext* browser_context) {
set_suppress_default_focus_handling();
ax_mode_observation_.Observe(&ui::AXPlatform::GetInstance());
SetBrowserContext(browser_context);
GetViewAccessibility().SetRole(ax::mojom::Role::kWebView);
GetViewAccessibility().SetName(
std::string(), ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
}
WebView::~WebView() {
SetWebContents(nullptr);
browser_context_ = nullptr;
}
bool WebView::IsWebViewContents(const content::WebContents* web_contents) {
return web_contents->GetUserData(kIsWebViewContentsKey);
}
content::WebContents* WebView::GetWebContents(const GURL& url,
base::Location creator_location) {
if (!web_contents()) {
if (!browser_context_) {
return nullptr;
}
wc_owner_ = CreateWebContents(browser_context_, url, creator_location);
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);
if (replacement) {
replacement->SetColorProviderSource(GetWidget());
replacement->SetUserData(kIsWebViewContentsKey,
std::make_unique<base::SupportsUserData::Data>());
}
if (wc_owner_.get() != replacement) {
wc_owner_.reset();
}
AttachWebContentsNativeView();
if (replacement && replacement->GetPrimaryMainFrame()->IsRenderFrameLive()) {
SetUpNewMainFrame(replacement->GetPrimaryMainFrame());
} else {
LostMainFrame();
}
}
content::BrowserContext* WebView::GetBrowserContext() {
return browser_context_;
}
void WebView::SetBrowserContext(content::BrowserContext* browser_context) {
browser_context_ = browser_context;
}
void WebView::LoadInitialURL(const GURL& url,
HttpsUpgradePolicy https_upgrade_policy,
base::Location invoke_location) {
content::NavigationController::LoadURLParams params(url);
params.referrer = content::Referrer();
params.transition_type = ui::PAGE_TRANSITION_AUTO_TOPLEVEL;
params.force_no_https_upgrade =
https_upgrade_policy == HttpsUpgradePolicy::kNoUpgrade;
const GURL initial_url =
base::FeatureList::IsEnabled(features::kApplyInitialUrlToWebContents)
? url
: GURL();
content::WebContents* web_contents =
GetWebContents(initial_url, invoke_location);
DCHECK(web_contents);
web_contents->GetController().LoadURLWithParams(params);
}
void WebView::SetFastResize(bool fast_resize) {
holder_->set_fast_resize(fast_resize);
}
bool WebView::GetFastResize() const {
return holder_->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;
if (web_contents() &&
web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive()) {
MaybeEnableAutoResize(web_contents()->GetPrimaryMainFrame());
}
}
void WebView::SetResizeBackgroundColor(SkColor resize_background_color) {
holder_->SetBackgroundColorWhenClipped(resize_background_color);
}
void WebView::SetCrashedOverlayView(View* crashed_overlay_view) {
if (crashed_overlay_view_.view() == crashed_overlay_view) {
return;
}
if (crashed_overlay_view_.view()) {
RemoveChildView(crashed_overlay_view_.view());
holder_->SetVisible(true);
}
crashed_overlay_view_.SetView(crashed_overlay_view);
if (crashed_overlay_view_.view()) {
CHECK(crashed_overlay_view_.view()->owned_by_client());
AddChildViewRaw(crashed_overlay_view_.view());
holder_->SetVisible(false);
crashed_overlay_view_.view()->SetBoundsRect(GetLocalBounds());
}
UpdateCrashedOverlayView();
}
base::CallbackListSubscription WebView::AddWebContentsAttachedCallback(
WebContentsAttachedCallback callback) {
return web_contents_attached_callbacks_.Add(callback);
}
base::CallbackListSubscription WebView::AddWebContentsDetachedCallback(
WebContentsDetachedCallback callback) {
return web_contents_detached_callbacks_.Add(callback);
}
base::CallbackListSubscription WebView::AddWebContentsFocusedCallback(
WebContentsFocusedCallback callback) {
return web_contents_focused_callbacks_.Add(callback);
}
void WebView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
View* overlay = crashed_overlay_view_.view();
if (overlay) {
overlay->SetBoundsRect(GetLocalBounds());
}
gfx::Rect holder_bounds = GetContentsBounds();
if (!web_contents() || !web_contents()->IsBeingCaptured() ||
web_contents()->GetPreferredSize().IsEmpty() ||
!(web_contents()->GetDelegate() &&
web_contents()->GetDelegate()->IsFullscreenForTabOrPending(
web_contents()))) {
holder_->SetNativeViewSize(gfx::Size());
holder_->SetBoundsRect(holder_bounds);
if (is_letterboxing_) {
is_letterboxing_ = false;
OnLetterboxingChanged();
}
return;
}
const gfx::Size capture_size = web_contents()->GetPreferredSize();
const int64_t x =
static_cast<int64_t>(capture_size.width()) * holder_bounds.height();
const int64_t y =
static_cast<int64_t>(capture_size.height()) * holder_bounds.width();
if (y < x) {
holder_bounds.ClampToCenteredSize(gfx::Size(
holder_bounds.width(), static_cast<int>(y / capture_size.width())));
} else {
holder_bounds.ClampToCenteredSize(gfx::Size(
static_cast<int>(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);
}
return web_contents() && !web_contents()->IsCrashed();
}
bool WebView::OnMousePressed(const ui::MouseEvent& event) {
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::AddedToWidget() {
if (!web_contents()) {
return;
}
web_contents()->SetColorProviderSource(GetWidget());
if (holder_->native_view()) {
UpdateNativeViewHostAccessibleParent(holder_, parent());
}
}
void WebView::RemovedFromWidget() {
if (holder_->native_view()) {
holder_->SetParentAccessible(gfx::NativeViewAccessible());
}
}
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();
if (is_primary_web_contents_for_window_) {
if (auto* ax_platform_node =
ui::AXPlatformNode::FromNativeViewAccessible(accessible)) {
ax_platform_node->GetDelegate()->SetIsPrimaryWebContentsForWindow();
}
}
return accessible;
}
}
return View::GetNativeViewAccessible();
}
void WebView::OnAXModeAdded(ui::AXMode mode) {
if (!GetWidget() || !web_contents()) {
return;
}
UpdateNativeViewHostAccessibleParent(holder(), parent());
}
void WebView::RenderFrameCreated(content::RenderFrameHost* render_frame_host) {
if (render_frame_host != web_contents()->GetPrimaryMainFrame()) {
return;
}
SetUpNewMainFrame(render_frame_host);
}
void WebView::RenderFrameDeleted(content::RenderFrameHost* render_frame_host) {
if (render_frame_host != web_contents()->GetPrimaryMainFrame()) {
return;
}
LostMainFrame();
}
void WebView::RenderFrameHostChanged(content::RenderFrameHost* old_host,
content::RenderFrameHost* new_host) {
if (new_host != web_contents()->GetPrimaryMainFrame()) {
return;
}
if (!old_host) {
DCHECK(!new_host->IsRenderFrameLive());
return;
}
SetUpNewMainFrame(new_host);
}
void WebView::DidToggleFullscreenModeForTab(bool entered_fullscreen,
bool will_cause_resize) {
OnBoundsChanged(bounds());
NotifyAccessibilityWebContentsChanged();
}
void WebView::OnWebContentsFocused(
content::RenderWidgetHost* render_widget_host) {
RequestFocus();
web_contents_focused_callbacks_.Notify(this);
}
void WebView::AXTreeIDForMainFrameHasChanged() {
NotifyAccessibilityWebContentsChanged();
}
void WebView::WebContentsDestroyed() {
SetWebContents(nullptr);
}
void WebView::ResizeDueToAutoResize(content::WebContents* source,
const gfx::Size& new_size) {
if (source != web_contents()) {
return;
}
SetPreferredSize(new_size);
}
void WebView::AttachWebContentsNativeView() {
TRACE_EVENT0("views", "WebView::AttachWebContentsNativeView");
if (!GetWidget() || !web_contents()) {
return;
}
gfx::NativeView view_to_attach = web_contents()->GetNativeView();
OnBoundsChanged(bounds());
if (holder_->native_view() == view_to_attach) {
return;
}
const auto* bg_color =
WebContentsSetBackgroundColor::FromWebContents(web_contents());
if (bg_color) {
holder_->SetBackgroundColorWhenClipped(bg_color->color());
} else {
holder_->SetBackgroundColorWhenClipped(std::nullopt);
}
holder_->Attach(view_to_attach);
UpdateNativeViewHostAccessibleParent(holder(), parent());
if (HasFocus()) {
OnFocus();
}
web_contents_attached_callbacks_.Notify(this);
}
void WebView::DetachWebContentsNativeView() {
TRACE_EVENT0("views", "WebView::DetachWebContentsNativeView");
if (web_contents()) {
holder_->Detach();
web_contents_detached_callbacks_.Notify(this);
}
}
void WebView::UpdateCrashedOverlayView() {
View* overlay = crashed_overlay_view_.view();
if (web_contents() && web_contents()->IsCrashed() && overlay) {
SetFocusBehavior(FocusBehavior::NEVER);
overlay->SetVisible(true);
return;
}
SetFocusBehavior(web_contents() ? FocusBehavior::ALWAYS
: FocusBehavior::NEVER);
if (overlay) {
overlay->SetVisible(false);
}
}
void WebView::NotifyAccessibilityWebContentsChanged() {
if (!lock_child_ax_tree_id_override_) {
content::RenderFrameHost* rfh =
web_contents() ? web_contents()->GetPrimaryMainFrame() : nullptr;
GetViewAccessibility().SetChildTreeID(rfh ? rfh->GetAXTreeID()
: ui::AXTreeIDUnknown());
}
NotifyAccessibilityEventDeprecated(ax::mojom::Event::kChildrenChanged, false);
}
std::unique_ptr<content::WebContents> WebView::CreateWebContents(
content::BrowserContext* browser_context,
const GURL& url,
base::Location creator_location) {
std::unique_ptr<content::WebContents> contents;
if (*GetCreatorForTesting()) {
contents = GetCreatorForTesting()->Run(browser_context);
}
if (!contents) {
content::WebContents::CreateParams create_params(browser_context,
creator_location);
if (!url.is_empty()) {
create_params.site_instance =
content::SiteInstance::CreateForURL(browser_context, url);
}
return content::WebContents::Create(create_params);
}
return contents;
}
void WebView::SetUpNewMainFrame(content::RenderFrameHost* frame_host) {
MaybeEnableAutoResize(frame_host);
UpdateCrashedOverlayView();
NotifyAccessibilityWebContentsChanged();
if (HasFocus()) {
OnFocus();
}
}
void WebView::LostMainFrame() {
UpdateCrashedOverlayView();
NotifyAccessibilityWebContentsChanged();
}
void WebView::MaybeEnableAutoResize(content::RenderFrameHost* frame_host) {
DCHECK(frame_host->IsRenderFrameLive());
if (!max_size_.IsEmpty()) {
frame_host->GetView()->EnableAutoResize(min_size_, max_size_);
}
}
BEGIN_METADATA(WebView)
END_METADATA
}