| /* |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/exported/web_page_popup_impl.h" |
| |
| #include <memory> |
| |
| #include "cc/animation/animation_host.h" |
| #include "cc/layers/picture_layer.h" |
| #include "cc/trees/ukm_manager.h" |
| #include "third_party/blink/public/common/tokens/tokens.h" |
| #include "third_party/blink/public/mojom/input/input_handler.mojom-blink.h" |
| #include "third_party/blink/public/platform/scheduler/web_render_widget_scheduling_state.h" |
| #include "third_party/blink/public/web/web_view_client.h" |
| #include "third_party/blink/renderer/core/accessibility/ax_object_cache_base.h" |
| #include "third_party/blink/renderer/core/dom/context_features.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/events/message_event.h" |
| #include "third_party/blink/renderer/core/events/web_input_event_conversion.h" |
| #include "third_party/blink/renderer/core/exported/web_settings_impl.h" |
| #include "third_party/blink/renderer/core/exported/web_view_impl.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/screen_metrics_emulator.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/geometry/dom_rect.h" |
| #include "third_party/blink/renderer/core/input/event_handler.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/loader/empty_clients.h" |
| #include "third_party/blink/renderer/core/loader/frame_load_request.h" |
| #include "third_party/blink/renderer/core/page/focus_controller.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/page_popup_client.h" |
| #include "third_party/blink/renderer/core/page/page_popup_controller.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_animation_timeline.h" |
| #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/heap/handle.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/keyboard_codes.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread.h" |
| #include "third_party/blink/renderer/platform/text/text_direction.h" |
| #include "third_party/blink/renderer/platform/web_test_support.h" |
| #include "third_party/blink/renderer/platform/widget/frame_widget.h" |
| #include "third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.h" |
| #include "third_party/blink/renderer/platform/widget/widget_base.h" |
| |
| namespace blink { |
| namespace { |
| ScrollableArea* ToScrollableArea(Node* node) { |
| DCHECK(node); |
| LayoutBox* scrolling_box = node->GetLayoutBox(); |
| if (auto* element = DynamicTo<Element>(node)) |
| scrolling_box = element->GetLayoutBoxForScrolling(); |
| return scrolling_box ? scrolling_box->GetScrollableArea() : nullptr; |
| } |
| |
| bool CanScroll(Node* node) { |
| if (!node) |
| return false; |
| return ToScrollableArea(node); |
| } |
| |
| Node* FindFirstScroller(Node* event_target) { |
| DCHECK(event_target); |
| Node* cur_node = nullptr; |
| bool found = false; |
| LayoutBox* cur_box = event_target->GetLayoutObject() |
| ? event_target->GetLayoutObject()->EnclosingBox() |
| : nullptr; |
| while (cur_box) { |
| cur_node = cur_box->GetNode(); |
| if (CanScroll(cur_node)) { |
| found = true; |
| break; |
| } |
| cur_box = cur_box->ContainingBlock(); |
| } |
| if (found && cur_node) |
| return cur_node; |
| return nullptr; |
| } |
| } // namespace |
| |
| class PagePopupChromeClient final : public EmptyChromeClient { |
| public: |
| explicit PagePopupChromeClient(WebPagePopupImpl* popup) : popup_(popup) {} |
| |
| void SetWindowRect(const IntRect& rect, LocalFrame&) override { |
| popup_->SetWindowRect(rect); |
| } |
| |
| bool IsPopup() override { return true; } |
| |
| private: |
| void CloseWindowSoon() override { |
| // This skips past the PopupClient by calling ClosePopup() instead of |
| // Cancel(). |
| popup_->ClosePopup(); |
| } |
| |
| IntRect RootWindowRect(LocalFrame&) override { |
| // There is only one frame/widget in a WebPagePopup, so we can ignore the |
| // param. |
| return IntRect(popup_->WindowRectInScreen()); |
| } |
| |
| IntRect ViewportToScreen(const IntRect& rect, |
| const LocalFrameView*) const override { |
| gfx::Rect window_rect = popup_->WindowRectInScreen(); |
| gfx::Rect rect_in_dips = |
| popup_->widget_base_->BlinkSpaceToEnclosedDIPs(gfx::Rect(rect)); |
| rect_in_dips.Offset(window_rect.x(), window_rect.y()); |
| return IntRect(rect_in_dips); |
| } |
| |
| float WindowToViewportScalar(LocalFrame*, |
| const float scalar_value) const override { |
| return popup_->widget_base_->DIPsToBlinkSpace(scalar_value); |
| } |
| |
| void AddMessageToConsole(LocalFrame*, |
| mojom::ConsoleMessageSource, |
| mojom::ConsoleMessageLevel, |
| const String& message, |
| unsigned line_number, |
| const String&, |
| const String&) override { |
| #ifndef NDEBUG |
| fprintf(stderr, "CONSOLE MESSAGE:%u: %s\n", line_number, |
| message.Utf8().c_str()); |
| #endif |
| } |
| |
| void ScheduleAnimation(const LocalFrameView*, |
| base::TimeDelta delay = base::TimeDelta()) override { |
| // Destroying/removing the popup's content can be seen as a mutation that |
| // ends up calling ScheduleAnimation(). Since the popup is going away, we |
| // do not wish to actually do anything. |
| if (popup_->closing_) |
| return; |
| |
| // When the renderer has a compositor thread we need to follow the |
| // normal code path. |
| if (WebTestSupport::IsRunningWebTest() && !Thread::CompositorThread()) { |
| // In single-threaded web tests, the owner frame tree runs the composite |
| // step for the popup. Popup widgets don't run any composite step on their |
| // own. And we don't run popup tests with a compositor thread, so no need |
| // to check for that. |
| Document& opener_document = |
| popup_->popup_client_->OwnerElement().GetDocument(); |
| opener_document.GetPage()->GetChromeClient().ScheduleAnimation( |
| opener_document.GetFrame()->View(), delay); |
| return; |
| } |
| popup_->widget_base_->RequestAnimationAfterDelay(delay); |
| } |
| |
| void AttachCompositorAnimationTimeline(CompositorAnimationTimeline* timeline, |
| LocalFrame*) override { |
| popup_->widget_base_->AnimationHost()->AddAnimationTimeline( |
| timeline->GetAnimationTimeline()); |
| } |
| |
| void DetachCompositorAnimationTimeline(CompositorAnimationTimeline* timeline, |
| LocalFrame*) override { |
| popup_->widget_base_->AnimationHost()->RemoveAnimationTimeline( |
| timeline->GetAnimationTimeline()); |
| } |
| |
| const ScreenInfo& GetScreenInfo(LocalFrame&) const override { |
| // LocalFrame is ignored since there is only 1 frame in a popup. |
| return popup_->GetScreenInfo(); |
| } |
| |
| IntSize MinimumWindowSize() const override { return IntSize(0, 0); } |
| |
| void SetEventListenerProperties( |
| LocalFrame* frame, |
| cc::EventListenerClass event_class, |
| cc::EventListenerProperties properties) override { |
| // WebPagePopup always routes input to main thread (set up in RenderWidget), |
| // so no need to update listener properties. |
| } |
| cc::EventListenerProperties EventListenerProperties( |
| LocalFrame*, |
| cc::EventListenerClass event_class) const override { |
| // WebPagePopup always routes input to main thread (set up in RenderWidget), |
| // so no need to update listener properties. |
| return cc::EventListenerProperties::kNone; |
| } |
| |
| void SetHasScrollEventHandlers(LocalFrame* frame, |
| bool has_event_handlers) override { |
| // WebPagePopup's compositor does not handle compositor thread input (set up |
| // in RenderWidget) so there is no need to signal this. |
| } |
| |
| void SetTouchAction(LocalFrame* frame, TouchAction touch_action) override { |
| // Touch action is not used in the compositor for WebPagePopup. |
| } |
| |
| void AttachRootLayer(scoped_refptr<cc::Layer> layer, |
| LocalFrame* local_root) override { |
| popup_->SetRootLayer(layer.get()); |
| } |
| |
| void SetToolTip(LocalFrame&, |
| const String& tooltip_text, |
| TextDirection dir) override { |
| popup_->widget_base_->SetToolTipText(tooltip_text, dir); |
| } |
| |
| void InjectGestureScrollEvent(LocalFrame& local_frame, |
| WebGestureDevice device, |
| const gfx::Vector2dF& delta, |
| ui::ScrollGranularity granularity, |
| cc::ElementId scrollable_area_element_id, |
| WebInputEvent::Type injected_type) override { |
| popup_->InjectGestureScrollEvent(device, delta, granularity, |
| scrollable_area_element_id, injected_type); |
| } |
| |
| WebPagePopupImpl* popup_; |
| }; |
| |
| class PagePopupFeaturesClient : public ContextFeaturesClient { |
| bool IsEnabled(Document*, ContextFeatures::FeatureType, bool) override; |
| }; |
| |
| bool PagePopupFeaturesClient::IsEnabled(Document*, |
| ContextFeatures::FeatureType type, |
| bool default_value) { |
| if (type == ContextFeatures::kPagePopup) |
| return true; |
| return default_value; |
| } |
| |
| // WebPagePopupImpl ---------------------------------------------------------- |
| |
| WebPagePopupImpl::WebPagePopupImpl( |
| CrossVariantMojoAssociatedRemote<mojom::blink::PopupWidgetHostInterfaceBase> |
| popup_widget_host, |
| CrossVariantMojoAssociatedRemote<mojom::blink::WidgetHostInterfaceBase> |
| widget_host, |
| CrossVariantMojoAssociatedReceiver<mojom::blink::WidgetInterfaceBase> |
| widget, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) |
| : popup_widget_host_(std::move(popup_widget_host)), |
| widget_base_( |
| std::make_unique<WidgetBase>(/*widget_base_client=*/this, |
| std::move(widget_host), |
| std::move(widget), |
| task_runner, |
| /*hidden=*/false, |
| /*never_composited=*/false, |
| /*is_for_child_local_root=*/false)) { |
| popup_widget_host_.set_disconnect_handler(WTF::Bind( |
| &WebPagePopupImpl::WidgetHostDisconnected, WTF::Unretained(this))); |
| } |
| |
| WebPagePopupImpl::~WebPagePopupImpl() { |
| DCHECK(!page_); |
| } |
| |
| void WebPagePopupImpl::InitializeForTesting(WebView* opener_web_view) { |
| SetWebView(static_cast<WebViewImpl*>(opener_web_view)); |
| } |
| |
| void WebPagePopupImpl::SetWebView(WebViewImpl* opener_web_view) { |
| DCHECK(opener_web_view); |
| DCHECK(!opener_web_view_); |
| opener_web_view_ = opener_web_view; |
| if (auto* widget = opener_web_view->MainFrameViewWidget()) { |
| if (auto* device_emulator = widget->DeviceEmulator()) { |
| opener_widget_screen_origin_ = device_emulator->ViewRectOrigin(); |
| opener_original_widget_screen_origin_ = |
| device_emulator->original_view_rect().origin(); |
| opener_emulator_scale_ = device_emulator->scale(); |
| } |
| } |
| } |
| |
| void WebPagePopupImpl::Initialize(WebViewImpl* opener_web_view, |
| PagePopupClient* popup_client) { |
| DCHECK(popup_client); |
| popup_client_ = popup_client; |
| SetWebView(opener_web_view); |
| |
| Page::PageClients page_clients; |
| FillWithEmptyClients(page_clients); |
| chrome_client_ = MakeGarbageCollected<PagePopupChromeClient>(this); |
| page_clients.chrome_client = chrome_client_.Get(); |
| |
| Settings& main_settings = opener_web_view_->GetPage()->GetSettings(); |
| page_ = Page::CreateNonOrdinary(page_clients, opener_web_view_->GetPage() |
| ->GetPageScheduler() |
| ->GetAgentGroupScheduler()); |
| page_->GetSettings().SetAcceleratedCompositingEnabled(true); |
| page_->GetSettings().SetScriptEnabled(true); |
| page_->GetSettings().SetAllowScriptsToCloseWindows(true); |
| page_->GetSettings().SetMinimumFontSize(main_settings.GetMinimumFontSize()); |
| page_->GetSettings().SetMinimumLogicalFontSize( |
| main_settings.GetMinimumLogicalFontSize()); |
| page_->GetSettings().SetScrollAnimatorEnabled( |
| main_settings.GetScrollAnimatorEnabled()); |
| page_->GetSettings().SetAvailablePointerTypes( |
| main_settings.GetAvailablePointerTypes()); |
| page_->GetSettings().SetPrimaryPointerType( |
| main_settings.GetPrimaryPointerType()); |
| |
| // The style can be out-of-date if e.g. a key event handler modified the |
| // OwnerElement()'s style before the default handler started opening the |
| // popup. If the key handler forced a style update the style may be up-to-date |
| // and null. |
| // Note that if there's a key event handler which changes the color-scheme |
| // between the key is pressed and the popup is opened, the color-scheme of the |
| // form element and its popup may not match. |
| // If we think it's important to have an up-to-date style here, we need to run |
| // an UpdateStyleAndLayoutTree() before opening the popup in the various |
| // default event handlers. |
| if (const auto* style = popup_client_->OwnerElement().GetComputedStyle()) { |
| // Avoid using dark color scheme stylesheet for popups when forced colors |
| // mode is active. |
| // TODO(iopopesc): move this to popup CSS when the FocedColors feature is |
| // enabled by default. |
| bool in_forced_colors_mode = |
| popup_client_->OwnerElement().GetDocument().InForcedColorsMode(); |
| page_->GetSettings().SetPreferredColorScheme( |
| style->UsedColorScheme() == mojom::blink::ColorScheme::kDark && |
| !in_forced_colors_mode |
| ? mojom::blink::PreferredColorScheme::kDark |
| : mojom::blink::PreferredColorScheme::kLight); |
| } |
| popup_client_->CreatePagePopupController(*page_, *this); |
| |
| ProvideContextFeaturesTo(*page_, std::make_unique<PagePopupFeaturesClient>()); |
| DEFINE_STATIC_LOCAL(Persistent<LocalFrameClient>, empty_local_frame_client, |
| (MakeGarbageCollected<EmptyLocalFrameClient>())); |
| |
| // Creating new WindowAgentFactory because page popup content is owned by the |
| // user agent and should be isolated from the main frame. However, if we are a |
| // page popup in LayoutTests ensure we use the popup owner's frame for looking |
| // up the Agent so tests can possibly access the document via internals API. |
| WindowAgentFactory* window_agent_factory = nullptr; |
| if (WebTestSupport::IsRunningWebTest()) { |
| Document& owner_document = popup_client_->OwnerElement().GetDocument(); |
| window_agent_factory = &owner_document.GetFrame()->window_agent_factory(); |
| } |
| |
| auto* frame = MakeGarbageCollected<LocalFrame>( |
| empty_local_frame_client, *page_, |
| /* FrameOwner* */ nullptr, /* Frame* parent */ nullptr, |
| /* Frame* previous_sibling */ nullptr, |
| FrameInsertType::kInsertInConstructor, LocalFrameToken(), |
| window_agent_factory, |
| /* InterfaceRegistry* */ nullptr, |
| /* policy_container */ nullptr); |
| frame->SetPagePopupOwner(popup_client_->OwnerElement()); |
| frame->SetView(MakeGarbageCollected<LocalFrameView>(*frame)); |
| |
| if (WebTestSupport::IsRunningWebTest()) { |
| // In order for the shared WindowAgentFactory for tests to work correctly, |
| // we need to also copy settings used in WindowAgent selection over to the |
| // popup frame. |
| Settings* owner_settings = |
| popup_client_->OwnerElement().GetDocument().GetFrame()->GetSettings(); |
| frame->GetSettings()->SetWebSecurityEnabled( |
| owner_settings->GetWebSecurityEnabled()); |
| frame->GetSettings()->SetAllowUniversalAccessFromFileURLs( |
| owner_settings->GetAllowUniversalAccessFromFileURLs()); |
| } |
| |
| frame->Init(nullptr); |
| frame->View()->SetParentVisible(true); |
| frame->View()->SetSelfVisible(true); |
| |
| DCHECK(frame->DomWindow()); |
| DCHECK_EQ(popup_client_->OwnerElement().GetDocument().ExistingAXObjectCache(), |
| frame->GetDocument()->ExistingAXObjectCache()); |
| if (AXObjectCache* cache = frame->GetDocument()->ExistingAXObjectCache()) { |
| cache->InitializePopup(frame->GetDocument()); |
| cache->ChildrenChanged(&popup_client_->OwnerElement()); |
| } |
| |
| page_->AnimationHostInitialized(*widget_base_->AnimationHost(), nullptr); |
| |
| scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); |
| popup_client_->WriteDocument(data.get()); |
| frame->SetPageZoomFactor(popup_client_->ZoomFactor()); |
| frame->ForceSynchronousDocumentInstall("text/html", std::move(data)); |
| |
| popup_owner_client_rect_ = |
| popup_client_->OwnerElement().getBoundingClientRect(); |
| popup_widget_host_->ShowPopup( |
| initial_rect_, |
| WTF::Bind(&WebPagePopupImpl::DidShowPopup, WTF::Unretained(this))); |
| should_defer_setting_window_rect_ = false; |
| widget_base_->SetPendingWindowRect(initial_rect_); |
| |
| SetFocus(true); |
| } |
| |
| void WebPagePopupImpl::DidShowPopup() { |
| widget_base_->AckPendingWindowRect(); |
| } |
| |
| void WebPagePopupImpl::DidSetBounds() { |
| widget_base_->AckPendingWindowRect(); |
| } |
| |
| void WebPagePopupImpl::InitializeCompositing( |
| scheduler::WebAgentGroupScheduler& agent_group_scheduler, |
| cc::TaskGraphRunner* task_graph_runner, |
| const ScreenInfo& screen_info, |
| std::unique_ptr<cc::UkmRecorderFactory> ukm_recorder_factory, |
| const cc::LayerTreeSettings* settings) { |
| // Careful Initialize() is called after InitializeCompositing, so don't do |
| // much work here. |
| widget_base_->InitializeCompositing(agent_group_scheduler, task_graph_runner, |
| /*for_child_local_root_frame=*/false, |
| screen_info, |
| std::move(ukm_recorder_factory), settings, |
| /*frame_widget_input_handler=*/nullptr); |
| cc::LayerTreeDebugState debug_state = |
| widget_base_->LayerTreeHost()->GetDebugState(); |
| debug_state.TurnOffHudInfoDisplay(); |
| widget_base_->LayerTreeHost()->SetDebugState(debug_state); |
| } |
| |
| scheduler::WebRenderWidgetSchedulingState* |
| WebPagePopupImpl::RendererWidgetSchedulingState() { |
| return widget_base_->RendererWidgetSchedulingState(); |
| } |
| |
| void WebPagePopupImpl::SetCursor(const ui::Cursor& cursor) { |
| widget_base_->SetCursor(cursor); |
| } |
| |
| bool WebPagePopupImpl::HandlingInputEvent() { |
| return widget_base_->input_handler().handling_input_event(); |
| } |
| |
| void WebPagePopupImpl::SetHandlingInputEvent(bool handling) { |
| widget_base_->input_handler().set_handling_input_event(handling); |
| } |
| |
| void WebPagePopupImpl::ProcessInputEventSynchronouslyForTesting( |
| const WebCoalescedInputEvent& event, |
| HandledEventCallback callback) { |
| widget_base_->input_handler().HandleInputEvent(event, nullptr, |
| std::move(callback)); |
| } |
| |
| void WebPagePopupImpl::UpdateTextInputState() { |
| widget_base_->UpdateTextInputState(); |
| } |
| |
| void WebPagePopupImpl::UpdateSelectionBounds() { |
| widget_base_->UpdateSelectionBounds(); |
| } |
| |
| void WebPagePopupImpl::ShowVirtualKeyboard() { |
| widget_base_->ShowVirtualKeyboard(); |
| } |
| |
| void WebPagePopupImpl::SetFocus(bool focus) { |
| widget_base_->SetFocus(focus); |
| } |
| |
| bool WebPagePopupImpl::HasFocus() { |
| return widget_base_->has_focus(); |
| } |
| |
| void WebPagePopupImpl::FlushInputProcessedCallback() { |
| widget_base_->FlushInputProcessedCallback(); |
| } |
| |
| void WebPagePopupImpl::CancelCompositionForPepper() { |
| widget_base_->CancelCompositionForPepper(); |
| } |
| |
| void WebPagePopupImpl::ApplyVisualProperties( |
| const VisualProperties& visual_properties) { |
| widget_base_->UpdateVisualProperties(visual_properties); |
| } |
| |
| const ScreenInfo& WebPagePopupImpl::GetScreenInfo() { |
| return widget_base_->GetScreenInfo(); |
| } |
| |
| gfx::Rect WebPagePopupImpl::WindowRect() { |
| return widget_base_->WindowRect(); |
| } |
| |
| gfx::Rect WebPagePopupImpl::ViewRect() { |
| return widget_base_->ViewRect(); |
| } |
| |
| void WebPagePopupImpl::SetScreenRects(const gfx::Rect& widget_screen_rect, |
| const gfx::Rect& window_screen_rect) { |
| widget_base_->SetScreenRects(widget_screen_rect, window_screen_rect); |
| } |
| |
| gfx::Size WebPagePopupImpl::VisibleViewportSizeInDIPs() { |
| return widget_base_->VisibleViewportSizeInDIPs(); |
| } |
| |
| bool WebPagePopupImpl::IsHidden() const { |
| return widget_base_->is_hidden(); |
| } |
| |
| void WebPagePopupImpl::SetCompositorVisible(bool visible) { |
| widget_base_->SetCompositorVisible(visible); |
| } |
| |
| void WebPagePopupImpl::PostMessageToPopup(const String& message) { |
| if (!page_) |
| return; |
| ScriptForbiddenScope::AllowUserAgentScript allow_script; |
| MainFrame().DomWindow()->DispatchEvent(*MessageEvent::Create(message)); |
| } |
| |
| void WebPagePopupImpl::Update() { |
| if (!page_ && !popup_client_) |
| return; |
| |
| DOMRect* dom_rect = popup_client_->OwnerElement().getBoundingClientRect(); |
| bool forced_update = (*dom_rect != *popup_owner_client_rect_); |
| if (forced_update) |
| popup_owner_client_rect_ = dom_rect; |
| |
| popup_client_->Update(forced_update); |
| if (forced_update) |
| SetWindowRect(IntRect(WindowRectInScreen())); |
| } |
| |
| void WebPagePopupImpl::DestroyPage() { |
| page_->WillCloseAnimationHost(nullptr); |
| page_->WillBeDestroyed(); |
| page_.Clear(); |
| } |
| |
| AXObject* WebPagePopupImpl::RootAXObject() { |
| if (!page_) |
| return nullptr; |
| // If |page_| is non-null, the main frame must have a Document. |
| Document* document = MainFrame().GetDocument(); |
| AXObjectCache* cache = document->ExistingAXObjectCache(); |
| // There should never be a circumstance when RootAXObject() is triggered |
| // and the AXObjectCache doesn't already exist. It's called when trying |
| // to attach the accessibility tree of the pop-up to the host page. |
| DCHECK(cache); |
| return To<AXObjectCacheBase>(cache)->GetOrCreate(document->GetLayoutView()); |
| } |
| |
| void WebPagePopupImpl::SetWindowRect(const IntRect& rect_in_screen) { |
| if (!closing_) { |
| IntRect owner_window_rect_in_screen = OwnerWindowRectInScreen(); |
| Document& document = popup_client_->OwnerElement().GetDocument(); |
| if (owner_window_rect_in_screen.Contains(rect_in_screen)) { |
| UseCounter::Count(document, |
| WebFeature::kPopupDoesNotExceedOwnerWindowBounds); |
| } else { |
| WebFeature feature = |
| document.GetFrame()->IsMainFrame() |
| ? WebFeature::kPopupExceedsOwnerWindowBounds |
| : WebFeature::kPopupExceedsOwnerWindowBoundsForIframe; |
| UseCounter::Count(document, feature); |
| } |
| } |
| |
| gfx::Rect window_rect = rect_in_screen; |
| |
| // Popups aren't emulated, but the WidgetScreenRect and WindowScreenRect |
| // given to them are. When they set the WindowScreenRect it is based on those |
| // emulated values, so we reverse the emulation. |
| if (opener_emulator_scale_) |
| EmulatedToScreenRect(window_rect); |
| |
| if (!should_defer_setting_window_rect_) { |
| widget_base_->SetPendingWindowRect(window_rect); |
| popup_widget_host_->SetPopupBounds( |
| window_rect, |
| WTF::Bind(&WebPagePopupImpl::DidSetBounds, WTF::Unretained(this))); |
| } else { |
| initial_rect_ = window_rect; |
| } |
| } |
| |
| void WebPagePopupImpl::SetRootLayer(scoped_refptr<cc::Layer> layer) { |
| root_layer_ = std::move(layer); |
| widget_base_->LayerTreeHost()->SetRootLayer(root_layer_); |
| } |
| |
| void WebPagePopupImpl::SetSuppressFrameRequestsWorkaroundFor704763Only( |
| bool suppress_frame_requests) { |
| if (!page_) |
| return; |
| page_->Animator().SetSuppressFrameRequestsWorkaroundFor704763Only( |
| suppress_frame_requests); |
| } |
| |
| void WebPagePopupImpl::UpdateLifecycle(WebLifecycleUpdate requested_update, |
| DocumentUpdateReason reason) { |
| if (!page_) |
| return; |
| // Popups always update their lifecycle in the context of the containing |
| // document's lifecycle, so explicitly override the reason. |
| PageWidgetDelegate::UpdateLifecycle(*page_, MainFrame(), requested_update, |
| DocumentUpdateReason::kPagePopup); |
| } |
| |
| void WebPagePopupImpl::Resize(const gfx::Size& new_size_in_viewport) { |
| gfx::Size new_size_in_dips = |
| widget_base_->BlinkSpaceToFlooredDIPs(new_size_in_viewport); |
| gfx::Rect window_rect_in_dips = WindowRectInScreen(); |
| |
| // TODO(bokan): We should only call into this if the bounds actually changed |
| // but this reveals a bug in Aura. crbug.com/633140. |
| window_rect_in_dips.set_size(new_size_in_dips); |
| SetWindowRect(IntRect(window_rect_in_dips)); |
| |
| if (page_) { |
| MainFrame().View()->Resize(WebSize(new_size_in_viewport)); |
| page_->GetVisualViewport().SetSize(WebSize(new_size_in_viewport)); |
| } |
| } |
| |
| WebInputEventResult WebPagePopupImpl::HandleKeyEvent( |
| const WebKeyboardEvent& event) { |
| if (closing_) |
| return WebInputEventResult::kNotHandled; |
| |
| if (suppress_next_keypress_event_) { |
| suppress_next_keypress_event_ = false; |
| return WebInputEventResult::kHandledSuppressed; |
| } |
| |
| if (WebInputEvent::Type::kRawKeyDown == event.GetType()) { |
| Element* focused_element = FocusedElement(); |
| if (event.windows_key_code == VKEY_TAB && focused_element && |
| focused_element->IsKeyboardFocusable()) { |
| // If the tab key is pressed while a keyboard focusable element is |
| // focused, we should not send a corresponding keypress event. |
| suppress_next_keypress_event_ = true; |
| } |
| } |
| LocalFrame::NotifyUserActivation( |
| popup_client_->OwnerElement().GetDocument().GetFrame(), |
| mojom::blink::UserActivationNotificationType::kInteraction); |
| return MainFrame().GetEventHandler().KeyEvent(event); |
| } |
| |
| cc::LayerTreeHost* WebPagePopupImpl::LayerTreeHostForTesting() { |
| return widget_base_->LayerTreeHost(); |
| } |
| |
| void WebPagePopupImpl::BeginMainFrame(base::TimeTicks last_frame_time) { |
| if (!page_) |
| return; |
| // FIXME: This should use lastFrameTimeMonotonic but doing so |
| // breaks tests. |
| PageWidgetDelegate::Animate(*page_, base::TimeTicks::Now()); |
| } |
| |
| bool WebPagePopupImpl::WillHandleGestureEvent(const WebGestureEvent& event) { |
| return false; |
| } |
| |
| void WebPagePopupImpl::WillHandleMouseEvent(const WebMouseEvent& event) {} |
| |
| void WebPagePopupImpl::ObserveGestureEventAndResult( |
| const WebGestureEvent& gesture_event, |
| const gfx::Vector2dF& unused_delta, |
| const cc::OverscrollBehavior& overscroll_behavior, |
| bool event_processed) { |
| } |
| |
| WebInputEventResult WebPagePopupImpl::HandleCharEvent( |
| const WebKeyboardEvent& event) { |
| if (suppress_next_keypress_event_) { |
| suppress_next_keypress_event_ = false; |
| return WebInputEventResult::kHandledSuppressed; |
| } |
| return HandleKeyEvent(event); |
| } |
| |
| WebInputEventResult WebPagePopupImpl::HandleGestureEvent( |
| const WebGestureEvent& event) { |
| if (closing_) |
| return WebInputEventResult::kNotHandled; |
| if (event.GetType() == WebInputEvent::Type::kGestureTap || |
| event.GetType() == WebInputEvent::Type::kGestureTapDown) { |
| if (!IsViewportPointInWindow(event.PositionInWidget().x(), |
| event.PositionInWidget().y())) { |
| Cancel(); |
| return WebInputEventResult::kNotHandled; |
| } |
| LocalFrame::NotifyUserActivation( |
| popup_client_->OwnerElement().GetDocument().GetFrame(), |
| mojom::blink::UserActivationNotificationType::kInteraction); |
| CheckScreenPointInOwnerWindowAndCount( |
| event.PositionInScreen(), |
| WebFeature::kPopupGestureTapExceedsOwnerWindowBounds); |
| } |
| if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) { |
| if (event.GetType() == WebInputEvent::Type::kGestureScrollBegin) { |
| HitTestLocation locationScroll(FloatPoint(event.PositionInWidget())); |
| HitTestResult resultScroll = |
| MainFrame().GetEventHandler().HitTestResultAtLocation(locationScroll); |
| scrollable_node_ = FindFirstScroller(resultScroll.InnerNode()); |
| return WebInputEventResult::kHandledSystem; |
| } |
| if (event.GetType() == WebInputEvent::Type::kGestureScrollUpdate) { |
| if (!scrollable_node_) |
| return WebInputEventResult::kNotHandled; |
| |
| ScrollableArea* scrollable = ToScrollableArea(scrollable_node_); |
| |
| if (!scrollable) |
| return WebInputEventResult::kNotHandled; |
| ScrollOffset scroll_offset(-event.data.scroll_update.delta_x, |
| -event.data.scroll_update.delta_y); |
| scrollable->UserScroll(event.data.scroll_update.delta_units, |
| scroll_offset, ScrollableArea::ScrollCallback()); |
| return WebInputEventResult::kHandledSystem; |
| } |
| if (event.GetType() == WebInputEvent::Type::kGestureScrollEnd) { |
| scrollable_node_ = nullptr; |
| return WebInputEventResult::kHandledSystem; |
| } |
| } |
| WebGestureEvent scaled_event = |
| TransformWebGestureEvent(MainFrame().View(), event); |
| return MainFrame().GetEventHandler().HandleGestureEvent(scaled_event); |
| } |
| |
| void WebPagePopupImpl::HandleMouseDown(LocalFrame& main_frame, |
| const WebMouseEvent& event) { |
| if (IsViewportPointInWindow(event.PositionInWidget().x(), |
| event.PositionInWidget().y())) { |
| LocalFrame::NotifyUserActivation( |
| popup_client_->OwnerElement().GetDocument().GetFrame(), |
| mojom::blink::UserActivationNotificationType::kInteraction); |
| CheckScreenPointInOwnerWindowAndCount( |
| event.PositionInScreen(), |
| WebFeature::kPopupMouseDownExceedsOwnerWindowBounds); |
| PageWidgetEventHandler::HandleMouseDown(main_frame, event); |
| } else { |
| Cancel(); |
| } |
| } |
| |
| WebInputEventResult WebPagePopupImpl::HandleMouseWheel( |
| LocalFrame& main_frame, |
| const WebMouseWheelEvent& event) { |
| if (IsViewportPointInWindow(event.PositionInWidget().x(), |
| event.PositionInWidget().y())) { |
| CheckScreenPointInOwnerWindowAndCount( |
| event.PositionInScreen(), |
| WebFeature::kPopupMouseWheelExceedsOwnerWindowBounds); |
| return PageWidgetEventHandler::HandleMouseWheel(main_frame, event); |
| } |
| Cancel(); |
| return WebInputEventResult::kNotHandled; |
| } |
| |
| LocalFrame& WebPagePopupImpl::MainFrame() const { |
| DCHECK(page_); |
| // The main frame for a popup will never be out-of-process. |
| return *To<LocalFrame>(page_->MainFrame()); |
| } |
| |
| Element* WebPagePopupImpl::FocusedElement() const { |
| if (!page_) |
| return nullptr; |
| |
| LocalFrame* frame = page_->GetFocusController().FocusedFrame(); |
| if (!frame) |
| return nullptr; |
| |
| Document* document = frame->GetDocument(); |
| if (!document) |
| return nullptr; |
| |
| return document->FocusedElement(); |
| } |
| |
| bool WebPagePopupImpl::IsViewportPointInWindow(int x, int y) { |
| gfx::Point point_in_dips = |
| widget_base_->BlinkSpaceToFlooredDIPs(gfx::Point(x, y)); |
| gfx::Rect window_rect = WindowRectInScreen(); |
| return gfx::Rect(window_rect.size()).Contains(point_in_dips); |
| } |
| |
| void WebPagePopupImpl::CheckScreenPointInOwnerWindowAndCount( |
| const gfx::PointF& point_in_screen, |
| WebFeature feature) const { |
| if (closing_) |
| return; |
| |
| IntRect owner_window_rect = OwnerWindowRectInScreen(); |
| if (!owner_window_rect.Contains(point_in_screen.x(), point_in_screen.y())) |
| UseCounter::Count(popup_client_->OwnerElement().GetDocument(), feature); |
| } |
| |
| IntRect WebPagePopupImpl::OwnerWindowRectInScreen() const { |
| LocalFrameView* view = popup_client_->OwnerElement().GetDocument().View(); |
| IntRect frame_rect = view->FrameRect(); |
| return view->FrameToScreen(frame_rect); |
| } |
| |
| WebInputEventResult WebPagePopupImpl::DispatchBufferedTouchEvents() { |
| if (closing_) |
| return WebInputEventResult::kNotHandled; |
| return MainFrame().GetEventHandler().DispatchBufferedTouchEvents(); |
| } |
| |
| WebInputEventResult WebPagePopupImpl::HandleInputEvent( |
| const WebCoalescedInputEvent& event) { |
| if (closing_) |
| return WebInputEventResult::kNotHandled; |
| DCHECK(!WebInputEvent::IsTouchEventType(event.Event().GetType())); |
| return PageWidgetDelegate::HandleInputEvent(*this, event, &MainFrame()); |
| } |
| |
| void WebPagePopupImpl::FocusChanged(bool enable) { |
| if (!page_) |
| return; |
| if (enable) |
| page_->GetFocusController().SetActive(true); |
| page_->GetFocusController().SetFocused(enable); |
| } |
| |
| void WebPagePopupImpl::ScheduleAnimation() { |
| widget_base_->LayerTreeHost()->SetNeedsAnimate(); |
| } |
| |
| void WebPagePopupImpl::UpdateVisualProperties( |
| const VisualProperties& visual_properties) { |
| widget_base_->UpdateSurfaceAndScreenInfo( |
| visual_properties.local_surface_id.value_or(viz::LocalSurfaceId()), |
| visual_properties.compositor_viewport_pixel_rect, |
| visual_properties.screen_info); |
| widget_base_->SetVisibleViewportSizeInDIPs( |
| visual_properties.visible_viewport_size); |
| |
| // TODO(crbug.com/1155388): Popups are a single "global" object that don't |
| // inherit the scale factor of the frame containing the corresponding element |
| // so compositing_scale_factor is always 1 and has no effect. |
| float combined_scale_factor = visual_properties.page_scale_factor * |
| visual_properties.compositing_scale_factor; |
| widget_base_->LayerTreeHost()->SetExternalPageScaleFactor( |
| combined_scale_factor, visual_properties.is_pinch_gesture_active); |
| |
| Resize(widget_base_->DIPsToCeiledBlinkSpace(visual_properties.new_size)); |
| } |
| |
| const ScreenInfo& WebPagePopupImpl::GetOriginalScreenInfo() { |
| return widget_base_->GetScreenInfo(); |
| } |
| |
| gfx::Rect WebPagePopupImpl::ViewportVisibleRect() { |
| return widget_base_->CompositorViewportRect(); |
| } |
| |
| KURL WebPagePopupImpl::GetURLForDebugTrace() { |
| if (!page_) |
| return {}; |
| WebFrame* main_frame = opener_web_view_->MainFrame(); |
| if (main_frame->IsWebLocalFrame()) |
| return main_frame->ToWebLocalFrame()->GetDocument().Url(); |
| return {}; |
| } |
| |
| void WebPagePopupImpl::WidgetHostDisconnected() { |
| Close(); |
| // Careful, this is now destroyed. |
| } |
| |
| void WebPagePopupImpl::Close() { |
| // If the popup is closed from the renderer via Cancel(), then ClosePopup() |
| // has already run on another stack, and destroyed |page_|. If the popup is |
| // closed from the browser via IPC to RenderWidget, then we come here first |
| // and want to synchronously Cancel() immediately. |
| if (page_) { |
| // We set |closing_| here to inform ClosePopup() that it is being run |
| // synchronously from inside Close(). |
| closing_ = true; |
| // This should end up running ClosePopup() though the PopupClient. |
| Cancel(); |
| } |
| |
| widget_base_->Shutdown(); |
| widget_base_.reset(); |
| |
| // Self-delete on Close(). |
| Release(); |
| } |
| |
| void WebPagePopupImpl::ClosePopup() { |
| // There's always a |page_| when we get here because if we Close() this object |
| // due to ClosePopupWidgetSoon(), it will see the |page_| destroyed and not |
| // run this method again. And the renderer does not close the same popup more |
| // than once. |
| DCHECK(page_); |
| |
| // If the popup is closed from the renderer via Cancel(), then we want to |
| // initiate closing immediately here, but send a request for completing the |
| // close process through the browser via PopupWidgetHost::RequestClosePopup(), |
| // which will disconnect the channel come back to this class to |
| // WidgetHostDisconnected(). If |closing_| is already true, then the browser |
| // initiated the close on its own, via WidgetHostDisconnected IPC, which means |
| // ClosePopup() is being run inside the same stack, and does not need to |
| // request the browser to close the widget. |
| const bool running_inside_close = closing_; |
| if (!running_inside_close) { |
| // Bounce through the browser to get it to close the RenderWidget, which |
| // will Close() this object too. Only if we're not currently already |
| // responding to the browser closing us though. We don't need to do a post |
| // task like WebViewImpl::CloseWindowSoon does because we shouldn't be |
| // executing javascript influencing this popup widget. |
| popup_widget_host_->RequestClosePopup(); |
| } |
| |
| closing_ = true; |
| |
| if (AXObjectCache* cache = MainFrame().GetDocument()->ExistingAXObjectCache()) |
| cache->DisposePopup(MainFrame().GetDocument()); |
| |
| { |
| // This function can be called in EventDispatchForbiddenScope for the main |
| // document, and the following operations dispatch some events. It's safe |
| // because web authors can't listen the events. |
| EventDispatchForbiddenScope::AllowUserAgentEvents allow_events; |
| |
| MainFrame().Loader().StopAllLoaders(/*abort_client=*/true); |
| PagePopupController::From(*page_)->ClearPagePopupClient(); |
| DestroyPage(); |
| } |
| |
| // Informs the client to drop any references to this popup as it will be |
| // destroyed. |
| popup_client_->DidClosePopup(); |
| |
| // Drops the reference to the popup from WebViewImpl, making |this| the only |
| // owner of itself. Note however that WebViewImpl may briefly extend the |
| // lifetime of this object since it owns a reference, but it should only be |
| // to call HasSamePopupClient(). |
| opener_web_view_->CleanupPagePopup(); |
| } |
| |
| LocalDOMWindow* WebPagePopupImpl::Window() { |
| return MainFrame().DomWindow(); |
| } |
| |
| WebDocument WebPagePopupImpl::GetDocument() { |
| return WebDocument(MainFrame().GetDocument()); |
| } |
| |
| void WebPagePopupImpl::Cancel() { |
| if (popup_client_) |
| popup_client_->CancelPopup(); |
| } |
| |
| gfx::Rect WebPagePopupImpl::WindowRectInScreen() const { |
| return widget_base_->WindowRect(); |
| } |
| |
| void WebPagePopupImpl::InjectGestureScrollEvent( |
| WebGestureDevice device, |
| const gfx::Vector2dF& delta, |
| ScrollGranularity granularity, |
| cc::ElementId scrollable_area_element_id, |
| WebInputEvent::Type injected_type) { |
| widget_base_->input_handler().InjectGestureScrollEvent( |
| device, delta, granularity, scrollable_area_element_id, injected_type); |
| } |
| |
| void WebPagePopupImpl::ScreenRectToEmulated(gfx::Rect& screen_rect) { |
| if (!opener_emulator_scale_) |
| return; |
| screen_rect.set_x( |
| opener_widget_screen_origin_.x() + |
| (screen_rect.x() - opener_original_widget_screen_origin_.x()) / |
| opener_emulator_scale_); |
| screen_rect.set_y( |
| opener_widget_screen_origin_.y() + |
| (screen_rect.y() - opener_original_widget_screen_origin_.y()) / |
| opener_emulator_scale_); |
| } |
| |
| void WebPagePopupImpl::EmulatedToScreenRect(gfx::Rect& screen_rect) { |
| if (!opener_emulator_scale_) |
| return; |
| screen_rect.set_x(opener_original_widget_screen_origin_.x() + |
| (screen_rect.x() - opener_widget_screen_origin_.x()) * |
| opener_emulator_scale_); |
| screen_rect.set_y(opener_original_widget_screen_origin_.y() + |
| (screen_rect.y() - opener_widget_screen_origin_.y()) * |
| opener_emulator_scale_); |
| } |
| |
| std::unique_ptr<cc::LayerTreeFrameSink> |
| WebPagePopupImpl::AllocateNewLayerTreeFrameSink() { |
| return nullptr; |
| } |
| |
| // WebPagePopup ---------------------------------------------------------------- |
| |
| WebPagePopup* WebPagePopup::Create( |
| CrossVariantMojoAssociatedRemote<mojom::blink::PopupWidgetHostInterfaceBase> |
| popup_widget_host, |
| CrossVariantMojoAssociatedRemote<mojom::blink::WidgetHostInterfaceBase> |
| widget_host, |
| CrossVariantMojoAssociatedReceiver<mojom::blink::WidgetInterfaceBase> |
| widget, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| // A WebPagePopupImpl instance usually has two references. |
| // - One owned by the instance itself. It represents the visible widget. |
| // - One owned by a WebViewImpl. It's released when the WebViewImpl ask the |
| // WebPagePopupImpl to close. |
| // We need them because the closing operation is asynchronous and the widget |
| // can be closed while the WebViewImpl is unaware of it. |
| auto popup = base::AdoptRef( |
| new WebPagePopupImpl(std::move(popup_widget_host), std::move(widget_host), |
| std::move(widget), task_runner)); |
| popup->AddRef(); |
| return popup.get(); |
| } |
| |
| } // namespace blink |