blob: 8ab197693ed861ee46b954297d05c5e2edb2c4a8 [file] [log] [blame]
/*
* Copyright (C) 2011, 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 "core/exported/WebViewImpl.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/memory/scoped_refptr.h"
#include "build/build_config.h"
#include "core/CSSValueKeywords.h"
#include "core/CoreInitializer.h"
#include "core/clipboard/DataObject.h"
#include "core/dom/ContextFeaturesClientImpl.h"
#include "core/dom/Document.h"
#include "core/dom/LayoutTreeBuilderTraversal.h"
#include "core/dom/Text.h"
#include "core/dom/UserGestureIndicator.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/Editor.h"
#include "core/editing/EphemeralRange.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/SelectionTemplate.h"
#include "core/editing/ime/InputMethodController.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/editing/serializers/HTMLInterchange.h"
#include "core/editing/serializers/Serialization.h"
#include "core/events/CurrentInputEvent.h"
#include "core/events/KeyboardEvent.h"
#include "core/events/UIEventWithKeyState.h"
#include "core/events/WebInputEventConversion.h"
#include "core/events/WheelEvent.h"
#include "core/exported/WebDevToolsAgentImpl.h"
#include "core/exported/WebPluginContainerImpl.h"
#include "core/exported/WebRemoteFrameImpl.h"
#include "core/exported/WebSettingsImpl.h"
#include "core/frame/BrowserControls.h"
#include "core/frame/EventHandlerRegistry.h"
#include "core/frame/FullscreenController.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/LocalFrameClient.h"
#include "core/frame/LocalFrameView.h"
#include "core/frame/PageScaleConstraintsSet.h"
#include "core/frame/RemoteFrame.h"
#include "core/frame/ResizeViewportAnchor.h"
#include "core/frame/RotationViewportAnchor.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/frame/VisualViewport.h"
#include "core/frame/WebLocalFrameImpl.h"
#include "core/fullscreen/Fullscreen.h"
#include "core/html/HTMLPlugInElement.h"
#include "core/html/PluginDocument.h"
#include "core/html/forms/HTMLTextAreaElement.h"
#include "core/html/media/HTMLMediaElement.h"
#include "core/html_names.h"
#include "core/input/ContextMenuAllowedScope.h"
#include "core/input/EventHandler.h"
#include "core/input/TouchActionUtil.h"
#include "core/inspector/DevToolsEmulator.h"
#include "core/layout/LayoutEmbeddedContent.h"
#include "core/layout/LayoutView.h"
#include "core/layout/TextAutosizer.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/FrameLoadRequest.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/FrameLoaderStateMachine.h"
#include "core/loader/InteractiveDetector.h"
#include "core/loader/PrerendererClient.h"
#include "core/page/ChromeClientImpl.h"
#include "core/page/ContextMenuController.h"
#include "core/page/ContextMenuProvider.h"
#include "core/page/FocusController.h"
#include "core/page/FrameTree.h"
#include "core/page/Page.h"
#include "core/page/PageLifecycleState.h"
#include "core/page/PageOverlay.h"
#include "core/page/PagePopupClient.h"
#include "core/page/PointerLockController.h"
#include "core/page/TouchDisambiguation.h"
#include "core/page/ValidationMessageClientImpl.h"
#include "core/page/scrolling/TopDocumentRootScrollerController.h"
#include "core/paint/FirstMeaningfulPaintDetector.h"
#include "core/paint/LinkHighlightImpl.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/compositing/PaintLayerCompositor.h"
#include "core/timing/DOMWindowPerformance.h"
#include "core/timing/Performance.h"
#include "platform/ContextMenu.h"
#include "platform/ContextMenuItem.h"
#include "platform/Cursor.h"
#include "platform/Histogram.h"
#include "platform/KeyboardCodes.h"
#include "platform/animation/CompositorAnimationHost.h"
#include "platform/exported/WebActiveGestureAnimation.h"
#include "platform/fonts/FontCache.h"
#include "platform/geometry/FloatRect.h"
#include "platform/graphics/Color.h"
#include "platform/graphics/CompositorMutatorClient.h"
#include "platform/graphics/CompositorMutatorImpl.h"
#include "platform/graphics/FirstPaintInvalidationTracking.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/Image.h"
#include "platform/graphics/gpu/DrawingBuffer.h"
#include "platform/graphics/paint/DrawingRecorder.h"
#include "platform/image-decoders/ImageDecoder.h"
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/loader/fetch/UniqueIdentifier.h"
#include "platform/runtime_enabled_features.h"
#include "platform/scheduler/child/web_scheduler.h"
#include "platform/scheduler/renderer/web_view_scheduler.h"
#include "platform/scroll/ScrollbarTheme.h"
#include "platform/weborigin/SchemeRegistry.h"
#include "platform/wtf/AutoReset.h"
#include "platform/wtf/PtrUtil.h"
#include "platform/wtf/Time.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCompositeAndReadbackAsyncCallback.h"
#include "public/platform/WebCompositorSupport.h"
#include "public/platform/WebFloatPoint.h"
#include "public/platform/WebGestureCurve.h"
#include "public/platform/WebImage.h"
#include "public/platform/WebInputEvent.h"
#include "public/platform/WebLayerTreeView.h"
#include "public/platform/WebMenuSourceType.h"
#include "public/platform/WebScrollIntoViewParams.h"
#include "public/platform/WebTextInputInfo.h"
#include "public/platform/WebURLRequest.h"
#include "public/platform/WebVector.h"
#include "public/web/WebAutofillClient.h"
#include "public/web/WebConsoleMessage.h"
#include "public/web/WebElement.h"
#include "public/web/WebFrame.h"
#include "public/web/WebFrameClient.h"
#include "public/web/WebFrameWidget.h"
#include "public/web/WebHitTestResult.h"
#include "public/web/WebInputElement.h"
#include "public/web/WebMeaningfulLayout.h"
#include "public/web/WebMediaPlayerAction.h"
#include "public/web/WebNode.h"
#include "public/web/WebPlugin.h"
#include "public/web/WebPluginAction.h"
#include "public/web/WebRange.h"
#include "public/web/WebScopedUserGesture.h"
#include "public/web/WebSelection.h"
#include "public/web/WebViewClient.h"
#include "public/web/WebWindowFeatures.h"
#include "third_party/WebKit/common/page/page_visibility_state.mojom-blink.h"
#if defined(WTF_USE_DEFAULT_RENDER_THEME)
#include "core/layout/LayoutThemeDefault.h"
#endif
// Get rid of WTF's pow define so we can use std::pow.
#undef pow
#include <cmath> // for std::pow
// The following constants control parameters for automated scaling of webpages
// (such as due to a double tap gesture or find in page etc.). These are
// experimentally determined.
static const int touchPointPadding = 32;
static const int nonUserInitiatedPointPadding = 11;
static const float minScaleDifference = 0.01f;
static const float doubleTapZoomContentDefaultMargin = 5;
static const float doubleTapZoomContentMinimumMargin = 2;
static const double doubleTapZoomAnimationDurationInSeconds = 0.25;
static const float doubleTapZoomAlreadyLegibleRatio = 1.2f;
static const double multipleTargetsZoomAnimationDurationInSeconds = 0.25;
static const double findInPageAnimationDurationInSeconds = 0;
// Constants for snapping to minimum zoom.
static const double maximumZoomForSnapToMinimum = 1.05;
static const double snapToMiminimumZoomAnimationDurationInSeconds = 0.2;
// Constants for viewport anchoring on resize.
static const float viewportAnchorCoordX = 0.5f;
static const float viewportAnchorCoordY = 0;
// Constants for zooming in on a focused text field.
static const double scrollAndScaleAnimationDurationInSeconds = 0.2;
static const int minReadableCaretHeight = 16;
static const int minReadableCaretHeightForTextArea = 13;
static const float minScaleChangeToTriggerZoom = 1.5f;
static const float leftBoxRatio = 0.3f;
static const int caretPadding = 10;
namespace blink {
// Change the text zoom level by kTextSizeMultiplierRatio each time the user
// zooms text in or out (ie., change by 20%). The min and max values limit
// text zoom to half and 3x the original text size. These three values match
// those in Apple's port in WebKit/WebKit/WebView/WebView.mm
const double WebView::kTextSizeMultiplierRatio = 1.2;
const double WebView::kMinTextSizeMultiplier = 0.5;
const double WebView::kMaxTextSizeMultiplier = 3.0;
// Used to defer all page activity in cases where the embedder wishes to run
// a nested event loop. Using a stack enables nesting of message loop
// invocations.
static Vector<std::unique_ptr<ScopedPagePauser>>& PagePauserStack() {
DEFINE_STATIC_LOCAL(Vector<std::unique_ptr<ScopedPagePauser>>, pauser_stack,
());
return pauser_stack;
}
void WebView::WillEnterModalLoop() {
PagePauserStack().push_back(std::make_unique<ScopedPagePauser>());
}
void WebView::DidExitModalLoop() {
DCHECK(PagePauserStack().size());
PagePauserStack().pop_back();
}
// static
HashSet<WebViewImpl*>& WebViewImpl::AllInstances() {
DEFINE_STATIC_LOCAL(HashSet<WebViewImpl*>, all_instances, ());
return all_instances;
}
static bool g_should_use_external_popup_menus = false;
void WebView::SetUseExternalPopupMenus(bool use_external_popup_menus) {
g_should_use_external_popup_menus = use_external_popup_menus;
}
bool WebViewImpl::UseExternalPopupMenus() {
return g_should_use_external_popup_menus;
}
namespace {
class EmptyEventListener final : public EventListener {
public:
static EmptyEventListener* Create() { return new EmptyEventListener(); }
bool operator==(const EventListener& other) const override {
return this == &other;
}
private:
EmptyEventListener() : EventListener(kCPPEventListenerType) {}
void handleEvent(ExecutionContext* execution_context, Event*) override {}
};
class ColorOverlay final : public PageOverlay::Delegate {
public:
explicit ColorOverlay(WebColor color) : color_(color) {}
private:
void PaintPageOverlay(const PageOverlay& page_overlay,
GraphicsContext& graphics_context,
const WebSize& size) const override {
if (DrawingRecorder::UseCachedDrawingIfPossible(
graphics_context, page_overlay, DisplayItem::kPageOverlay))
return;
FloatRect rect(0, 0, size.width, size.height);
DrawingRecorder recorder(graphics_context, page_overlay,
DisplayItem::kPageOverlay);
graphics_context.FillRect(rect, color_);
}
WebColor color_;
};
} // namespace
// WebView ----------------------------------------------------------------
WebView* WebView::Create(WebViewClient* client,
mojom::PageVisibilityState visibility_state,
WebView* opener) {
return WebViewImpl::Create(client, visibility_state,
static_cast<WebViewImpl*>(opener));
}
WebViewImpl* WebViewImpl::Create(WebViewClient* client,
mojom::PageVisibilityState visibility_state,
WebViewImpl* opener) {
// Pass the WebViewImpl's self-reference to the caller.
auto web_view =
base::AdoptRef(new WebViewImpl(client, visibility_state, opener));
web_view->AddRef();
return web_view.get();
}
void WebView::UpdateVisitedLinkState(unsigned long long link_hash) {
Page::VisitedStateChanged(link_hash);
}
void WebView::ResetVisitedLinkState(bool invalidate_visited_link_hashes) {
Page::AllVisitedStateChanged(invalidate_visited_link_hashes);
}
void WebViewImpl::SetPrerendererClient(
WebPrerendererClient* prerenderer_client) {
DCHECK(page_);
ProvidePrerendererClientTo(*page_,
new PrerendererClient(*page_, prerenderer_client));
}
WebViewImpl::WebViewImpl(WebViewClient* client,
mojom::PageVisibilityState visibility_state,
WebViewImpl* opener)
: client_(client),
chrome_client_(ChromeClientImpl::Create(this)),
should_auto_resize_(false),
zoom_level_(0),
minimum_zoom_level_(ZoomFactorToZoomLevel(kMinTextSizeMultiplier)),
maximum_zoom_level_(ZoomFactorToZoomLevel(kMaxTextSizeMultiplier)),
zoom_factor_for_device_scale_factor_(0.f),
maximum_legible_scale_(1),
double_tap_zoom_page_scale_factor_(0),
double_tap_zoom_pending_(false),
enable_fake_page_scale_animation_for_testing_(false),
fake_page_scale_animation_page_scale_factor_(0),
fake_page_scale_animation_use_anchor_(false),
compositor_device_scale_factor_override_(0),
suppress_next_keypress_event_(false),
ime_accept_events_(true),
dev_tools_emulator_(nullptr),
tabs_to_links_(false),
layer_tree_view_(nullptr),
root_layer_(nullptr),
root_graphics_layer_(nullptr),
visual_viewport_container_layer_(nullptr),
matches_heuristics_for_gpu_rasterization_(false),
fullscreen_controller_(FullscreenController::Create(this)),
base_background_color_(Color::kWhite),
base_background_color_override_enabled_(false),
base_background_color_override_(Color::kTransparent),
background_color_override_enabled_(false),
background_color_override_(Color::kTransparent),
zoom_factor_override_(0),
should_dispatch_first_visually_non_empty_layout_(false),
should_dispatch_first_layout_after_finished_parsing_(false),
should_dispatch_first_layout_after_finished_loading_(false),
display_mode_(kWebDisplayModeBrowser),
elastic_overscroll_(FloatSize()),
mutator_(nullptr),
scheduler_(Platform::Current()
->CurrentThread()
->Scheduler()
->CreateWebViewScheduler(this, this)),
override_compositor_visibility_(false) {
Page::PageClients page_clients;
page_clients.chrome_client = chrome_client_.Get();
page_ =
Page::CreateOrdinary(page_clients, opener ? opener->GetPage() : nullptr);
CoreInitializer::GetInstance().ProvideModulesToPage(*page_, client_);
page_->SetValidationMessageClient(ValidationMessageClientImpl::Create(*this));
SetVisibilityState(visibility_state, true);
InitializeLayerTreeView();
dev_tools_emulator_ = DevToolsEmulator::Create(this);
AllInstances().insert(this);
page_importance_signals_.SetObserver(client);
resize_viewport_anchor_ = new ResizeViewportAnchor(*page_);
}
WebViewImpl::~WebViewImpl() {
DCHECK(!page_);
// Each highlight uses m_owningWebViewImpl->m_linkHighlightsTimeline
// in destructor. m_linkHighlightsTimeline might be destroyed earlier
// than m_linkHighlights.
DCHECK(link_highlights_.IsEmpty());
}
ValidationMessageClient* WebViewImpl::GetValidationMessageClient() const {
return page_ ? &page_->GetValidationMessageClient() : nullptr;
}
WebDevToolsAgentImpl* WebViewImpl::MainFrameDevToolsAgentImpl() {
WebLocalFrameImpl* main_frame = MainFrameImpl();
return main_frame ? main_frame->DevToolsAgentImpl() : nullptr;
}
WebLocalFrameImpl* WebViewImpl::MainFrameImpl() const {
return page_ && page_->MainFrame() && page_->MainFrame()->IsLocalFrame()
? WebLocalFrameImpl::FromFrame(page_->DeprecatedLocalMainFrame())
: nullptr;
}
bool WebViewImpl::TabKeyCyclesThroughElements() const {
DCHECK(page_);
return page_->TabKeyCyclesThroughElements();
}
void WebViewImpl::SetTabKeyCyclesThroughElements(bool value) {
if (page_)
page_->SetTabKeyCyclesThroughElements(value);
}
void WebViewImpl::HandleMouseLeave(LocalFrame& main_frame,
const WebMouseEvent& event) {
client_->SetMouseOverURL(WebURL());
PageWidgetEventHandler::HandleMouseLeave(main_frame, event);
}
void WebViewImpl::HandleMouseDown(LocalFrame& main_frame,
const WebMouseEvent& event) {
// If there is a popup open, close it as the user is clicking on the page
// (outside of the popup). We also save it so we can prevent a click on an
// element from immediately reopening the same popup.
scoped_refptr<WebPagePopupImpl> page_popup;
if (event.button == WebMouseEvent::Button::kLeft) {
page_popup = page_popup_;
HidePopups();
DCHECK(!page_popup_);
}
// Take capture on a mouse down on a plugin so we can send it mouse events.
// If the hit node is a plugin but a scrollbar is over it don't start mouse
// capture because it will interfere with the scrollbar receiving events.
LayoutPoint point(event.PositionInWidget());
if (event.button == WebMouseEvent::Button::kLeft &&
page_->MainFrame()->IsLocalFrame()) {
point =
page_->DeprecatedLocalMainFrame()->View()->RootFrameToContents(point);
HitTestResult result(page_->DeprecatedLocalMainFrame()
->GetEventHandler()
.HitTestResultAtPoint(point));
result.SetToShadowHostIfInRestrictedShadowRoot();
Node* hit_node = result.InnerNodeOrImageMapImage();
if (!result.GetScrollbar() && hit_node && hit_node->GetLayoutObject() &&
hit_node->GetLayoutObject()->IsEmbeddedObject()) {
mouse_capture_node_ = hit_node;
TRACE_EVENT_ASYNC_BEGIN0("input", "capturing mouse", this);
}
}
PageWidgetEventHandler::HandleMouseDown(main_frame, event);
if (event.button == WebMouseEvent::Button::kLeft && mouse_capture_node_) {
mouse_capture_gesture_token_ =
main_frame.GetEventHandler().TakeLastMouseDownGestureToken();
}
if (page_popup_ && page_popup &&
page_popup_->HasSamePopupClient(page_popup.get())) {
// That click triggered a page popup that is the same as the one we just
// closed. It needs to be closed.
CancelPagePopup();
}
// Dispatch the contextmenu event regardless of if the click was swallowed.
if (!GetPage()->GetSettings().GetShowContextMenuOnMouseUp()) {
#if defined(OS_MACOSX)
if (event.button == WebMouseEvent::Button::kRight ||
(event.button == WebMouseEvent::Button::kLeft &&
event.GetModifiers() & WebMouseEvent::kControlKey))
MouseContextMenu(event);
#else
if (event.button == WebMouseEvent::Button::kRight)
MouseContextMenu(event);
#endif
}
}
void WebViewImpl::SetDisplayMode(WebDisplayMode mode) {
display_mode_ = mode;
if (!MainFrameImpl() || !MainFrameImpl()->GetFrameView())
return;
MainFrameImpl()->GetFrameView()->SetDisplayMode(mode);
}
void WebViewImpl::MouseContextMenu(const WebMouseEvent& event) {
if (!MainFrameImpl() || !MainFrameImpl()->GetFrameView())
return;
page_->GetContextMenuController().ClearContextMenu();
WebMouseEvent transformed_event =
TransformWebMouseEvent(MainFrameImpl()->GetFrameView(), event);
transformed_event.menu_source_type = kMenuSourceMouse;
LayoutPoint position_in_root_frame(transformed_event.PositionInRootFrame());
// Find the right target frame. See issue 1186900.
HitTestResult result = HitTestResultForRootFramePos(position_in_root_frame);
Frame* target_frame;
if (result.InnerNodeOrImageMapImage())
target_frame = result.InnerNodeOrImageMapImage()->GetDocument().GetFrame();
else
target_frame = page_->GetFocusController().FocusedOrMainFrame();
if (!target_frame->IsLocalFrame())
return;
LocalFrame* target_local_frame = ToLocalFrame(target_frame);
{
ContextMenuAllowedScope scope;
target_local_frame->GetEventHandler().SendContextMenuEvent(
transformed_event, nullptr);
}
// Actually showing the context menu is handled by the ContextMenuController
// implementation...
}
void WebViewImpl::HandleMouseUp(LocalFrame& main_frame,
const WebMouseEvent& event) {
PageWidgetEventHandler::HandleMouseUp(main_frame, event);
if (GetPage()->GetSettings().GetShowContextMenuOnMouseUp()) {
// Dispatch the contextmenu event regardless of if the click was swallowed.
// On Mac/Linux, we handle it on mouse down, not up.
if (event.button == WebMouseEvent::Button::kRight)
MouseContextMenu(event);
}
}
WebInputEventResult WebViewImpl::HandleMouseWheel(
LocalFrame& main_frame,
const WebMouseWheelEvent& event) {
// Halt an in-progress fling on a wheel tick.
if (!event.has_precise_scrolling_deltas) {
if (WebFrameWidgetBase* widget = MainFrameImpl()->FrameWidget())
widget->EndActiveFlingAnimation();
}
HidePopups();
return PageWidgetEventHandler::HandleMouseWheel(main_frame, event);
}
WebInputEventResult WebViewImpl::HandleGestureEvent(
const WebGestureEvent& event) {
if (!client_ || !client_->CanHandleGestureEvent()) {
return WebInputEventResult::kNotHandled;
}
WebInputEventResult event_result = WebInputEventResult::kNotHandled;
bool event_cancelled = false; // for disambiguation
// Special handling for slow-path fling gestures.
switch (event.GetType()) {
case WebInputEvent::kGestureFlingStart:
case WebInputEvent::kGestureFlingCancel: {
if (WebFrameWidgetBase* widget = MainFrameImpl()->FrameWidget())
event_result = widget->HandleGestureFlingEvent(event);
client_->DidHandleGestureEvent(event, event_cancelled);
return event_result;
}
default:
break;
}
WebGestureEvent scaled_event =
TransformWebGestureEvent(MainFrameImpl()->GetFrameView(), event);
// Special handling for double tap and scroll events as we don't want to
// hit test for them.
switch (event.GetType()) {
case WebInputEvent::kGestureDoubleTap:
if (web_settings_->DoubleTapToZoomEnabled() &&
MinimumPageScaleFactor() != MaximumPageScaleFactor()) {
AnimateDoubleTapZoom(
FlooredIntPoint(scaled_event.PositionInRootFrame()));
}
// GestureDoubleTap is currently only used by Android for zooming. For
// WebCore, GestureTap with tap count = 2 is used instead. So we drop
// GestureDoubleTap here.
event_result = WebInputEventResult::kHandledSystem;
client_->DidHandleGestureEvent(event, event_cancelled);
return event_result;
case WebInputEvent::kGestureScrollBegin:
case WebInputEvent::kGestureScrollEnd:
case WebInputEvent::kGestureScrollUpdate:
case WebInputEvent::kGestureFlingStart:
// Scrolling-related gesture events invoke EventHandler recursively for
// each frame down the chain, doing a single-frame hit-test per frame.
// This matches handleWheelEvent. Perhaps we could simplify things by
// rewriting scroll handling to work inner frame out, and then unify with
// other gesture events.
event_result = MainFrameImpl()
->GetFrame()
->GetEventHandler()
.HandleGestureScrollEvent(scaled_event);
client_->DidHandleGestureEvent(event, event_cancelled);
return event_result;
case WebInputEvent::kGesturePinchBegin:
case WebInputEvent::kGesturePinchEnd:
case WebInputEvent::kGesturePinchUpdate:
return WebInputEventResult::kNotHandled;
default:
break;
}
// Hit test across all frames and do touch adjustment as necessary for the
// event type.
GestureEventWithHitTestResults targeted_event =
page_->DeprecatedLocalMainFrame()->GetEventHandler().TargetGestureEvent(
scaled_event);
// Handle link highlighting outside the main switch to avoid getting lost in
// the complicated set of cases handled below.
switch (event.GetType()) {
case WebInputEvent::kGestureShowPress:
// Queue a highlight animation, then hand off to regular handler.
EnableTapHighlightAtPoint(targeted_event);
break;
case WebInputEvent::kGestureTapCancel:
case WebInputEvent::kGestureTap:
case WebInputEvent::kGestureLongPress:
for (size_t i = 0; i < link_highlights_.size(); ++i)
link_highlights_[i]->StartHighlightAnimationIfNeeded();
break;
default:
break;
}
switch (event.GetType()) {
case WebInputEvent::kGestureTap: {
// Don't trigger a disambiguation popup on sites designed for mobile
// devices. Instead, assume that the page has been designed with big
// enough buttons and links. Don't trigger a disambiguation popup when
// screencasting, since it's implemented outside of compositor pipeline
// and is not being screencasted itself. This leads to bad user
// experience.
WebDevToolsAgentImpl* dev_tools = MainFrameDevToolsAgentImpl();
VisualViewport& visual_viewport = GetPage()->GetVisualViewport();
bool screencast_enabled = dev_tools && dev_tools->ScreencastEnabled();
if (event.data.tap.width > 0 &&
!visual_viewport.ShouldDisableDesktopWorkarounds() &&
!screencast_enabled) {
IntRect bounding_box(visual_viewport.ViewportToRootFrame(
IntRect(event.x - event.data.tap.width / 2,
event.y - event.data.tap.height / 2, event.data.tap.width,
event.data.tap.height)));
// TODO(bokan): We shouldn't pass details of the VisualViewport offset
// to render_view_impl. crbug.com/459591
WebSize visual_viewport_offset =
FlooredIntSize(visual_viewport.GetScrollOffset());
if (web_settings_->MultiTargetTapNotificationEnabled()) {
Vector<IntRect> good_targets;
HeapVector<Member<Node>> highlight_nodes;
FindGoodTouchTargets(bounding_box, MainFrameImpl()->GetFrame(),
good_targets, highlight_nodes);
// FIXME: replace touch adjustment code when numberOfGoodTargets == 1?
// Single candidate case is currently handled by:
// https://bugs.webkit.org/show_bug.cgi?id=85101
if (good_targets.size() >= 2 && client_ &&
client_->DidTapMultipleTargets(visual_viewport_offset,
bounding_box, good_targets)) {
// Stash the position of the node that would've been used absent
// disambiguation, for UMA purposes.
last_tap_disambiguation_best_candidate_position_ =
targeted_event.GetHitTestResult().RoundedPointInMainFrame() -
RoundedIntSize(targeted_event.GetHitTestResult().LocalPoint());
EnableTapHighlights(highlight_nodes);
for (size_t i = 0; i < link_highlights_.size(); ++i)
link_highlights_[i]->StartHighlightAnimationIfNeeded();
event_result = WebInputEventResult::kHandledSystem;
event_cancelled = true;
break;
}
}
}
{
ContextMenuAllowedScope scope;
event_result =
MainFrameImpl()->GetFrame()->GetEventHandler().HandleGestureEvent(
targeted_event);
}
if (page_popup_ && last_hidden_page_popup_ &&
page_popup_->HasSamePopupClient(last_hidden_page_popup_.get())) {
// The tap triggered a page popup that is the same as the one we just
// closed. It needs to be closed.
CancelPagePopup();
}
last_hidden_page_popup_ = nullptr;
break;
}
case WebInputEvent::kGestureTwoFingerTap:
case WebInputEvent::kGestureLongPress:
case WebInputEvent::kGestureLongTap: {
if (!MainFrameImpl() || !MainFrameImpl()->GetFrameView())
break;
page_->GetContextMenuController().ClearContextMenu();
{
ContextMenuAllowedScope scope;
event_result =
MainFrameImpl()->GetFrame()->GetEventHandler().HandleGestureEvent(
targeted_event);
}
break;
}
case WebInputEvent::kGestureTapDown: {
// Touch pinch zoom and scroll on the page (outside of a popup) must hide
// the popup. In case of a touch scroll or pinch zoom, this function is
// called with GestureTapDown rather than a GSB/GSU/GSE or GPB/GPU/GPE.
// When we close a popup because of a GestureTapDown, we also save it so
// we can prevent the following GestureTap from immediately reopening the
// same popup.
last_hidden_page_popup_ = page_popup_;
HidePopups();
DCHECK(!page_popup_);
event_result =
MainFrameImpl()->GetFrame()->GetEventHandler().HandleGestureEvent(
targeted_event);
break;
}
case WebInputEvent::kGestureTapCancel: {
last_hidden_page_popup_ = nullptr;
event_result =
MainFrameImpl()->GetFrame()->GetEventHandler().HandleGestureEvent(
targeted_event);
break;
}
case WebInputEvent::kGestureShowPress: {
event_result =
MainFrameImpl()->GetFrame()->GetEventHandler().HandleGestureEvent(
targeted_event);
break;
}
case WebInputEvent::kGestureTapUnconfirmed: {
event_result =
MainFrameImpl()->GetFrame()->GetEventHandler().HandleGestureEvent(
targeted_event);
break;
}
default: { NOTREACHED(); }
}
client_->DidHandleGestureEvent(event, event_cancelled);
return event_result;
}
namespace {
// This enum is used to back a histogram, and should therefore be treated as
// append-only.
enum TapDisambiguationResult {
kUmaTapDisambiguationOther = 0,
kUmaTapDisambiguationBackButton = 1,
kUmaTapDisambiguationTappedOutside = 2,
kUmaTapDisambiguationTappedInsideDeprecated = 3,
kUmaTapDisambiguationTappedInsideSameNode = 4,
kUmaTapDisambiguationTappedInsideDifferentNode = 5,
kUmaTapDisambiguationCount = 6,
};
void RecordTapDisambiguation(TapDisambiguationResult result) {
UMA_HISTOGRAM_ENUMERATION("Touchscreen.TapDisambiguation", result,
kUmaTapDisambiguationCount);
}
} // namespace
void WebViewImpl::ResolveTapDisambiguation(double timestamp_seconds,
WebPoint tap_viewport_offset,
bool is_long_press) {
WebGestureEvent event(is_long_press ? WebInputEvent::kGestureLongPress
: WebInputEvent::kGestureTap,
WebInputEvent::kNoModifiers, timestamp_seconds);
event.x = tap_viewport_offset.x;
event.y = tap_viewport_offset.y;
event.source_device = blink::kWebGestureDeviceTouchscreen;
{
// Compute UMA stat about whether the node selected by disambiguation UI was
// different from the one preferred by the regular hit-testing + adjustment
// logic.
WebGestureEvent scaled_event =
TransformWebGestureEvent(MainFrameImpl()->GetFrameView(), event);
GestureEventWithHitTestResults targeted_event =
page_->DeprecatedLocalMainFrame()->GetEventHandler().TargetGestureEvent(
scaled_event);
WebPoint node_position =
targeted_event.GetHitTestResult().RoundedPointInMainFrame() -
RoundedIntSize(targeted_event.GetHitTestResult().LocalPoint());
TapDisambiguationResult result =
(node_position == last_tap_disambiguation_best_candidate_position_)
? kUmaTapDisambiguationTappedInsideSameNode
: kUmaTapDisambiguationTappedInsideDifferentNode;
RecordTapDisambiguation(result);
}
HandleGestureEvent(event);
}
WebInputEventResult WebViewImpl::HandleSyntheticWheelFromTouchpadPinchEvent(
const WebGestureEvent& pinch_event) {
DCHECK_EQ(pinch_event.GetType(), WebInputEvent::kGesturePinchUpdate);
// For pinch gesture events, match typical trackpad behavior on Windows by
// sending fake wheel events with the ctrl modifier set when we see trackpad
// pinch gestures. Ideally we'd someday get a platform 'pinch' event and
// send that instead.
WebMouseWheelEvent wheel_event(
WebInputEvent::kMouseWheel,
pinch_event.GetModifiers() | WebInputEvent::kControlKey,
pinch_event.TimeStampSeconds());
wheel_event.SetPositionInWidget(pinch_event.x, pinch_event.y);
wheel_event.SetPositionInScreen(pinch_event.global_x, pinch_event.global_y);
wheel_event.delta_x = 0;
// The function to convert scales to deltaY values is designed to be
// compatible with websites existing use of wheel events, and with existing
// Windows trackpad behavior. In particular, we want:
// - deltas should accumulate via addition: f(s1*s2)==f(s1)+f(s2)
// - deltas should invert via negation: f(1/s) == -f(s)
// - zoom in should be positive: f(s) > 0 iff s > 1
// - magnitude roughly matches wheels: f(2) > 25 && f(2) < 100
// - a formula that's relatively easy to use from JavaScript
// Note that 'wheel' event deltaY values have their sign inverted. So to
// convert a wheel deltaY back to a scale use Math.exp(-deltaY/100).
DCHECK_GT(pinch_event.data.pinch_update.scale, 0);
wheel_event.delta_y = 100.0f * log(pinch_event.data.pinch_update.scale);
wheel_event.has_precise_scrolling_deltas = true;
wheel_event.wheel_ticks_x = 0;
wheel_event.wheel_ticks_y = pinch_event.data.pinch_update.scale > 1 ? 1 : -1;
return HandleInputEvent(blink::WebCoalescedInputEvent(wheel_event));
}
bool WebViewImpl::StartPageScaleAnimation(const IntPoint& target_position,
bool use_anchor,
float new_scale,
double duration_in_seconds) {
VisualViewport& visual_viewport = GetPage()->GetVisualViewport();
WebPoint clamped_point = target_position;
if (!use_anchor) {
clamped_point =
visual_viewport.ClampDocumentOffsetAtScale(target_position, new_scale);
if (!duration_in_seconds) {
SetPageScaleFactor(new_scale);
LocalFrameView* view = MainFrameImpl()->GetFrameView();
if (view && view->GetScrollableArea()) {
view->GetScrollableArea()->SetScrollOffset(
ScrollOffset(clamped_point.x, clamped_point.y),
kProgrammaticScroll);
}
return false;
}
}
if (use_anchor && new_scale == PageScaleFactor())
return false;
if (enable_fake_page_scale_animation_for_testing_) {
fake_page_scale_animation_target_position_ = target_position;
fake_page_scale_animation_use_anchor_ = use_anchor;
fake_page_scale_animation_page_scale_factor_ = new_scale;
} else {
if (!layer_tree_view_)
return false;
layer_tree_view_->StartPageScaleAnimation(target_position, use_anchor,
new_scale, duration_in_seconds);
}
return true;
}
void WebViewImpl::EnableFakePageScaleAnimationForTesting(bool enable) {
enable_fake_page_scale_animation_for_testing_ = enable;
fake_page_scale_animation_target_position_ = IntPoint();
fake_page_scale_animation_use_anchor_ = false;
fake_page_scale_animation_page_scale_factor_ = 0;
}
void WebViewImpl::SetShowFPSCounter(bool show) {
if (layer_tree_view_) {
TRACE_EVENT0("blink", "WebViewImpl::setShowFPSCounter");
layer_tree_view_->SetShowFPSCounter(show);
}
}
void WebViewImpl::SetShowPaintRects(bool show) {
if (layer_tree_view_) {
TRACE_EVENT0("blink", "WebViewImpl::setShowPaintRects");
layer_tree_view_->SetShowPaintRects(show);
}
FirstPaintInvalidationTracking::SetEnabledForShowPaintRects(show);
}
void WebViewImpl::SetShowDebugBorders(bool show) {
if (layer_tree_view_)
layer_tree_view_->SetShowDebugBorders(show);
}
void WebViewImpl::SetShowScrollBottleneckRects(bool show) {
if (layer_tree_view_)
layer_tree_view_->SetShowScrollBottleneckRects(show);
}
void WebViewImpl::AcceptLanguagesChanged() {
if (client_)
FontCache::AcceptLanguagesChanged(client_->AcceptLanguages());
if (!GetPage())
return;
GetPage()->AcceptLanguagesChanged();
}
void WebViewImpl::ReportIntervention(const WebString& message) {
if (!MainFrameImpl())
return;
WebConsoleMessage console_message(WebConsoleMessage::kLevelWarning, message);
MainFrameImpl()->AddMessageToConsole(console_message);
}
void WebViewImpl::RequestBeginMainFrameNotExpected(bool new_state) {
if (layer_tree_view_) {
layer_tree_view_->RequestBeginMainFrameNotExpected(new_state);
}
}
void WebViewImpl::SetPageStopped(bool stopped) {
if (!GetPage())
return;
GetPage()->SetLifecycleState(stopped ? PageLifecycleState::kStopped
: GetPage()->IsPageVisible()
? PageLifecycleState::kActive
: PageLifecycleState::kHidden);
}
WebInputEventResult WebViewImpl::HandleKeyEvent(const WebKeyboardEvent& event) {
DCHECK((event.GetType() == WebInputEvent::kRawKeyDown) ||
(event.GetType() == WebInputEvent::kKeyDown) ||
(event.GetType() == WebInputEvent::kKeyUp));
TRACE_EVENT2("input", "WebViewImpl::handleKeyEvent", "type",
WebInputEvent::GetName(event.GetType()), "text",
String(event.text).Utf8());
// Halt an in-progress fling on a key event.
if (WebFrameWidgetBase* widget = MainFrameImpl()->FrameWidget())
widget->EndActiveFlingAnimation();
// Please refer to the comments explaining the m_suppressNextKeypressEvent
// member.
// The m_suppressNextKeypressEvent is set if the KeyDown is handled by
// Webkit. A keyDown event is typically associated with a keyPress(char)
// event and a keyUp event. We reset this flag here as this is a new keyDown
// event.
suppress_next_keypress_event_ = false;
// If there is a popup, it should be the one processing the event, not the
// page.
if (page_popup_) {
page_popup_->HandleKeyEvent(event);
// We need to ignore the next Char event after this otherwise pressing
// enter when selecting an item in the popup will go to the page.
if (WebInputEvent::kRawKeyDown == event.GetType())
suppress_next_keypress_event_ = true;
return WebInputEventResult::kHandledSystem;
}
Frame* focused_frame = FocusedCoreFrame();
if (!focused_frame || !focused_frame->IsLocalFrame())
return WebInputEventResult::kNotHandled;
LocalFrame* frame = ToLocalFrame(focused_frame);
WebInputEventResult result = frame->GetEventHandler().KeyEvent(event);
if (result != WebInputEventResult::kNotHandled) {
if (WebInputEvent::kRawKeyDown == event.GetType()) {
// Suppress the next keypress event unless the focused node is a plugin
// node. (Flash needs these keypress events to handle non-US keyboards.)
Element* element = FocusedElement();
if (element && element->GetLayoutObject() &&
element->GetLayoutObject()->IsEmbeddedObject()) {
if (event.windows_key_code == VKEY_TAB) {
// If the plugin supports keyboard focus then we should not send a tab
// keypress event.
WebPluginContainerImpl* plugin_view =
ToLayoutEmbeddedContent(element->GetLayoutObject())->Plugin();
if (plugin_view && plugin_view->SupportsKeyboardFocus()) {
suppress_next_keypress_event_ = true;
}
}
} else {
suppress_next_keypress_event_ = true;
}
}
return result;
}
#if !defined(OS_MACOSX)
const WebInputEvent::Type kContextMenuKeyTriggeringEventType =
#if defined(OS_WIN)
WebInputEvent::kKeyUp;
#else
WebInputEvent::kRawKeyDown;
#endif
const WebInputEvent::Type kShiftF10TriggeringEventType =
WebInputEvent::kRawKeyDown;
bool is_unmodified_menu_key =
!(event.GetModifiers() & WebInputEvent::kInputModifiers) &&
event.windows_key_code == VKEY_APPS;
bool is_shift_f10 = (event.GetModifiers() & WebInputEvent::kInputModifiers) ==
WebInputEvent::kShiftKey &&
event.windows_key_code == VKEY_F10;
if ((is_unmodified_menu_key &&
event.GetType() == kContextMenuKeyTriggeringEventType) ||
(is_shift_f10 && event.GetType() == kShiftF10TriggeringEventType)) {
SendContextMenuEvent();
return WebInputEventResult::kHandledSystem;
}
#endif // !defined(OS_MACOSX)
return WebInputEventResult::kNotHandled;
}
WebInputEventResult WebViewImpl::HandleCharEvent(
const WebKeyboardEvent& event) {
DCHECK_EQ(event.GetType(), WebInputEvent::kChar);
TRACE_EVENT1("input", "WebViewImpl::handleCharEvent", "text",
String(event.text).Utf8());
// Please refer to the comments explaining the m_suppressNextKeypressEvent
// member. The m_suppressNextKeypressEvent is set if the KeyDown is
// handled by Webkit. A keyDown event is typically associated with a
// keyPress(char) event and a keyUp event. We reset this flag here as it
// only applies to the current keyPress event.
bool suppress = suppress_next_keypress_event_;
suppress_next_keypress_event_ = false;
// If there is a popup, it should be the one processing the event, not the
// page.
if (page_popup_)
return page_popup_->HandleKeyEvent(event);
LocalFrame* frame = ToLocalFrame(FocusedCoreFrame());
if (!frame) {
return suppress ? WebInputEventResult::kHandledSuppressed
: WebInputEventResult::kNotHandled;
}
EventHandler& handler = frame->GetEventHandler();
if (!event.IsCharacterKey())
return WebInputEventResult::kHandledSuppressed;
// Accesskeys are triggered by char events and can't be suppressed.
if (handler.HandleAccessKey(event))
return WebInputEventResult::kHandledSystem;
// Safari 3.1 does not pass off windows system key messages (WM_SYSCHAR) to
// the eventHandler::keyEvent. We mimic this behavior on all platforms since
// for now we are converting other platform's key events to windows key
// events.
if (event.is_system_key)
return WebInputEventResult::kNotHandled;
if (suppress)
return WebInputEventResult::kHandledSuppressed;
WebInputEventResult result = handler.KeyEvent(event);
if (result != WebInputEventResult::kNotHandled)
return result;
return WebInputEventResult::kNotHandled;
}
WebRect WebViewImpl::ComputeBlockBound(const WebPoint& point_in_root_frame,
bool ignore_clipping) {
if (!MainFrameImpl())
return WebRect();
// Use the point-based hit test to find the node.
LayoutPoint point = MainFrameImpl()->GetFrameView()->RootFrameToContents(
LayoutPoint(point_in_root_frame));
HitTestRequest::HitTestRequestType hit_type =
HitTestRequest::kReadOnly | HitTestRequest::kActive |
(ignore_clipping ? HitTestRequest::kIgnoreClipping : 0);
HitTestResult result =
MainFrameImpl()->GetFrame()->GetEventHandler().HitTestResultAtPoint(
point, hit_type);
result.SetToShadowHostIfInRestrictedShadowRoot();
Node* node = result.InnerNodeOrImageMapImage();
if (!node)
return WebRect();
// Find the block type node based on the hit node.
// FIXME: This wants to walk flat tree with
// LayoutTreeBuilderTraversal::parent().
while (node &&
(!node->GetLayoutObject() || node->GetLayoutObject()->IsInline()))
node = LayoutTreeBuilderTraversal::Parent(*node);
// Return the bounding box in the root frame's coordinate space.
if (node) {
IntRect point_in_root_frame = node->Node::PixelSnappedBoundingBox();
LocalFrame* frame = node->GetDocument().GetFrame();
return frame->View()->ContentsToRootFrame(point_in_root_frame);
}
return WebRect();
}
WebRect WebViewImpl::WidenRectWithinPageBounds(const WebRect& source,
int target_margin,
int minimum_margin) {
WebSize max_size;
IntSize scroll_offset;
if (MainFrame()) {
// TODO(lukasza): https://crbug.com/734209: The DCHECK below holds now, but
// only because all of the callers don't support OOPIFs and exit early if
// the main frame is not local.
DCHECK(MainFrame()->IsWebLocalFrame());
max_size = MainFrame()->ToWebLocalFrame()->ContentsSize();
scroll_offset = MainFrame()->ToWebLocalFrame()->GetScrollOffset();
}
int left_margin = target_margin;
int right_margin = target_margin;
const int absolute_source_x = source.x + scroll_offset.Width();
if (left_margin > absolute_source_x) {
left_margin = absolute_source_x;
right_margin = std::max(left_margin, minimum_margin);
}
const int maximum_right_margin =
max_size.width - (source.width + absolute_source_x);
if (right_margin > maximum_right_margin) {
right_margin = maximum_right_margin;
left_margin = std::min(left_margin, std::max(right_margin, minimum_margin));
}
const int new_width = source.width + left_margin + right_margin;
const int new_x = source.x - left_margin;
DCHECK_GE(new_width, 0);
DCHECK_LE(scroll_offset.Width() + new_x + new_width, max_size.width);
return WebRect(new_x, source.y, new_width, source.height);
}
float WebViewImpl::MaximumLegiblePageScale() const {
// Pages should be as legible as on desktop when at dpi scale, so no
// need to zoom in further when automatically determining zoom level
// (after double tap, find in page, etc), though the user should still
// be allowed to manually pinch zoom in further if they desire.
if (GetPage()) {
return maximum_legible_scale_ *
GetPage()->GetSettings().GetAccessibilityFontScaleFactor();
}
return maximum_legible_scale_;
}
void WebViewImpl::ComputeScaleAndScrollForBlockRect(
const WebPoint& hit_point_in_root_frame,
const WebRect& block_rect_in_root_frame,
float padding,
float default_scale_when_already_legible,
float& scale,
WebPoint& scroll) {
scale = PageScaleFactor();
scroll.x = scroll.y = 0;
WebRect rect = block_rect_in_root_frame;
if (!rect.IsEmpty()) {
float default_margin = doubleTapZoomContentDefaultMargin;
float minimum_margin = doubleTapZoomContentMinimumMargin;
// We want the margins to have the same physical size, which means we
// need to express them in post-scale size. To do that we'd need to know
// the scale we're scaling to, but that depends on the margins. Instead
// we express them as a fraction of the target rectangle: this will be
// correct if we end up fully zooming to it, and won't matter if we
// don't.
rect = WidenRectWithinPageBounds(
rect, static_cast<int>(default_margin * rect.width / size_.width),
static_cast<int>(minimum_margin * rect.width / size_.width));
// Fit block to screen, respecting limits.
scale = static_cast<float>(size_.width) / rect.width;
scale = std::min(scale, MaximumLegiblePageScale());
if (PageScaleFactor() < default_scale_when_already_legible)
scale = std::max(scale, default_scale_when_already_legible);
scale = ClampPageScaleFactorToLimits(scale);
}
// FIXME: If this is being called for auto zoom during find in page,
// then if the user manually zooms in it'd be nice to preserve the
// relative increase in zoom they caused (if they zoom out then it's ok
// to zoom them back in again). This isn't compatible with our current
// double-tap zoom strategy (fitting the containing block to the screen)
// though.
float screen_width = size_.width / scale;
float screen_height = size_.height / scale;
// Scroll to vertically align the block.
if (rect.height < screen_height) {
// Vertically center short blocks.
rect.y -= 0.5 * (screen_height - rect.height);
} else {
// Ensure position we're zooming to (+ padding) isn't off the bottom of
// the screen.
rect.y = std::max<float>(
rect.y, hit_point_in_root_frame.y + padding - screen_height);
} // Otherwise top align the block.
// Do the same thing for horizontal alignment.
if (rect.width < screen_width) {
rect.x -= 0.5 * (screen_width - rect.width);
} else {
rect.x = std::max<float>(
rect.x, hit_point_in_root_frame.x + padding - screen_width);
}
scroll.x = rect.x;
scroll.y = rect.y;
scale = ClampPageScaleFactorToLimits(scale);
scroll = MainFrameImpl()->GetFrameView()->RootFrameToContents(scroll);
scroll =
GetPage()->GetVisualViewport().ClampDocumentOffsetAtScale(scroll, scale);
}
static Node* FindCursorDefiningAncestor(Node* node, LocalFrame* frame) {
// Go up the tree to find the node that defines a mouse cursor style
while (node) {
if (node->GetLayoutObject()) {
ECursor cursor = node->GetLayoutObject()->Style()->Cursor();
if (cursor != ECursor::kAuto ||
frame->GetEventHandler().UseHandCursor(node, node->IsLink()))
break;
}
node = LayoutTreeBuilderTraversal::Parent(*node);
}
return node;
}
static bool ShowsHandCursor(Node* node, LocalFrame* frame) {
if (!node || !node->GetLayoutObject())
return false;
ECursor cursor = node->GetLayoutObject()->Style()->Cursor();
return cursor == ECursor::kPointer ||
(cursor == ECursor::kAuto &&
frame->GetEventHandler().UseHandCursor(node, node->IsLink()));
}
Node* WebViewImpl::BestTapNode(
const GestureEventWithHitTestResults& targeted_tap_event) {
TRACE_EVENT0("input", "WebViewImpl::bestTapNode");
if (!page_ || !page_->MainFrame())
return nullptr;
Node* best_touch_node = targeted_tap_event.GetHitTestResult().InnerNode();
if (!best_touch_node)
return nullptr;
// We might hit something like an image map that has no layoutObject on it
// Walk up the tree until we have a node with an attached layoutObject
while (!best_touch_node->GetLayoutObject()) {
best_touch_node = LayoutTreeBuilderTraversal::Parent(*best_touch_node);
if (!best_touch_node)
return nullptr;
}
// Editable nodes should not be highlighted (e.g., <input>)
if (HasEditableStyle(*best_touch_node))
return nullptr;
Node* cursor_defining_ancestor = FindCursorDefiningAncestor(
best_touch_node, page_->DeprecatedLocalMainFrame());
// We show a highlight on tap only when the current node shows a hand cursor
if (!cursor_defining_ancestor ||
!ShowsHandCursor(cursor_defining_ancestor,
page_->DeprecatedLocalMainFrame())) {
return nullptr;
}
// We should pick the largest enclosing node with hand cursor set. We do this
// by first jumping up to cursorDefiningAncestor (which is already known to
// have hand cursor set). Then we locate the next cursor-defining ancestor up
// in the the tree and repeat the jumps as long as the node has hand cursor
// set.
do {
best_touch_node = cursor_defining_ancestor;
cursor_defining_ancestor = FindCursorDefiningAncestor(
LayoutTreeBuilderTraversal::Parent(*best_touch_node),
page_->DeprecatedLocalMainFrame());
} while (cursor_defining_ancestor &&
ShowsHandCursor(cursor_defining_ancestor,
page_->DeprecatedLocalMainFrame()));
return best_touch_node;
}
void WebViewImpl::EnableTapHighlightAtPoint(
const GestureEventWithHitTestResults& targeted_tap_event) {
Node* touch_node = BestTapNode(targeted_tap_event);
HeapVector<Member<Node>> highlight_nodes;
highlight_nodes.push_back(touch_node);
EnableTapHighlights(highlight_nodes);
}
void WebViewImpl::EnableTapHighlights(
HeapVector<Member<Node>>& highlight_nodes) {
if (highlight_nodes.IsEmpty())
return;
// Always clear any existing highlight when this is invoked, even if we
// don't get a new target to highlight.
link_highlights_.clear();
for (size_t i = 0; i < highlight_nodes.size(); ++i) {
Node* node = highlight_nodes[i];
if (!node || !node->GetLayoutObject())
continue;
Color highlight_color =
node->GetLayoutObject()->Style()->TapHighlightColor();
// Safari documentation for -webkit-tap-highlight-color says if the
// specified color has 0 alpha, then tap highlighting is disabled.
// http://developer.apple.com/library/safari/#documentation/appleapplications/reference/safaricssref/articles/standardcssproperties.html
if (!highlight_color.Alpha())
continue;
link_highlights_.push_back(LinkHighlightImpl::Create(node, this));
}
UpdateAllLifecyclePhases();
}
void WebViewImpl::AnimateDoubleTapZoom(const IntPoint& point_in_root_frame) {
// TODO(lukasza): https://crbug.com/734209: Add OOPIF support.
if (!MainFrameImpl())
return;
WebRect block_bounds = ComputeBlockBound(point_in_root_frame, false);
float scale;
WebPoint scroll;
ComputeScaleAndScrollForBlockRect(
point_in_root_frame, block_bounds, touchPointPadding,
MinimumPageScaleFactor() * doubleTapZoomAlreadyLegibleRatio, scale,
scroll);
bool still_at_previous_double_tap_scale =
(PageScaleFactor() == double_tap_zoom_page_scale_factor_ &&
double_tap_zoom_page_scale_factor_ != MinimumPageScaleFactor()) ||
double_tap_zoom_pending_;
bool scale_unchanged = fabs(PageScaleFactor() - scale) < minScaleDifference;
bool should_zoom_out = block_bounds.IsEmpty() || scale_unchanged ||
still_at_previous_double_tap_scale;
bool is_animating;
if (should_zoom_out) {
scale = MinimumPageScaleFactor();
IntPoint target_position =
MainFrameImpl()->GetFrameView()->RootFrameToContents(
point_in_root_frame);
is_animating = StartPageScaleAnimation(
target_position, true, scale, doubleTapZoomAnimationDurationInSeconds);
} else {
is_animating = StartPageScaleAnimation(
scroll, false, scale, doubleTapZoomAnimationDurationInSeconds);
}
// TODO(dglazkov): The only reason why we're using isAnimating and not just
// checking for m_layerTreeView->hasPendingPageScaleAnimation() is because of
// fake page scale animation plumbing for testing, which doesn't actually
// initiate a page scale animation.
if (is_animating) {
double_tap_zoom_page_scale_factor_ = scale;
double_tap_zoom_pending_ = true;
}
}
void WebViewImpl::ZoomToFindInPageRect(const WebRect& rect_in_root_frame) {
// TODO(lukasza): https://crbug.com/734209: Add OOPIF support.
if (!MainFrameImpl())
return;
WebRect block_bounds = ComputeBlockBound(
WebPoint(rect_in_root_frame.x + rect_in_root_frame.width / 2,
rect_in_root_frame.y + rect_in_root_frame.height / 2),
true);
if (block_bounds.IsEmpty()) {
// Keep current scale (no need to scroll as x,y will normally already
// be visible). FIXME: Revisit this if it isn't always true.
return;
}
float scale;
WebPoint scroll;
ComputeScaleAndScrollForBlockRect(
WebPoint(rect_in_root_frame.x, rect_in_root_frame.y), block_bounds,
nonUserInitiatedPointPadding, MinimumPageScaleFactor(), scale, scroll);
StartPageScaleAnimation(scroll, false, scale,
findInPageAnimationDurationInSeconds);
}
bool WebViewImpl::ZoomToMultipleTargetsRect(const WebRect& rect_in_root_frame) {
// TODO(lukasza): https://crbug.com/734209: Add OOPIF support.
if (!MainFrameImpl())
return false;
float scale;
WebPoint scroll;
ComputeScaleAndScrollForBlockRect(
WebPoint(rect_in_root_frame.x, rect_in_root_frame.y), rect_in_root_frame,
nonUserInitiatedPointPadding, MinimumPageScaleFactor(), scale, scroll);
if (scale <= PageScaleFactor())
return false;
StartPageScaleAnimation(scroll, false, scale,
multipleTargetsZoomAnimationDurationInSeconds);
return true;
}
#if !defined(OS_MACOSX)
// Mac has no way to open a context menu based on a keyboard event.
WebInputEventResult WebViewImpl::SendContextMenuEvent() {
// The contextMenuController() holds onto the last context menu that was
// popped up on the page until a new one is created. We need to clear
// this menu before propagating the event through the DOM so that we can
// detect if we create a new menu for this event, since we won't create
// a new menu if the DOM swallows the event and the defaultEventHandler does
// not run.
GetPage()->GetContextMenuController().ClearContextMenu();
{
ContextMenuAllowedScope scope;
Frame* focused_frame = GetPage()->GetFocusController().FocusedOrMainFrame();
if (!focused_frame->IsLocalFrame())
return WebInputEventResult::kNotHandled;
// Firefox reveal focus based on "keydown" event but not "contextmenu"
// event, we match FF.
if (Element* focused_element =
ToLocalFrame(focused_frame)->GetDocument()->FocusedElement())
focused_element->scrollIntoViewIfNeeded();
return ToLocalFrame(focused_frame)
->GetEventHandler()
.ShowNonLocatedContextMenu(nullptr, kMenuSourceKeyboard);
}
}
#else
WebInputEventResult WebViewImpl::SendContextMenuEvent() {
return WebInputEventResult::kNotHandled;
}
#endif
void WebViewImpl::ShowContextMenuAtPoint(float x,
float y,
ContextMenuProvider* menu_provider) {
if (!GetPage()->MainFrame()->IsLocalFrame())
return;
{
ContextMenuAllowedScope scope;
GetPage()->GetContextMenuController().ClearContextMenu();
GetPage()->GetContextMenuController().ShowContextMenuAtPoint(
GetPage()->DeprecatedLocalMainFrame(), x, y, menu_provider);
}
}
void WebViewImpl::ShowContextMenuForElement(WebElement element) {
if (!GetPage())
return;
GetPage()->GetContextMenuController().ClearContextMenu();
{
ContextMenuAllowedScope scope;
if (LocalFrame* focused_frame = ToLocalFrame(
GetPage()->GetFocusController().FocusedOrMainFrame())) {
focused_frame->GetEventHandler().ShowNonLocatedContextMenu(
element.Unwrap<Element>());
}
}
}
PagePopup* WebViewImpl::OpenPagePopup(PagePopupClient* client) {
DCHECK(client);
if (HasOpenedPopup())
HidePopups();
DCHECK(!page_popup_);
WebLocalFrameImpl* frame = WebLocalFrameImpl::FromFrame(
client->OwnerElement().GetDocument().GetFrame()->LocalFrameRoot());
WebWidget* popup_widget = client_->CreatePopup(frame, kWebPopupTypePage);
// CreatePopup returns nullptr if this renderer process is about to die.
if (!popup_widget)
return nullptr;
page_popup_ = ToWebPagePopupImpl(popup_widget);
if (!page_popup_->Initialize(this, client)) {
page_popup_->ClosePopup();
page_popup_ = nullptr;
}
EnablePopupMouseWheelEventListener(frame);
return page_popup_.get();
}
void WebViewImpl::ClosePagePopup(PagePopup* popup) {
DCHECK(popup);
WebPagePopupImpl* popup_impl = ToWebPagePopupImpl(popup);
DCHECK_EQ(page_popup_.get(), popup_impl);
if (page_popup_.get() != popup_impl)
return;
page_popup_->ClosePopup();
}
void WebViewImpl::CleanupPagePopup() {
page_popup_ = nullptr;
DisablePopupMouseWheelEventListener();
}
void WebViewImpl::CancelPagePopup() {
if (page_popup_)
page_popup_->Cancel();
}
void WebViewImpl::EnablePopupMouseWheelEventListener(
WebLocalFrameImpl* local_root) {
DCHECK(!popup_mouse_wheel_event_listener_);
Document* document = local_root->GetDocument();
DCHECK(document);
// We register an empty event listener, EmptyEventListener, so that mouse
// wheel events get sent to the WebView.
popup_mouse_wheel_event_listener_ = EmptyEventListener::Create();
document->addEventListener(EventTypeNames::mousewheel,
popup_mouse_wheel_event_listener_, false);
local_root_with_empty_mouse_wheel_listener_ = local_root;
}
void WebViewImpl::DisablePopupMouseWheelEventListener() {
// TODO(kenrb): Concerns the same as in enablePopupMouseWheelEventListener.
// See https://crbug.com/566130
DCHECK(popup_mouse_wheel_event_listener_);
Document* document =
local_root_with_empty_mouse_wheel_listener_->GetDocument();
DCHECK(document);
// Document may have already removed the event listener, for instance, due
// to a navigation, but remove it anyway.
document->removeEventListener(EventTypeNames::mousewheel,
popup_mouse_wheel_event_listener_.Release(),
false);
local_root_with_empty_mouse_wheel_listener_ = nullptr;
}
LocalDOMWindow* WebViewImpl::PagePopupWindow() const {
return page_popup_ ? page_popup_->Window() : nullptr;
}
Frame* WebViewImpl::FocusedCoreFrame() const {
return page_ ? page_->GetFocusController().FocusedOrMainFrame() : nullptr;
}
// WebWidget ------------------------------------------------------------------
void WebViewImpl::Close() {
DCHECK(AllInstances().Contains(this));
AllInstances().erase(this);
if (page_) {
// Initiate shutdown for the entire frameset. This will cause a lot of
// notifications to be sent.
page_->WillBeDestroyed();
page_.Clear();
}
// Reset the delegate to prevent notifications being sent as we're being
// deleted.
client_ = nullptr;
Release(); // Balances a reference acquired in WebView::Create
}
WebSize WebViewImpl::Size() {
return size_;
}
void WebViewImpl::ResizeVisualViewport(const WebSize& new_size) {
GetPage()->GetVisualViewport().SetSize(new_size);
GetPage()->GetVisualViewport().ClampToBoundaries();
}
void WebViewImpl::UpdateICBAndResizeViewport() {
// We'll keep the initial containing block size from changing when the top
// controls hide so that the ICB will always be the same size as the
// viewport with the browser controls shown.
IntSize icb_size = size_;
if (GetBrowserControls().PermittedState() == kWebBrowserControlsBoth &&
!GetBrowserControls().ShrinkViewport()) {
icb_size.Expand(0, -GetBrowserControls().TotalHeight());
}
GetPageScaleConstraintsSet().DidChangeInitialContainingBlockSize(icb_size);
UpdatePageDefinedViewportConstraints(
MainFrameImpl()->GetFrame()->GetDocument()->GetViewportDescription());
UpdateMainFrameLayoutSize();
GetPage()->GetVisualViewport().SetSize(size_);
if (MainFrameImpl()->GetFrameView()) {
MainFrameImpl()->GetFrameView()->SetInitialViewportSize(icb_size);
if (!MainFrameImpl()->GetFrameView()->NeedsLayout())
resize_viewport_anchor_->ResizeFrameView(MainFrameSize());
}
}
void WebViewImpl::UpdateBrowserControlsState(WebBrowserControlsState constraint,
WebBrowserControlsState current,
bool animate) {
WebBrowserControlsState old_permitted_state =
GetBrowserControls().PermittedState();
GetBrowserControls().UpdateConstraintsAndState(constraint, current, animate);
// If the controls are going from a locked hidden to unlocked state, or vice
// versa, the ICB size needs to change but we can't rely on getting a
// WebViewImpl::resize since the top controls shown state may not have
// changed.
if ((old_permitted_state == kWebBrowserControlsHidden &&
constraint == kWebBrowserControlsBoth) ||
(old_permitted_state == kWebBrowserControlsBoth &&
constraint == kWebBrowserControlsHidden)) {
UpdateICBAndResizeViewport();
}
if (layer_tree_view_)
layer_tree_view_->UpdateBrowserControlsState(constraint, current, animate);
}
void WebViewImpl::DidUpdateBrowserControls() {
if (layer_tree_view_) {
layer_tree_view_->SetBrowserControlsShownRatio(
GetBrowserControls().ShownRatio());
layer_tree_view_->SetBrowserControlsHeight(
GetBrowserControls().TopHeight(), GetBrowserControls().BottomHeight(),
GetBrowserControls().ShrinkViewport());
}
WebLocalFrameImpl* main_frame = MainFrameImpl();
if (!main_frame)
return;
LocalFrameView* view = main_frame->GetFrameView();
if (!view)
return;
VisualViewport& visual_viewport = GetPage()->GetVisualViewport();
{
// This object will save the current visual viewport offset w.r.t. the
// document and restore it when the object goes out of scope. It's
// needed since the browser controls adjustment will change the maximum
// scroll offset and we may need to reposition them to keep the user's
// apparent position unchanged.
ResizeViewportAnchor::ResizeScope resize_scope(*resize_viewport_anchor_);
visual_viewport.SetBrowserControlsAdjustment(
GetBrowserControls().UnreportedSizeAdjustment());
}
}
void WebViewImpl::SetOverscrollBehavior(
const WebOverscrollBehavior& overscroll_behavior) {
if (layer_tree_view_)
layer_tree_view_->SetOverscrollBehavior(overscroll_behavior);
}
BrowserControls& WebViewImpl::GetBrowserControls() {
return GetPage()->GetBrowserControls();
}
void WebViewImpl::ResizeViewWhileAnchored(float top_controls_height,
float bottom_controls_height,
bool browser_controls_shrink_layout) {
DCHECK(MainFrameImpl());
GetBrowserControls().SetHeight(top_controls_height, bottom_controls_height,
browser_controls_shrink_layout);
{
// Avoids unnecessary invalidations while various bits of state in
// TextAutosizer are updated.
TextAutosizer::DeferUpdatePageInfo defer_update_page_info(GetPage());
LocalFrameView* frame_view = MainFrameImpl()->GetFrameView();
IntRect old_rect = frame_view->FrameRect();
UpdateICBAndResizeViewport();
IntRect new_rect = frame_view->FrameRect();
frame_view->MarkViewportConstrainedObjectsForLayout(
old_rect.Width() != new_rect.Width(),
old_rect.Height() != new_rect.Height());
}
fullscreen_controller_->UpdateSize();
// Update lifecyle phases immediately to recalculate the minimum scale limit
// for rotation anchoring, and to make sure that no lifecycle states are
// stale if this WebView is embedded in another one.
UpdateAllLifecyclePhases();
}
void WebViewImpl::ResizeWithBrowserControls(
const WebSize& new_size,
float top_controls_height,
float bottom_controls_height,
bool browser_controls_shrink_layout) {
if (should_auto_resize_)
return;
if (size_ == new_size &&
GetBrowserControls().TopHeight() == top_controls_height &&
GetBrowserControls().BottomHeight() == bottom_controls_height &&
GetBrowserControls().ShrinkViewport() == browser_controls_shrink_layout)
return;
if (GetPage()->MainFrame() && !GetPage()->MainFrame()->IsLocalFrame()) {
// Viewport resize for a remote main frame does not require any
// particular action, but the state needs to reflect the correct size
// so that it can be used for initalization if the main frame gets
// swapped to a LocalFrame at a later time.
size_ = new_size;
GetPageScaleConstraintsSet().DidChangeInitialContainingBlockSize(size_);
GetPage()->GetVisualViewport().SetSize(size_);
GetPage()->GetBrowserControls().SetHeight(top_controls_height,
bottom_controls_height,
browser_controls_shrink_layout);
return;
}
WebLocalFrameImpl* main_frame = MainFrameImpl();
if (!main_frame)
return;
LocalFrameView* view = main_frame->GetFrameView();
if (!view)
return;
VisualViewport& visual_viewport = GetPage()->GetVisualViewport();
bool is_rotation =
GetPage()->GetSettings().GetMainFrameResizesAreOrientationChanges() &&
size_.width && ContentsSize().Width() && new_size.width != size_.width &&
!fullscreen_controller_->IsFullscreenOrTransitioning();
size_ = new_size;
FloatSize viewport_anchor_coords(viewportAnchorCoordX, viewportAnchorCoordY);
if (is_rotation) {
RotationViewportAnchor anchor(*view, visual_viewport,
viewport_anchor_coords,
GetPageScaleConstraintsSet());
ResizeViewWhileAnchored(top_controls_height, bottom_controls_height,
browser_controls_shrink_layout);
} else {
ResizeViewportAnchor::ResizeScope resize_scope(*resize_viewport_anchor_);
ResizeViewWhileAnchored(top_controls_height, bottom_controls_height,
browser_controls_shrink_layout);
}
SendResizeEventAndRepaint();
}
void WebViewImpl::Resize(const WebSize& new_size) {
if (should_auto_resize_ || size_ == new_size)
return;
ResizeWithBrowserControls(new_size, GetBrowserControls().TopHeight(),
GetBrowserControls().BottomHeight(),
GetBrowserControls().ShrinkViewport());
}
void WebViewImpl::DidEnterFullscreen() {
fullscreen_controller_->DidEnterFullscreen();
}
void WebViewImpl::DidExitFullscreen() {
fullscreen_controller_->DidExitFullscreen();
}
void WebViewImpl::DidUpdateFullscreenSize() {
fullscreen_controller_->UpdateSize();
}
void WebViewImpl::SetSuppressFrameRequestsWorkaroundFor704763Only(
bool suppress_frame_requests) {
page_->Animator().SetSuppressFrameRequestsWorkaroundFor704763Only(
suppress_frame_requests);
}
void WebViewImpl::BeginFrame(double last_frame_time_monotonic) {
TRACE_EVENT1("blink", "WebViewImpl::beginFrame", "frameTime",
last_frame_time_monotonic);
DCHECK(last_frame_time_monotonic);
if (!MainFrameImpl())
return;
if (WebFrameWidgetBase* widget = MainFrameImpl()->FrameWidget())
widget->UpdateGestureAnimation(last_frame_time_monotonic);
DocumentLifecycle::AllowThrottlingScope throttling_scope(
MainFrameImpl()->GetFrame()->GetDocument()->Lifecycle());
PageWidgetDelegate::Animate(*page_, last_frame_time_monotonic);
if (auto* client = GetValidationMessageClient())
client->LayoutOverlay();
}
void WebViewImpl::UpdateLifecycle(LifecycleUpdate requested_update) {
TRACE_EVENT0("blink", "WebViewImpl::updateAllLifecyclePhases");
if (!MainFrameImpl())
return;
DocumentLifecycle::AllowThrottlingScope throttling_scope(
MainFrameImpl()->GetFrame()->GetDocument()->Lifecycle());
PageWidgetDelegate::UpdateLifecycle(*page_, *MainFrameImpl()->GetFrame(),
requested_update);
UpdateLayerTreeBackgroundColor();
if (requested_update == LifecycleUpdate::kPrePaint)
return;
if (auto* client = GetValidationMessageClient())
client->PaintOverlay();
if (WebDevToolsAgentImpl* devtools = MainFrameDevToolsAgentImpl())
devtools->PaintOverlay();
if (page_color_overlay_)
page_color_overlay_->GetGraphicsLayer()->Paint(nullptr);
// TODO(chrishtr): link highlights don't currently paint themselves, it's
// still driven by cc. Fix this.
for (size_t i = 0; i < link_highlights_.size(); ++i)
link_highlights_[i]->UpdateGeometry();
if (LocalFrameView* view = MainFrameImpl()->GetFrameView()) {
LocalFrame* frame = MainFrameImpl()->GetFrame();
WebWidgetClient* client =
WebLocalFrameImpl::FromFrame(frame)->FrameWidget()->Client();
if (should_dispatch_first_visually_non_empty_layout_ &&
view->IsVisuallyNonEmpty()) {
should_dispatch_first_visually_non_empty_layout_ = false;
// TODO(esprehn): Move users of this callback to something
// better, the heuristic for "visually non-empty" is bad.
client->DidMeaningfulLayout(WebMeaningfulLayout::kVisuallyNonEmpty);
}
if (should_dispatch_first_layout_after_finished_parsing_ &&
frame->GetDocument()->HasFinishedParsing()) {
should_dispatch_first_layout_after_finished_parsing_ = false;
client->DidMeaningfulLayout(WebMeaningfulLayout::kFinishedParsing);
}
if (should_dispatch_first_layout_after_finished_loading_ &&
frame->GetDocument()->IsLoadCompleted()) {
should_dispatch_first_layout_after_finished_loading_ = false;
client->DidMeaningfulLayout(WebMeaningfulLayout::kFinishedLoading);
}
}
}
void WebViewImpl::Paint(WebCanvas* canvas, const WebRect& rect) {
// This should only be used when compositing is not being used for this
// WebView, and it is painting into the recording of its parent.
DCHECK(!IsAcceleratedCompositingActive());
PageWidgetDelegate::Paint(*page_, canvas, rect,
*page_->DeprecatedLocalMainFrame());
}
#if defined(OS_ANDROID)
void WebViewImpl::PaintIgnoringCompositing(WebCanvas* canvas,
const WebRect& rect) {
// This is called on a composited WebViewImpl, but we will ignore it,
// producing all possible content of the WebViewImpl into the WebCanvas.
DCHECK(IsAcceleratedCompositingActive());
PageWidgetDelegate::PaintIgnoringCompositing(
*page_, canvas, rect, *page_->DeprecatedLocalMainFrame());
}
#endif
void WebViewImpl::LayoutAndPaintAsync(
WebLayoutAndPaintAsyncCallback* callback) {
layer_tree_view_->LayoutAndPaintAsync(callback);
}
void WebViewImpl::CompositeAndReadbackAsync(
WebCompositeAndReadbackAsyncCallback* callback) {
layer_tree_view_->CompositeAndReadbackAsync(callback);
}
void WebViewImpl::ThemeChanged() {
if (!GetPage())
return;
if (!GetPage()->MainFrame()->IsLocalFrame())
return;
LocalFrameView* view = GetPage()->DeprecatedLocalMainFrame()->View();
WebRect damaged_rect(0, 0, size_.width, size_.height);
view->InvalidateRect(damaged_rect);
}
void WebViewImpl::EnterFullscreen(LocalFrame& frame) {
fullscreen_controller_->EnterFullscreen(frame);
}
void WebViewImpl::ExitFullscreen(LocalFrame& frame) {
fullscreen_controller_->ExitFullscreen(frame);
}
void WebViewImpl::FullscreenElementChanged(Element* old_element,
Element* new_element) {
fullscreen_controller_->FullscreenElementChanged(old_element, new_element);
}
bool WebViewImpl::HasHorizontalScrollbar() {
return MainFrameImpl()
->GetFrameView()
->LayoutViewportScrollableArea()
->HorizontalScrollbar();
}
bool WebViewImpl::HasVerticalScrollbar() {
return MainFrameImpl()
->GetFrameView()
->LayoutViewportScrollableArea()
->VerticalScrollbar();
}
WebInputEventResult WebViewImpl::DispatchBufferedTouchEvents() {
if (!MainFrameImpl())
return WebInputEventResult::kNotHandled;
if (WebDevToolsAgentImpl* devtools = MainFrameDevToolsAgentImpl())
devtools->DispatchBufferedTouchEvents();
return MainFrameImpl()
->GetFrame()
->GetEventHandler()
.DispatchBufferedTouchEvents();
}
WebInputEventResult WebViewImpl::HandleInputEvent(
const WebCoalescedInputEvent& coalesced_event) {
return HandleInputEventIncludingTouch(coalesced_event);
}
WebInputEventResult WebViewImpl::HandleInputEventInternal(
const WebCoalescedInputEvent& coalesced_event) {
const WebInputEvent& input_event = coalesced_event.Event();
// TODO(dcheng): The fact that this is getting called when there is no local
// main frame is problematic and probably indicates a bug in the input event
// routing code.
if (!MainFrameImpl())
return WebInputEventResult::kNotHandled;
DCHECK(!WebInputEvent::IsTouchEventType(input_event.GetType()));
GetPage()->GetVisualViewport().StartTrackingPinchStats();
TRACE_EVENT1("input,rail", "WebViewImpl::handleInputEvent", "type",
WebInputEvent::GetName(input_event.GetType()));
// If a drag-and-drop operation is in progress, ignore input events.
if (MainFrameImpl()->FrameWidget()->DoingDragAndDrop())
return WebInputEventResult::kHandledSuppressed;
if (dev_tools_emulator_->HandleInputEvent(input_event))
return WebInputEventResult::kHandledSuppressed;
if (WebDevToolsAgentImpl* devtools = MainFrameDevToolsAgentImpl()) {
if (devtools->HandleInputEvent(input_event))
return WebInputEventResult::kHandledSuppressed;
}
// Report the event to be NOT processed by WebKit, so that the browser can
// handle it appropriately.
if (WebFrameWidgetBase::IgnoreInputEvents())
return WebInputEventResult::kNotHandled;
AutoReset<const WebInputEvent*> current_event_change(
&CurrentInputEvent::current_input_event_, &input_event);
UIEventWithKeyState::ClearNewTabModifierSetFromIsolatedWorld();
bool is_pointer_locked = false;
if (WebFrameWidgetBase* widget = MainFrameImpl()->FrameWidget()) {
if (WebWidgetClient* client = widget->Client())
is_pointer_locked = client->IsPointerLocked();
}
if (is_pointer_locked &&
WebInputEvent::IsMouseEventType(input_event.GetType())) {
MainFrameImpl()->FrameWidget()->PointerLockMouseEvent(coalesced_event);
return WebInputEventResult::kHandledSystem;
}
Document& main_frame_document = *MainFrameImpl()->GetFrame()->GetDocument();
if (input_event.GetType() != WebInputEvent::kMouseMove) {
FirstMeaningfulPaintDetector::From(main_frame_document).NotifyInputEvent();
}
if (input_event.GetType() != WebInputEvent::kMouseMove &&
input_event.GetType() != WebInputEvent::kMouseEnter &&
input_event.GetType() != WebInputEvent::kMouseLeave) {
InteractiveDetector* interactive_detector(
InteractiveDetector::From(main_frame_document));
if (interactive_detector) {
interactive_detector->OnInvalidatingInputEvent(
input_event.TimeStampSeconds());
}
}
if (mouse_capture_node_ &&
WebInputEvent::IsMouseEventType(input_event.GetType())) {
TRACE_EVENT1("input", "captured mouse event", "type",
input_event.GetType());
// Save m_mouseCaptureNode since mouseCaptureLost() will clear it.
Node* node = mouse_capture_node_;
// Not all platforms call mouseCaptureLost() directly.
if (input_event.GetType() == WebInputEvent::kMouseUp)
MouseCaptureLost();
std::unique_ptr<UserGestureIndicator> gesture_indicator;
AtomicString event_type;
switch (input_event.GetType()) {
case WebInputEvent::kMouseEnter:
event_type = EventTypeNames::mouseover;
break;
case WebInputEvent::kMouseMove:
event_type = EventTypeNames::mousemove;
break;
case WebInputEvent::kMouseLeave:
event_type = EventTypeNames::mouseout;
break;
case WebInputEvent::kMouseDown:
event_type = EventTypeNames::mousedown;
gesture_indicator = Frame::NotifyUserActivation(
node->GetDocument().GetFrame(), UserGestureToken::kNewGesture);
mouse_capture_gesture_token_ = gesture_indicator->CurrentToken();
break;
case WebInputEvent::kMouseUp:
event_type = EventTypeNames::mouseup;
gesture_indicator = WTF::WrapUnique(
new UserGestureIndicator(std::move(mouse_capture_gesture_token_)));
break;
default:
NOTREACHED();
}
WebMouseEvent transformed_event =
TransformWebMouseEvent(MainFrameImpl()->GetFrameView(),
static_cast<const WebMouseEvent&>(input_event));
node->DispatchMouseEvent(transformed_event, event_type,
transformed_event.click_count);
return WebInputEventResult::kHandledSystem;
}
// FIXME: This should take in the intended frame, not the local frame root.
WebInputEventResult result = PageWidgetDelegate::HandleInputEvent(
*this, coalesced_event, MainFrameImpl()->GetFrame());
if (result != WebInputEventResult::kNotHandled)
return result;
// Unhandled pinch events should adjust the scale.
if (input_event.GetType() == WebInputEvent::kGesturePinchUpdate) {
const WebGestureEvent& pinch_event =
static_cast<const WebGestureEvent&>(input_event);
// For touchpad gestures synthesize a Windows-like wheel event
// to send to any handlers that may exist. Not necessary for touchscreen
// as touch events would have already been sent for the gesture.
if (pinch_event.source_device == kWebGestureDeviceTouchpad) {
result = HandleSyntheticWheelFromTouchpadPinchEvent(pinch_event);
if (result != WebInputEventResult::kNotHandled)
return result;
}
if (pinch_event.data.pinch_update.zoom_disabled)
return WebInputEventResult::kNotHandled;
if (GetPage()->GetVisualViewport().MagnifyScaleAroundAnchor(
pinch_event.data.pinch_update.scale,
FloatPoint(pinch_event.x, pinch_event.y)))
return WebInputEventResult::kHandledSystem;
}
// If the page is close to minimum scale at pinch end, snap to minimum.
if (input_event.GetType() == WebInputEvent::kGesturePinchEnd) {
const WebGestureEvent& pinch_event =
static_cast<const WebGestureEvent&>(input_event);
float min_scale = MinimumPageScaleFactor();
if (pinch_event.source_device == kWebGestureDeviceTouchpad &&
PageScaleFactor() <= min_scale * maximumZoomForSnapToMinimum) {
IntPoint target_position =
MainFrameImpl()->GetFrameView()->ViewportToContents(
IntPoint(pinch_event.x, pinch_event.y));
StartPageScaleAnimation(target_position, true, min_scale,
snapToMiminimumZoomAnimationDurationInSeconds);
}
}
return WebInputEventResult::kNotHandled;
}
void WebViewImpl::SetCursorVisibilityState(bool is_visible) {
if (page_)
page_->SetIsCursorVisible(is_visible);
}
void WebViewImpl::MouseCaptureLost() {
TRACE_EVENT_ASYNC_END0("input", "capturing mouse", this);
mouse_capture_node_ = nullptr;
}
void WebViewImpl::SetFocus(bool enable) {
page_->GetFocusController().SetFocused(enable);
if (enable) {
page_->GetFocusController().SetActive(true);
LocalFrame* focused_frame = page_->GetFocusController().FocusedFrame();
if (focused_frame) {
Element* element = focused_frame->GetDocument()->FocusedElement();
if (element && focused_frame->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.IsNone()) {
// If the selection was cleared while the WebView was not
// focused, then the focus element shows with a focus ring but
// no caret and does respond to keyboard inputs.
focused_frame->GetDocument()->UpdateStyleAndLayoutTree();
if (element->IsTextControl()) {
element->UpdateFocusAppearance(SelectionBehaviorOnFocus::kRestore);
} else if (HasEditableStyle(*element)) {
// updateFocusAppearance() selects all the text of
// contentseditable DIVs. So we set the selection explicitly
// instead. Note that this has the side effect of moving the
// caret back to the beginning of the text.
Position position(element, 0);
focused_frame->Selection().SetSelectionAndEndTyping(
SelectionInDOMTree::Builder().Collapse(position).Build());
}
}
}
ime_accept_events_ = true;
} else {
HidePopups();
// Clear focus on the currently focused frame if any.
if (!page_)
return;
LocalFrame* frame = page_->MainFrame() && page_->MainFrame()->IsLocalFrame()
? page_->DeprecatedLocalMainFrame()
: nullptr;
if (!frame)
return;
LocalFrame* focused_frame = FocusedLocalFrameInWidget();
if (focused_frame) {
// Finish an ongoing composition to delete the composition node.
if (focused_frame->GetInputMethodController().HasComposition()) {
// TODO(editing-dev): The use of
// updateStyleAndLayoutIgnorePendingStylesheets needs to be audited.
// See http://crbug.com/590369 for more details.
focused_frame->GetDocument()
->UpdateStyleAndLayoutIgnorePendingStylesheets();
focused_frame->GetInputMethodController().FinishComposingText(
InputMethodController::kKeepSelection);
}
ime_accept_events_ = false;
}
}
}
bool WebViewImpl::SelectionBounds(WebRect& anchor_web,
WebRect& focus_web) const {
const Frame* frame = FocusedCoreFrame();
if (!frame || !frame->IsLocalFrame())
return false;
const LocalFrame* local_frame = ToLocalFrame(frame);
if (!local_frame)
return false;
LocalFrameView* frame_view = local_frame->View();
if (!frame_view)
return false;
IntRect anchor;
IntRect focus;
if (!local_frame->Selection().ComputeAbsoluteBounds(anchor, focus))
return false;
VisualViewport& visual_viewport = GetPage()->GetVisualViewport();
anchor_web = visual_viewport.RootFrameToViewport(
frame_view->AbsoluteToRootFrame(anchor));
focus_web = visual_viewport.RootFrameToViewport(
frame_view->AbsoluteToRootFrame(focus));
return true;
}
WebColor WebViewImpl::BackgroundColor() const {
if (background_color_override_enabled_)
return background_color_override_;
if (!page_)
return BaseBackgroundColor().Rgb();
if (!page_->MainFrame())
return BaseBackgroundColor().Rgb();
if (!page_->MainFrame()->IsLocalFrame())
return BaseBackgroundColor().Rgb();
LocalFrameView* view = page_->DeprecatedLocalMainFrame()->View();
if (!view)
return BaseBackgroundColor().Rgb();
return view->DocumentBackgroundColor().Rgb();
}
WebPagePopupImpl* WebViewImpl::GetPagePopup() const {
return page_popup_.get();
}
bool WebViewImpl::IsAcceleratedCompositingActive() const {
return root_layer_;
}
void WebViewImpl::WillCloseLayerTreeView() {
if (link_highlights_timeline_) {
link_highlights_.clear();
DetachCompositorAnimationTimeline(link_highlights_timeline_.get());
link_highlights_timeline_.reset();
}
if (layer_tree_view_)
GetPage()->WillCloseLayerTreeView(*layer_tree_view_, nullptr);
SetRootLayer(nullptr);
animation_host_ = nullptr;
mutator_ = nullptr;
layer_tree_view_ = nullptr;
}
void WebViewImpl::DidAcquirePointerLock() {
if (MainFrameImpl())
MainFrameImpl()->FrameWidget()->DidAcquirePointerLock();
}
void WebViewImpl::DidNotAcquirePointerLock() {
if (MainFrameImpl())
MainFrameImpl()->FrameWidget()->DidNotAcquirePointerLock();
}
void WebViewImpl::DidLosePointerLock() {
// Make sure that the main frame wasn't swapped-out when the pointer lock is
// lost. There's a race that can happen when a pointer lock is requested, but
// the browser swaps out the main frame while the pointer lock request is in
// progress. This won't be needed once the main frame is refactored to not use
// the WebViewImpl as its WebWidget.
if (MainFrameImpl())
MainFrameImpl()->FrameWidget()->DidLosePointerLock();
}
// TODO(ekaramad):This method is almost duplicated in WebFrameWidgetImpl as
// well. This code needs to be refactored (http://crbug.com/629721).
bool WebViewImpl::GetCompositionCharacterBounds(WebVector<WebRect>& bounds) {
WebInputMethodController* controller = GetActiveWebInputMethodController();
if (!controller)
return false;
WebRange range = controller->CompositionRange();
if (range.IsEmpty())
return false;
WebLocalFrame* frame = FocusedFrame();
// Only consider frames whose local root is the main frame. For other
// local frames which have different local roots, the corresponding
// WebFrameWidget will handle this task.
if (frame->LocalRoot() != MainFrameImpl())
return false;
size_t character_count = range.length();
size_t offset = range.StartOffset();
WebVector<WebRect> result(character_count);
WebRect webrect;
for (size_t i = 0; i < character_count; ++i) {
if (!frame->FirstRectForCharacterRange(offset + i, 1, webrect)) {
DLOG(ERROR) << "Could not retrieve character rectangle at " << i;
return false;
}
result[i] = webrect;
}
bounds.Swap(result);
return true;
}
// WebView --------------------------------------------------------------------
WebSettingsImpl* WebViewImpl::SettingsImpl() {
if (!web_settings_) {
web_settings_ = WTF::WrapUnique(
new WebSettingsImpl(&page_->GetSettings(), dev_tools_emulator_.Get()));
}
DCHECK(web_settings_);
return web_settings_.get();
}
WebSettings* WebViewImpl::GetSettings() {
return SettingsImpl();
}
WebString WebViewImpl::PageEncoding() const {
if (!page_)
return WebString();
if (!page_->MainFrame()->IsLocalFrame())
return WebString();
// FIXME: Is this check needed?
if (!page_->DeprecatedLocalMainFrame()->GetDocument()->Loader())
return WebString();
return page_->DeprecatedLocalMainFrame()->GetDocument()->EncodingName();
}
WebFrame* WebViewImpl::MainFrame() {
return WebFrame::FromFrame(page_ ? page_->MainFrame() : nullptr);
}
WebLocalFrame* WebViewImpl::FocusedFrame() {
Frame* frame = FocusedCoreFrame();
// TODO(yabinh): focusedCoreFrame() should always return a local frame, and
// the following check should be unnecessary.
// See crbug.com/625068
if (!frame || !frame->IsLocalFrame())
return nullptr;
return WebLocalFrameImpl::FromFrame(ToLocalFrame(frame));
}
void WebViewImpl::SetFocusedFrame(WebFrame* frame) {
if (!frame) {
// Clears the focused frame if any.
Frame* focused_frame = FocusedCoreFrame();
if (focused_frame && focused_frame->IsLocalFrame())
ToLocalFrame(focused_frame)->Selection().SetFrameIsFocused(false);
return;
}
LocalFrame* core_frame = ToWebLocalFrameImpl(frame)->GetFrame();
core_frame->GetPage()->GetFocusController().SetFocusedFrame(core_frame);
}
void WebViewImpl::FocusDocumentView(WebFrame* frame) {
// This is currently only used when replicating focus changes for
// cross-process frames, and |notifyEmbedder| is disabled to avoid sending
// duplicate frameFocused updates from FocusController to the browser
// process, which already knows the latest focused frame.
GetPage()->GetFocusController().FocusDocumentView(
WebFrame::ToCoreFrame(*frame), false /* notifyEmbedder */);
}
void WebViewImpl::SetInitialFocus(bool reverse) {
if (!page_)
return;
Frame* frame = GetPage()->GetFocusController().FocusedOrMainFrame();
if (frame->IsLocalFrame()) {
if (Document* document = ToLocalFrame(frame)->GetDocument())
document->ClearFocusedElement();
}
GetPage()->GetFocusController().SetInitialFocus(
reverse ? kWebFocusTypeBackward : kWebFocusTypeForward);
}
void WebViewImpl::ClearFocusedElement() {
Frame* frame = FocusedCoreFrame();
if (!frame || !frame->IsLocalFrame())
return;
LocalFrame* local_frame = ToLocalFrame(frame);
Document* document = local_frame->GetDocument();
if (!document)
return;
Element* old_focused_element = document->FocusedElement();
document->ClearFocusedElement();
if (!old_focused_element)
return;
// If a text field has focus, we need to make sure the selection controller
// knows to remove selection from it. Otherwise, the text field is still
// processing keyboard events even though focus has been moved to the page and
// keystrokes get eaten as a result.
document->UpdateStyleAndLayoutTree();
if (HasEditableStyle(*old_focused_element) ||
old_focused_element->IsTextControl())
local_frame->Selection().Clear();
}
// TODO(dglazkov): Remove and replace with Node:hasEditableStyle.
// http://crbug.com/612560
static bool IsElementEditable(const Element* element) {
element->GetDocument().UpdateStyleAndLayoutTree();
if (HasEditableStyle(*element))
return true;
if (element->IsTextControl()) {
if (!ToTextControlElement(element)->IsDisabledOrReadOnly())
return true;
}
return EqualIgnoringASCIICase(element->getAttribute(HTMLNames::roleAttr),
"textbox");
}
bool WebViewImpl::ScrollFocusedEditableElementIntoView() {
Frame* frame = GetPage()->MainFrame();
if (!frame)
return false;
Element* element = FocusedElement();
if (!element || !IsElementEditable(element))
return false;
if (frame->IsRemoteFrame()) {
// The rest of the logic here is not implemented for OOPIFs. For now instead
// of implementing the logic below (which involves finding the scale and
// scrolling point), editable elements inside OOPIFs are scrolled into view
// instead. However, a common logic should be implemented for both OOPIFs
// and in-process frames to assure a consistent behavior across all frame
// types (https://crbug.com/784982).
LayoutObject* layout_object = element->GetLayoutObject();
if (!layout_object)
return false;
layout_object->ScrollRectToVisible(element->BoundingBox(),
WebScrollIntoViewParams());
return true;
}
if (!frame->View())
return false;
element->GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
bool zoom_in_to_legible_scale =
web_settings_->AutoZoomFocusedNodeToLegibleScale() &&
!GetPage()->GetVisualViewport().ShouldDisableDesktopWorkarounds();
if (zoom_in_to_legible_scale) {
// When deciding whether to zoom in on a focused text box, we should
// decide not to zoom in if the user won't be able to zoom out. e.g if the
// textbox is within a touch-action: none container the user can't zoom
// back out.
TouchAction action = TouchActionUtil::ComputeEffectiveTouchAction(*element);
if (!(action & TouchAction::kTouchActionPinchZoom))
zoom_in_to_legible_scale = false;
}
float scale;
IntPoint scroll;
bool need_animation;
ComputeScaleAndScrollForFocusedNode(element, zoom_in_to_legible_scale, scale,
scroll, need_animation);
if (need_animation) {
StartPageScaleAnimation(scroll, false, scale,
scrollAndScaleAnimationDurationInSeconds);
}
return true;
}
void WebViewImpl::SmoothScroll(int target_x, int target_y, long duration_ms) {
IntPoint target_position(target_x, target_y);
StartPageScaleAnimation(target_position, false, PageScaleFactor(),
(double)duration_ms / 1000);
}
void WebViewImpl::ComputeScaleAndScrollForFocusedNode(
Node* focused_node,
bool zoom_in_to_legible_scale,
float& new_scale,
IntPoint& new_scroll,
bool& need_animation) {
VisualViewport& visual_viewport = GetPage()->GetVisualViewport();
LocalFrameView* main_frame_view = MainFrameImpl()->GetFrameView();
WebRect caret_in_viewport, unused_end;
SelectionBounds(caret_in_viewport, unused_end);
// 'caretInDocument' is rect encompassing the blinking cursor relative to the
// root document.
IntRect caret_in_document = main_frame_view->RootFrameToDocument(
visual_viewport.ViewportToRootFrame(caret_in_viewport));
LocalFrameView* textbox_view = focused_node->GetDocument().View();
IntRect textbox_rect_in_document =
main_frame_view->RootFrameToDocument(textbox_view->AbsoluteToRootFrame(
PixelSnappedIntRect(focused_node->Node::BoundingBox())));
if (!zoom_in_to_legible_scale) {
new_scale = PageScaleFactor();
} else {
// Pick a scale which is reasonably readable. This is the scale at which
// the caret height will become minReadableCaretHeightForNode (adjusted
// for dpi and font scale factor).
const int min_readable_caret_height_for_node =
textbox_rect_in_document.Height() >= 2 * caret_in_document.Height()
? minReadableCaretHeightForTextArea
: minReadableCaretHeight;
new_scale = ClampPageScaleFactorToLimits(
MaximumLegiblePageScale() * min_readable_caret_height_for_node /
caret_in_document.Height());
new_scale = std::max(new_scale, PageScaleFactor());
}
const float delta_scale = new_scale / PageScaleFactor();
need_animation = false;
// If we are at less than the target zoom level, zoom in.
if (delta_scale > minScaleChangeToTriggerZoom)
need_animation = true;
else
new_scale = PageScaleFactor();
// If the caret is offscreen, then animate.
if (!visual_viewport.VisibleRectInDocument().Contains(caret_in_document))
need_animation = true;
// If the box is partially offscreen and it's possible to bring it fully
// onscreen, then animate.
if (visual_viewport.VisibleRect().Width() >=
textbox_rect_in_document.Width() &&
visual_viewport.VisibleRect().Height() >=
textbox_rect_in_document.Height() &&
!visual_viewport.VisibleRectInDocument().Contains(
textbox_rect_in_document))
need_animation = true;
if (!need_animation)
return;
FloatSize target_viewport_size(visual_viewport.Size());
target_viewport_size.Scale(1 / new_scale);
if (textbox_rect_in_document.Width() <= target_viewport_size.Width()) {
// Field is narrower than screen. Try to leave padding on left so field's
// label is visible, but it's more important to ensure entire field is
// onscreen.
int ideal_left_padding = target_viewport_size.Width() * leftBoxRatio;
int max_left_padding_keeping_box_onscreen =
target_viewport_size.Width() - textbox_rect_in_document.Width();
new_scroll.SetX(textbox_rect_in_document.X() -
std::min<int>(ideal_left_padding,
max_left_padding_keeping_box_onscreen));
} else {
// Field is wider than screen. Try to left-align field, unless caret would
// be offscreen, in which case right-align the caret.
new_scroll.SetX(std::max<int>(textbox_rect_in_document.X(),
caret_in_document.X() +
caret_in_document.Width() + caretPadding -
target_viewport_size.Width()));
}
if (textbox_rect_in_document.Height() <= target_viewport_size.Height()) {
// Field is shorter than screen. Vertically center it.
new_scroll.SetY(
textbox_rect_in_document.Y() -
(target_viewport_size.Height() - textbox_rect_in_document.Height()) /
2);
} else {
// Field is taller than screen. Try to top align field, unless caret would
// be offscreen, in which case bottom-align the caret.
new_scroll.SetY(
std::max<int>(textbox_rect_in_document.Y(),
caret_in_document.Y() + caret_in_document.Height() +
caretPadding - target_viewport_size.Height()));
}
}
void WebViewImpl::AdvanceFocus(bool reverse) {
GetPage()->GetFocusController().AdvanceFocus(reverse ? kWebFocusTypeBackward
: kWebFocusTypeForward);
}
void WebViewImpl::AdvanceFocusAcrossFrames(WebFocusType type,
WebRemoteFrame* from,
WebLocalFrame* to) {
// TODO(alexmos): Pass in proper with sourceCapabilities.
GetPage()->GetFocusController().AdvanceFocusAcrossFrames(
type, ToWebRemoteFrameImpl(from)->GetFrame(),
ToWebLocalFrameImpl(to)->GetFrame());
}
double WebViewImpl::ZoomLevel() {
return zoom_level_;
}
void WebViewImpl::PropagateZoomFactorToLocalFrameRoots(Frame* frame,
float zoom_factor) {
if (frame->IsLocalRoot()) {
LocalFrame* local_frame = ToLocalFrame(frame);
if (Document* document = local_frame->GetDocument()) {
if (!document->IsPluginDocument() ||
!ToPluginDocument(document)->GetPluginView()) {
local_frame->SetPageZoomFactor(zoom_factor);
}
}
}
for (Frame* child = frame->Tree().FirstChild(); child;
child = child->Tree().NextSibling())
PropagateZoomFactorToLocalFrameRoots(child, zoom_factor);
}
double WebViewImpl::SetZoomLevel(double zoom_level) {
if (zoom_level < minimum_zoom_level_)
zoom_level_ = minimum_zoom_level_;
else if (zoom_level > maximum_zoom_level_)
zoom_level_ = maximum_zoom_level_;
else
zoom_level_ = zoom_level;
float zoom_factor =
zoom_factor_override_
? zoom_factor_override_
: static_cast<float>(ZoomLevelToZoomFactor(zoom_level_));
if (zoom_factor_for_device_scale_factor_) {
if (compositor_device_scale_factor_override_) {
// Adjust the page's DSF so that DevicePixelRatio becomes
// m_zoomFactorForDeviceScaleFactor.
GetPage()->SetDeviceScaleFactorDeprecated(
zoom_factor_for_device_scale_factor_ /
compositor_device_scale_factor_override_);
zoom_factor *= compositor_device_scale_factor_override_;
} else {
GetPage()->SetDeviceScaleFactorDeprecated(1.f);
zoom_factor *= zoom_factor_for_device_scale_factor_;
}
}
PropagateZoomFactorToLocalFrameRoots(page_->MainFrame(), zoom_factor);
return zoom_level_;
}
void WebViewImpl::ZoomLimitsChanged(double minimum_zoom_level,
double maximum_zoom_level) {
minimum_zoom_level_ = minimum_zoom_level;
maximum_zoom_level_ = maximum_zoom_level;
client_->ZoomLimitsChanged(minimum_zoom_level_, maximum_zoom_level_);
}
float WebViewImpl::TextZoomFactor() {
return MainFrameImpl()->GetFrame()->TextZoomFactor();
}
float WebViewImpl::SetTextZoomFactor(float text_zoom_factor) {
LocalFrame* frame = MainFrameImpl()->GetFrame();
if (frame->GetWebPluginContainer())
return 1;
frame->SetTextZoomFactor(text_zoom_factor);
return text_zoom_factor;
}
double WebView::ZoomLevelToZoomFactor(double zoom_level) {
return pow(kTextSizeMultiplierRatio, zoom_level);
}
double WebView::ZoomFactorToZoomLevel(double factor)