// Copyright 2013 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/plugins/renderer/webview_plugin.h" #include #include #include #include "base/auto_reset.h" #include "base/bind.h" #include "base/callback_helpers.h" #include "base/location.h" #include "base/metrics/histogram_macros.h" #include "base/numerics/safe_conversions.h" #include "base/task/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "gin/converter.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "skia/ext/platform_canvas.h" #include "third_party/blink/public/common/input/web_coalesced_input_event.h" #include "third_party/blink/public/common/page/page_zoom.h" #include "third_party/blink/public/common/tokens/tokens.h" #include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/mojom/input/focus_type.mojom.h" #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h" #include "third_party/blink/public/platform/web_policy_container.h" #include "third_party/blink/public/platform/web_url.h" #include "third_party/blink/public/platform/web_url_response.h" #include "third_party/blink/public/web/web_document.h" #include "third_party/blink/public/web/web_element.h" #include "third_party/blink/public/web/web_frame_widget.h" #include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_navigation_control.h" #include "third_party/blink/public/web/web_plugin_container.h" #include "third_party/blink/public/web/web_view.h" using blink::DragOperationsMask; using blink::WebDragData; using blink::WebFrameWidget; using blink::WebLocalFrame; using blink::WebMouseEvent; using blink::WebPlugin; using blink::WebPluginContainer; using blink::WebString; using blink::WebURLError; using blink::WebURLResponse; using blink::WebVector; using blink::WebView; using blink::web_pref::WebPreferences; WebViewPlugin::WebViewPlugin(WebView* web_view, WebViewPlugin::Delegate* delegate, const WebPreferences& preferences) : blink::WebViewObserver(web_view), delegate_(delegate), container_(nullptr), finished_loading_(false), focused_(false), is_painting_(false), is_resizing_(false), web_view_helper_(this, preferences, web_view->GetRendererPreferences()) {} // static WebViewPlugin* WebViewPlugin::Create(WebView* web_view, WebViewPlugin::Delegate* delegate, const WebPreferences& preferences, const std::string& html_data, const GURL& url) { DCHECK(url.is_valid()) << "Blink requires the WebView to have a valid URL."; WebViewPlugin* plugin = new WebViewPlugin(web_view, delegate, preferences); // Loading may synchronously access |delegate| which could be // uninitialized just yet, so load in another task. plugin->GetTaskRunner()->PostTask( FROM_HERE, base::BindOnce(&WebViewPlugin::LoadHTML, plugin->weak_factory_.GetWeakPtr(), html_data, url)); return plugin; } WebViewPlugin::~WebViewPlugin() { DCHECK(!weak_factory_.HasWeakPtrs()); } void WebViewPlugin::ReplayReceivedData(WebPlugin* plugin) { if (!response_.IsNull()) { plugin->DidReceiveResponse(response_); for (auto it = data_.begin(); it != data_.end(); ++it) { plugin->DidReceiveData(it->c_str(), it->length()); } } // We need to transfer the |focused_| to new plugin after it loaded. if (focused_) plugin->UpdateFocus(true, blink::mojom::FocusType::kNone); if (finished_loading_) plugin->DidFinishLoading(); if (error_) plugin->DidFailLoading(*error_); } WebPluginContainer* WebViewPlugin::Container() const { return container_; } bool WebViewPlugin::Initialize(WebPluginContainer* container) { DCHECK(container); DCHECK_EQ(this, container->Plugin()); container_ = container; // We must call layout again here to ensure that the container is laid // out before we next try to paint it, which is a requirement of the // document life cycle in Blink. In most cases, needsLayout is set by // scheduleAnimation, but due to timers controlling widget update, // scheduleAnimation may be invoked before this initialize call (which // comes through the widget update process). It doesn't hurt to mark // for animation again, and it does help us in the race-condition situation. container_->ScheduleAnimation(); old_title_ = container_->GetElement().GetAttribute("title"); web_view()->SetZoomLevel( blink::PageZoomFactorToZoomLevel(container_->PageZoomFactor())); return true; } void WebViewPlugin::Destroy() { weak_factory_.InvalidateWeakPtrs(); if (delegate_) { delegate_->PluginDestroyed(); delegate_ = nullptr; } container_ = nullptr; blink::WebViewObserver::Observe(nullptr); base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this); } v8::Local WebViewPlugin::V8ScriptableObject(v8::Isolate* isolate) { if (!delegate_) return v8::Local(); return delegate_->GetV8ScriptableObject(isolate); } void WebViewPlugin::UpdateAllLifecyclePhases( blink::DocumentUpdateReason reason) { DCHECK(web_view()->MainFrameWidget()); web_view()->MainFrameWidget()->UpdateAllLifecyclePhases(reason); } bool WebViewPlugin::IsErrorPlaceholder() { if (!delegate_) return false; return delegate_->IsErrorPlaceholder(); } void WebViewPlugin::Paint(cc::PaintCanvas* canvas, const gfx::Rect& rect) { gfx::Rect paint_rect = gfx::IntersectRects(rect_, rect); if (paint_rect.IsEmpty()) return; base::AutoReset is_painting( &is_painting_, true); paint_rect.Offset(-rect_.x(), -rect_.y()); canvas->save(); canvas->translate(SkIntToScalar(rect_.x()), SkIntToScalar(rect_.y())); web_view()->MainFrameWidget()->UpdateLifecycle( blink::WebLifecycleUpdate::kAll, blink::DocumentUpdateReason::kBeginMainFrame); web_view()->PaintContent(canvas, paint_rect); canvas->restore(); } // Coordinates are relative to the containing window. void WebViewPlugin::UpdateGeometry(const gfx::Rect& window_rect, const gfx::Rect& clip_rect, const gfx::Rect& unobscured_rect, bool is_visible) { DCHECK(container_); base::AutoReset is_resizing(&is_resizing_, true); if (window_rect != rect_) { rect_ = window_rect; DCHECK(web_view()->MainFrameWidget()); web_view()->MainFrameWidget()->Resize(rect_.size()); } // Plugin updates are forbidden during Blink layout. Therefore, // UpdatePluginForNewGeometry must be posted to a task to run asynchronously. web_view_helper_.main_frame() ->GetAgentGroupScheduler() ->CompositorTaskRunner() ->PostTask(FROM_HERE, base::BindOnce(&WebViewPlugin::UpdatePluginForNewGeometry, weak_factory_.GetWeakPtr(), window_rect, unobscured_rect)); } void WebViewPlugin::UpdateFocus(bool focused, blink::mojom::FocusType focus_type) { focused_ = focused; } blink::WebInputEventResult WebViewPlugin::HandleInputEvent( const blink::WebCoalescedInputEvent& coalesced_event, ui::Cursor* cursor) { const blink::WebInputEvent& event = coalesced_event.Event(); // For tap events, don't handle them. They will be converted to // mouse events later and passed to here. if (event.GetType() == blink::WebInputEvent::Type::kGestureTap) return blink::WebInputEventResult::kNotHandled; // For LongPress events we return false, since otherwise the context menu will // be suppressed. https://crbug.com/482842 if (event.GetType() == blink::WebInputEvent::Type::kGestureLongPress) return blink::WebInputEventResult::kNotHandled; if (event.GetType() == blink::WebInputEvent::Type::kContextMenu) { if (delegate_) { const WebMouseEvent& mouse_event = static_cast(event); delegate_->ShowContextMenu(mouse_event); } return blink::WebInputEventResult::kHandledSuppressed; } current_cursor_ = *cursor; DCHECK(web_view()->MainFrameWidget()); blink::WebInputEventResult handled = web_view()->MainFrameWidget()->HandleInputEvent(coalesced_event); *cursor = current_cursor_; return handled; } void WebViewPlugin::DidReceiveResponse(const WebURLResponse& response) { DCHECK(response_.IsNull()); response_ = response; } void WebViewPlugin::DidReceiveData(const char* data, size_t data_length) { data_.push_back(std::string(data, data_length)); } void WebViewPlugin::DidFinishLoading() { DCHECK(!finished_loading_); finished_loading_ = true; } void WebViewPlugin::DidFailLoading(const WebURLError& error) { DCHECK(!error_); error_ = std::make_unique(error); } WebViewPlugin::WebViewHelper::WebViewHelper( WebViewPlugin* plugin, const WebPreferences& parent_web_preferences, const blink::RendererPreferences& parent_renderer_preferences) : plugin_(plugin), agent_group_scheduler_( blink::scheduler::WebThreadScheduler::MainThreadScheduler() ->CreateAgentGroupScheduler()) { web_view_ = WebView::Create( /*client=*/this, /*is_hidden=*/false, /*is_prerendering=*/false, /*is_inside_portal=*/false, /*fenced_frame_mode=*/absl::nullopt, /*compositing_enabled=*/false, /*widgets_never_composited=*/false, /*opener=*/nullptr, mojo::NullAssociatedReceiver(), *agent_group_scheduler_, /*session_storage_namespace_id=*/base::EmptyString(), /*page_base_background_color=*/absl::nullopt); // ApplyWebPreferences before making a WebLocalFrame so that the frame sees a // consistent view of our preferences. blink::WebView::ApplyWebPreferences(parent_web_preferences, web_view_); // Turn off AcceptLoadDrops for this plugin webview. blink::RendererPreferences renderer_preferences = parent_renderer_preferences; renderer_preferences.can_accept_load_drops = false; web_view_->SetRendererPreferences(renderer_preferences); WebLocalFrame* web_frame = WebLocalFrame::CreateMainFrame( web_view_, this, nullptr, blink::LocalFrameToken(), blink::DocumentToken(), nullptr); blink::WebFrameWidget* frame_widget = web_frame->InitializeFrameWidget( blink::CrossVariantMojoAssociatedRemote< blink::mojom::FrameWidgetHostInterfaceBase>(), blink::CrossVariantMojoAssociatedReceiver< blink::mojom::FrameWidgetInterfaceBase>(), blink_widget_host_receiver_.BindNewEndpointAndPassDedicatedRemote(), blink_widget_.BindNewEndpointAndPassDedicatedReceiver(), viz::FrameSinkId()); frame_widget->InitializeNonCompositing(this); frame_widget->DisableDragAndDrop(); // The WebFrame created here was already attached to the Page as its main // frame, and the WebFrameWidget has been initialized, so we can call // WebView's DidAttachLocalMainFrame(). web_view_->DidAttachLocalMainFrame(); } WebViewPlugin::WebViewHelper::~WebViewHelper() { web_view_->Close(); } void WebViewPlugin::WebViewHelper::UpdateTooltipUnderCursor( const std::u16string& tooltip_text, base::i18n::TextDirection hint) { UpdateTooltip(tooltip_text); } void WebViewPlugin::WebViewHelper::UpdateTooltipFromKeyboard( const std::u16string& tooltip_text, base::i18n::TextDirection hint, const gfx::Rect& bounds) { UpdateTooltip(tooltip_text); } void WebViewPlugin::WebViewHelper::ClearKeyboardTriggeredTooltip() { // This is an exception to the "only clear it if its set from keyboard" since // there are no way of knowing whether the tooltips were set from keyboard or // cursor in this class. In any case, this will clear the tooltip. UpdateTooltip(std::u16string()); } void WebViewPlugin::WebViewHelper::UpdateTooltip( const std::u16string& tooltip_text) { if (plugin_->container_) { plugin_->container_->GetElement().SetAttribute( "title", WebString::FromUTF16(tooltip_text)); } } void WebViewPlugin::WebViewHelper::InvalidateContainer() { if (plugin_->container_) plugin_->container_->Invalidate(); } void WebViewPlugin::WebViewHelper::SetCursor(const ui::Cursor& cursor) { plugin_->current_cursor_ = cursor; } void WebViewPlugin::WebViewHelper::ScheduleNonCompositedAnimation() { // Resizes must be self-contained: any lifecycle updating must // be triggerd from within the WebView or this WebViewPlugin. // This is because this WebViewPlugin is contained in another // WebView which may be in the middle of updating its lifecycle, // but after layout is done, and it is illegal to dirty earlier // lifecycle stages during later ones. if (plugin_->is_resizing_) return; if (plugin_->container_) { // This should never happen; see also crbug.com/545039 for context. DCHECK(!plugin_->is_painting_); // This goes to compositor of the containing frame. plugin_->container_->ScheduleAnimation(); } } std::unique_ptr WebViewPlugin::WebViewHelper::CreateURLLoaderFactory() { return plugin_->Container() ->GetDocument() .GetFrame() ->Client() ->CreateURLLoaderFactory(); } void WebViewPlugin::WebViewHelper::BindToFrame( blink::WebNavigationControl* frame) { frame_ = frame; } void WebViewPlugin::WebViewHelper::DidClearWindowObject() { if (!plugin_->delegate_) return; v8::Isolate* isolate = blink::MainThreadIsolate(); v8::HandleScope handle_scope(isolate); v8::MicrotasksScope microtasks_scope( isolate, v8::MicrotasksScope::kDoNotRunMicrotasks); v8::Local context = frame_->MainWorldScriptContext(); DCHECK(!context.IsEmpty()); v8::Context::Scope context_scope(context); v8::Local global = context->Global(); global ->Set(context, gin::StringToV8(isolate, "plugin"), plugin_->delegate_->GetV8Handle(isolate)) .Check(); } void WebViewPlugin::WebViewHelper::FrameDetached() { frame_->Close(); frame_ = nullptr; } void WebViewPlugin::OnZoomLevelChanged() { if (container_) { web_view()->SetZoomLevel( blink::PageZoomFactorToZoomLevel(container_->PageZoomFactor())); } } void WebViewPlugin::LoadHTML(const std::string& html_data, const GURL& url) { auto params = std::make_unique(); params->url = url; params->policy_container = std::make_unique(); // The |html_data| comes from files in: chrome/renderer/resources/plugins/ // Executing scripts is the only capability required. // // WebSandboxFlags is a bit field. This removes all the capabilities, except // script execution. using network::mojom::WebSandboxFlags; params->policy_container->policies.sandbox_flags = static_cast( ~static_cast(WebSandboxFlags::kScripts)); blink::WebNavigationParams::FillStaticResponse(params.get(), "text/html", "UTF-8", html_data); web_view_helper_.main_frame()->CommitNavigation(std::move(params), /*extra_data=*/nullptr); } void WebViewPlugin::UpdatePluginForNewGeometry( const gfx::Rect& window_rect, const gfx::Rect& unobscured_rect) { DCHECK(container_); if (!delegate_) return; // The delegate may instantiate a new plugin. delegate_->OnUnobscuredRectUpdate(gfx::Rect(unobscured_rect)); // The delegate may have dirtied style and layout of the WebView. // Run the lifecycle now so that it is clean. DCHECK(web_view()->MainFrameWidget()); web_view()->MainFrameWidget()->UpdateAllLifecyclePhases( blink::DocumentUpdateReason::kPlugin); } scoped_refptr WebViewPlugin::GetTaskRunner() { return web_view_helper_.main_frame()->GetTaskRunner( blink::TaskType::kInternalDefault); }