/*
 * 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 "web/WebViewImpl.h"

#include "core/CSSValueKeywords.h"
#include "core/HTMLNames.h"
#include "core/clipboard/DataObject.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentUserGestureToken.h"
#include "core/dom/Fullscreen.h"
#include "core/dom/LayoutTreeBuilderTraversal.h"
#include "core/dom/Text.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/Editor.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/InputMethodController.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/editing/markers/DocumentMarkerController.h"
#include "core/editing/serializers/HTMLInterchange.h"
#include "core/editing/serializers/Serialization.h"
#include "core/events/KeyboardEvent.h"
#include "core/events/UIEventWithKeyState.h"
#include "core/events/WheelEvent.h"
#include "core/fetch/UniqueIdentifier.h"
#include "core/frame/BrowserControls.h"
#include "core/frame/EventHandlerRegistry.h"
#include "core/frame/FrameHost.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/PageScaleConstraintsSet.h"
#include "core/frame/RemoteFrame.h"
#include "core/frame/Settings.h"
#include "core/frame/SmartClip.h"
#include "core/frame/UseCounter.h"
#include "core/frame/VisualViewport.h"
#include "core/html/HTMLMediaElement.h"
#include "core/html/HTMLPlugInElement.h"
#include "core/html/HTMLTextAreaElement.h"
#include "core/input/EventHandler.h"
#include "core/input/TouchActionUtil.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/TextAutosizer.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/loader/FrameLoadRequest.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/FrameLoaderClient.h"
#include "core/loader/FrameLoaderStateMachine.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/PagePopupClient.h"
#include "core/page/PointerLockController.h"
#include "core/page/ScopedPageSuspender.h"
#include "core/page/TouchDisambiguation.h"
#include "core/page/scrolling/TopDocumentRootScrollerController.h"
#include "core/paint/PaintLayer.h"
#include "core/timing/DOMWindowPerformance.h"
#include "core/timing/Performance.h"
#include "modules/accessibility/AXObject.h"
#include "modules/accessibility/AXObjectCacheImpl.h"
#include "modules/credentialmanager/CredentialManagerClient.h"
#include "modules/encryptedmedia/MediaKeysController.h"
#include "modules/storage/StorageNamespaceController.h"
#include "modules/webgl/WebGLRenderingContext.h"
#include "platform/ContextMenu.h"
#include "platform/ContextMenuItem.h"
#include "platform/Cursor.h"
#include "platform/Histogram.h"
#include "platform/KeyboardCodes.h"
#include "platform/PlatformGestureEvent.h"
#include "platform/PlatformMouseEvent.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/UserGestureIndicator.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/FirstPaintInvalidationTracking.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/Image.h"
#include "platform/graphics/ImageBuffer.h"
#include "platform/graphics/gpu/DrawingBuffer.h"
#include "platform/graphics/paint/DrawingRecorder.h"
#include "platform/image-decoders/ImageDecoder.h"
#include "platform/scroll/ScrollbarTheme.h"
#include "platform/tracing/TraceEvent.h"
#include "platform/weborigin/SchemeRegistry.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/WebLayerTreeView.h"
#include "public/platform/WebScheduler.h"
#include "public/platform/WebTextInputInfo.h"
#include "public/platform/WebURLRequest.h"
#include "public/platform/WebVector.h"
#include "public/platform/WebViewScheduler.h"
#include "public/web/WebAXObject.h"
#include "public/web/WebActiveWheelFlingParameters.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 "web/CompositionUnderlineVectorBuilder.h"
#include "web/CompositorMutatorImpl.h"
#include "web/CompositorProxyClientImpl.h"
#include "web/ContextFeaturesClientImpl.h"
#include "web/ContextMenuAllowedScope.h"
#include "web/DatabaseClientImpl.h"
#include "web/DedicatedWorkerMessagingProxyProviderImpl.h"
#include "web/DevToolsEmulator.h"
#include "web/FullscreenController.h"
#include "web/InspectorOverlay.h"
#include "web/LinkHighlightImpl.h"
#include "web/PageOverlay.h"
#include "web/PrerendererClientImpl.h"
#include "web/ResizeViewportAnchor.h"
#include "web/RotationViewportAnchor.h"
#include "web/SpeechRecognitionClientProxy.h"
#include "web/StorageQuotaClientImpl.h"
#include "web/ValidationMessageClientImpl.h"
#include "web/WebDevToolsAgentImpl.h"
#include "web/WebInputEventConversion.h"
#include "web/WebInputMethodControllerImpl.h"
#include "web/WebLocalFrameImpl.h"
#include "web/WebPagePopupImpl.h"
#include "web/WebPluginContainerImpl.h"
#include "web/WebRemoteFrameImpl.h"
#include "web/WebSettingsImpl.h"
#include "wtf/AutoReset.h"
#include "wtf/CurrentTime.h"
#include "wtf/PtrUtil.h"
#include "wtf/RefPtr.h"
#include <memory>

#if 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 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::textSizeMultiplierRatio = 1.2;
const double WebView::minTextSizeMultiplier = 0.5;
const double WebView::maxTextSizeMultiplier = 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<ScopedPageSuspender>>& pageSuspenderStack() {
  DEFINE_STATIC_LOCAL(Vector<std::unique_ptr<ScopedPageSuspender>>,
                      suspenderStack, ());
  return suspenderStack;
}

static bool shouldUseExternalPopupMenus = false;

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(CPPEventListenerType) {}

  void handleEvent(ExecutionContext* executionContext, Event*) override {}
};

class ColorOverlay final : public PageOverlay::Delegate {
 public:
  explicit ColorOverlay(WebColor color) : m_color(color) {}

 private:
  void paintPageOverlay(const PageOverlay& pageOverlay,
                        GraphicsContext& graphicsContext,
                        const WebSize& size) const override {
    if (DrawingRecorder::useCachedDrawingIfPossible(
            graphicsContext, pageOverlay, DisplayItem::kPageOverlay))
      return;
    FloatRect rect(0, 0, size.width, size.height);
    DrawingRecorder drawingRecorder(graphicsContext, pageOverlay,
                                    DisplayItem::kPageOverlay, rect);
    graphicsContext.fillRect(rect, m_color);
  }

  WebColor m_color;
};

}  // namespace

// WebView ----------------------------------------------------------------

WebView* WebView::create(WebViewClient* client,
                         WebPageVisibilityState visibilityState) {
  // Pass the WebViewImpl's self-reference to the caller.
  return WebViewImpl::create(client, visibilityState);
}

WebViewImpl* WebViewImpl::create(WebViewClient* client,
                                 WebPageVisibilityState visibilityState) {
  // Pass the WebViewImpl's self-reference to the caller.
  return adoptRef(new WebViewImpl(client, visibilityState)).leakRef();
}

void WebView::setUseExternalPopupMenus(bool useExternalPopupMenus) {
  shouldUseExternalPopupMenus = useExternalPopupMenus;
}

void WebView::updateVisitedLinkState(unsigned long long linkHash) {
  Page::visitedStateChanged(linkHash);
}

void WebView::resetVisitedLinkState(bool invalidateVisitedLinkHashes) {
  Page::allVisitedStateChanged(invalidateVisitedLinkHashes);
}

void WebView::willEnterModalLoop() {
  pageSuspenderStack().append(makeUnique<ScopedPageSuspender>());
}

void WebView::didExitModalLoop() {
  DCHECK(pageSuspenderStack().size());
  pageSuspenderStack().pop_back();
}

void WebViewImpl::setMainFrame(WebFrame* frame) {
  frame->toImplBase()->initializeCoreFrame(&page()->frameHost(), 0, nullAtom,
                                           nullAtom);
}

void WebViewImpl::setCredentialManagerClient(
    WebCredentialManagerClient* webCredentialManagerClient) {
  DCHECK(m_page);
  provideCredentialManagerClientTo(
      *m_page, new CredentialManagerClient(webCredentialManagerClient));
}

void WebViewImpl::setPrerendererClient(
    WebPrerendererClient* prerendererClient) {
  DCHECK(m_page);
  providePrerendererClientTo(*m_page,
                             new PrerendererClientImpl(prerendererClient));
}

void WebViewImpl::setSpellCheckClient(WebSpellCheckClient* spellCheckClient) {
  m_spellCheckClient = spellCheckClient;
}

// static
HashSet<WebViewImpl*>& WebViewImpl::allInstances() {
  DEFINE_STATIC_LOCAL(HashSet<WebViewImpl*>, allInstances, ());
  return allInstances;
}

WebViewImpl::WebViewImpl(WebViewClient* client,
                         WebPageVisibilityState visibilityState)
    : m_client(client),
      m_spellCheckClient(nullptr),
      m_chromeClientImpl(ChromeClientImpl::create(this)),
      m_contextMenuClientImpl(this),
      m_editorClientImpl(this),
      m_spellCheckerClientImpl(this),
      m_storageClientImpl(this),
      m_shouldAutoResize(false),
      m_zoomLevel(0),
      m_minimumZoomLevel(zoomFactorToZoomLevel(minTextSizeMultiplier)),
      m_maximumZoomLevel(zoomFactorToZoomLevel(maxTextSizeMultiplier)),
      m_zoomFactorForDeviceScaleFactor(0.f),
      m_maximumLegibleScale(1),
      m_doubleTapZoomPageScaleFactor(0),
      m_doubleTapZoomPending(false),
      m_enableFakePageScaleAnimationForTesting(false),
      m_fakePageScaleAnimationPageScaleFactor(0),
      m_fakePageScaleAnimationUseAnchor(false),
      m_ignoreInputEvents(false),
      m_compositorDeviceScaleFactorOverride(0),
      m_suppressNextKeypressEvent(false),
      m_imeAcceptEvents(true),
      m_devToolsEmulator(nullptr),
      m_isTransparent(false),
      m_tabsToLinks(false),
      m_layerTreeView(nullptr),
      m_rootLayer(nullptr),
      m_rootGraphicsLayer(nullptr),
      m_visualViewportContainerLayer(nullptr),
      m_matchesHeuristicsForGpuRasterization(false),
      m_flingModifier(0),
      m_flingSourceDevice(WebGestureDeviceUninitialized),
      m_fullscreenController(FullscreenController::create(this)),
      m_baseBackgroundColor(Color::white),
      m_backgroundColorOverride(Color::transparent),
      m_zoomFactorOverride(0),
      m_userGestureObserved(false),
      m_shouldDispatchFirstVisuallyNonEmptyLayout(false),
      m_shouldDispatchFirstLayoutAfterFinishedParsing(false),
      m_shouldDispatchFirstLayoutAfterFinishedLoading(false),
      m_displayMode(WebDisplayModeBrowser),
      m_elasticOverscroll(FloatSize()),
      m_mutator(nullptr),
      m_scheduler(wrapUnique(Platform::current()
                                 ->currentThread()
                                 ->scheduler()
                                 ->createWebViewScheduler(this, this)
                                 .release())),
      m_lastFrameTimeMonotonic(0),
      m_overrideCompositorVisibility(false) {
  Page::PageClients pageClients;
  pageClients.chromeClient = m_chromeClientImpl.get();
  pageClients.contextMenuClient = &m_contextMenuClientImpl;
  pageClients.editorClient = &m_editorClientImpl;
  pageClients.spellCheckerClient = &m_spellCheckerClientImpl;

  m_page = Page::createOrdinary(pageClients);
  MediaKeysController::provideMediaKeysTo(*m_page, &m_mediaKeysClientImpl);
  provideSpeechRecognitionTo(
      *m_page, SpeechRecognitionClientProxy::create(
                   client ? client->speechRecognizer() : nullptr));
  provideContextFeaturesTo(*m_page, ContextFeaturesClientImpl::create());
  provideDatabaseClientTo(*m_page, DatabaseClientImpl::create());

  provideStorageQuotaClientTo(*m_page, StorageQuotaClientImpl::create());
  m_page->setValidationMessageClient(
      ValidationMessageClientImpl::create(*this));
  provideDedicatedWorkerMessagingProxyProviderTo(
      *m_page, DedicatedWorkerMessagingProxyProviderImpl::create());
  StorageNamespaceController::provideStorageNamespaceTo(*m_page,
                                                        &m_storageClientImpl);

  setVisibilityState(visibilityState, true);

  initializeLayerTreeView();

  m_devToolsEmulator = DevToolsEmulator::create(this);

  allInstances().add(this);

  m_pageImportanceSignals.setObserver(client);
  m_resizeViewportAnchor = new ResizeViewportAnchor(*m_page);
}

WebViewImpl::~WebViewImpl() {
  DCHECK(!m_page);

  // Each highlight uses m_owningWebViewImpl->m_linkHighlightsTimeline
  // in destructor. m_linkHighlightsTimeline might be destroyed earlier
  // than m_linkHighlights.
  DCHECK(m_linkHighlights.isEmpty());
}

WebViewImpl::UserGestureNotifier::UserGestureNotifier(WebViewImpl* view)
    // TODO(kenrb, alexmos): |m_frame| should be set to the local root frame,
    // not the main frame. See crbug.com/589894.
    : m_frame(view->mainFrameImpl()),
      m_userGestureObserved(&view->m_userGestureObserved) {
  DCHECK(m_userGestureObserved);
}

WebViewImpl::UserGestureNotifier::~UserGestureNotifier() {
  if (!*m_userGestureObserved && m_frame &&
      m_frame->frame()->document()->hasReceivedUserGesture()) {
    *m_userGestureObserved = true;
    if (m_frame && m_frame->autofillClient())
      m_frame->autofillClient()->firstUserGestureObserved();
  }
}

WebDevToolsAgentImpl* WebViewImpl::mainFrameDevToolsAgentImpl() {
  WebLocalFrameImpl* mainFrame = mainFrameImpl();
  return mainFrame ? mainFrame->devToolsAgentImpl() : nullptr;
}

InspectorOverlay* WebViewImpl::inspectorOverlay() {
  if (WebDevToolsAgentImpl* devtools = mainFrameDevToolsAgentImpl())
    return devtools->overlay();
  return nullptr;
}

WebLocalFrameImpl* WebViewImpl::mainFrameImpl() const {
  return m_page && m_page->mainFrame() && m_page->mainFrame()->isLocalFrame()
             ? WebLocalFrameImpl::fromFrame(m_page->deprecatedLocalMainFrame())
             : nullptr;
}

bool WebViewImpl::tabKeyCyclesThroughElements() const {
  DCHECK(m_page);
  return m_page->tabKeyCyclesThroughElements();
}

void WebViewImpl::setTabKeyCyclesThroughElements(bool value) {
  if (m_page)
    m_page->setTabKeyCyclesThroughElements(value);
}

void WebViewImpl::handleMouseLeave(LocalFrame& mainFrame,
                                   const WebMouseEvent& event) {
  m_client->setMouseOverURL(WebURL());
  PageWidgetEventHandler::handleMouseLeave(mainFrame, event);
}

void WebViewImpl::handleMouseDown(LocalFrame& mainFrame,
                                  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.
  RefPtr<WebPagePopupImpl> pagePopup;
  if (event.button == WebMouseEvent::Button::Left) {
    pagePopup = m_pagePopup;
    hidePopups();
    DCHECK(!m_pagePopup);
  }

  // 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.
  IntPoint point(event.x, event.y);
  if (event.button == WebMouseEvent::Button::Left &&
      m_page->mainFrame()->isLocalFrame()) {
    point =
        m_page->deprecatedLocalMainFrame()->view()->rootFrameToContents(point);
    HitTestResult result(
        m_page->deprecatedLocalMainFrame()->eventHandler().hitTestResultAtPoint(
            point));
    result.setToShadowHostIfInUserAgentShadowRoot();
    Node* hitNode = result.innerNodeOrImageMapImage();

    if (!result.scrollbar() && hitNode && hitNode->layoutObject() &&
        hitNode->layoutObject()->isEmbeddedObject()) {
      m_mouseCaptureNode = hitNode;
      TRACE_EVENT_ASYNC_BEGIN0("input", "capturing mouse", this);
    }
  }

  PageWidgetEventHandler::handleMouseDown(mainFrame, event);

  if (event.button == WebMouseEvent::Button::Left && m_mouseCaptureNode)
    m_mouseCaptureGestureToken =
        mainFrame.eventHandler().takeLastMouseDownGestureToken();

  if (m_pagePopup && pagePopup &&
      m_pagePopup->hasSamePopupClient(pagePopup.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 (!page()->settings().showContextMenuOnMouseUp()) {
#if OS(MACOSX)
    if (event.button == WebMouseEvent::Button::Right ||
        (event.button == WebMouseEvent::Button::Left &&
         event.modifiers & WebMouseEvent::ControlKey))
      mouseContextMenu(event);
#else
    if (event.button == WebMouseEvent::Button::Right)
      mouseContextMenu(event);
#endif
  }
}

void WebViewImpl::setDisplayMode(WebDisplayMode mode) {
  m_displayMode = mode;
  if (!mainFrameImpl() || !mainFrameImpl()->frameView())
    return;

  mainFrameImpl()->frameView()->setDisplayMode(mode);
}

void WebViewImpl::mouseContextMenu(const WebMouseEvent& event) {
  if (!mainFrameImpl() || !mainFrameImpl()->frameView())
    return;

  m_page->contextMenuController().clearContextMenu();

  PlatformMouseEventBuilder pme(mainFrameImpl()->frameView(), event);

  // Find the right target frame. See issue 1186900.
  HitTestResult result = hitTestResultForRootFramePos(pme.position());
  Frame* targetFrame;
  if (result.innerNodeOrImageMapImage())
    targetFrame = result.innerNodeOrImageMapImage()->document().frame();
  else
    targetFrame = m_page->focusController().focusedOrMainFrame();

  if (!targetFrame->isLocalFrame())
    return;

  LocalFrame* targetLocalFrame = toLocalFrame(targetFrame);

#if OS(WIN)
  targetLocalFrame->view()->setCursor(pointerCursor());
#endif

  {
    ContextMenuAllowedScope scope;
    targetLocalFrame->eventHandler().sendContextMenuEvent(pme, nullptr);
  }
  // Actually showing the context menu is handled by the ContextMenuClient
  // implementation...
}

void WebViewImpl::handleMouseUp(LocalFrame& mainFrame,
                                const WebMouseEvent& event) {
  PageWidgetEventHandler::handleMouseUp(mainFrame, event);

  if (page()->settings().showContextMenuOnMouseUp()) {
    // 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::Right)
      mouseContextMenu(event);
  }
}

WebInputEventResult WebViewImpl::handleMouseWheel(
    LocalFrame& mainFrame,
    const WebMouseWheelEvent& event) {
  // Halt an in-progress fling on a wheel tick.
  if (!event.hasPreciseScrollingDeltas)
    endActiveFlingAnimation();

  hidePopups();
  return PageWidgetEventHandler::handleMouseWheel(mainFrame, event);
}

WebGestureEvent WebViewImpl::createGestureScrollEventFromFling(
    WebInputEvent::Type type,
    WebGestureDevice sourceDevice) const {
  WebGestureEvent gestureEvent;
  gestureEvent.type = type;
  gestureEvent.sourceDevice = sourceDevice;
  gestureEvent.timeStampSeconds = WTF::monotonicallyIncreasingTime();
  gestureEvent.x = m_positionOnFlingStart.x;
  gestureEvent.y = m_positionOnFlingStart.y;
  gestureEvent.globalX = m_globalPositionOnFlingStart.x;
  gestureEvent.globalY = m_globalPositionOnFlingStart.y;
  gestureEvent.modifiers = m_flingModifier;
  return gestureEvent;
}

bool WebViewImpl::scrollBy(const WebFloatSize& delta,
                           const WebFloatSize& velocity) {
  DCHECK_NE(m_flingSourceDevice, WebGestureDeviceUninitialized);
  if (!m_page || !m_page->mainFrame() || !m_page->mainFrame()->isLocalFrame() ||
      !m_page->deprecatedLocalMainFrame()->view())
    return false;

  if (m_flingSourceDevice == WebGestureDeviceTouchpad) {
    WebMouseWheelEvent syntheticWheel;
    const float tickDivisor = WheelEvent::TickMultiplier;

    syntheticWheel.type = WebInputEvent::MouseWheel;
    syntheticWheel.timeStampSeconds = WTF::monotonicallyIncreasingTime();
    syntheticWheel.deltaX = delta.width;
    syntheticWheel.deltaY = delta.height;
    syntheticWheel.wheelTicksX = delta.width / tickDivisor;
    syntheticWheel.wheelTicksY = delta.height / tickDivisor;
    syntheticWheel.hasPreciseScrollingDeltas = true;
    syntheticWheel.x = m_positionOnFlingStart.x;
    syntheticWheel.y = m_positionOnFlingStart.y;
    syntheticWheel.globalX = m_globalPositionOnFlingStart.x;
    syntheticWheel.globalY = m_globalPositionOnFlingStart.y;
    syntheticWheel.modifiers = m_flingModifier;

    if (handleMouseWheel(*m_page->deprecatedLocalMainFrame(), syntheticWheel) !=
        WebInputEventResult::NotHandled)
      return true;

    // TODO(dtapuska): Remove these GSB/GSE sequences when trackpad latching is
    // implemented; see crbug.com/526463.
    WebGestureEvent syntheticScrollBegin = createGestureScrollEventFromFling(
        WebInputEvent::GestureScrollBegin, WebGestureDeviceTouchpad);
    syntheticScrollBegin.data.scrollBegin.deltaXHint = delta.width;
    syntheticScrollBegin.data.scrollBegin.deltaYHint = delta.height;
    syntheticScrollBegin.data.scrollBegin.inertialPhase =
        WebGestureEvent::MomentumPhase;
    handleGestureEvent(syntheticScrollBegin);

    WebGestureEvent syntheticScrollUpdate = createGestureScrollEventFromFling(
        WebInputEvent::GestureScrollUpdate, WebGestureDeviceTouchpad);
    syntheticScrollUpdate.data.scrollUpdate.deltaX = delta.width;
    syntheticScrollUpdate.data.scrollUpdate.deltaY = delta.height;
    syntheticScrollUpdate.data.scrollUpdate.velocityX = velocity.width;
    syntheticScrollUpdate.data.scrollUpdate.velocityY = velocity.height;
    syntheticScrollUpdate.data.scrollUpdate.inertialPhase =
        WebGestureEvent::MomentumPhase;
    bool scrollUpdateHandled = handleGestureEvent(syntheticScrollUpdate) !=
                               WebInputEventResult::NotHandled;

    WebGestureEvent syntheticScrollEnd = createGestureScrollEventFromFling(
        WebInputEvent::GestureScrollEnd, WebGestureDeviceTouchpad);
    syntheticScrollEnd.data.scrollEnd.inertialPhase =
        WebGestureEvent::MomentumPhase;
    handleGestureEvent(syntheticScrollEnd);
    return scrollUpdateHandled;
  } else {
    WebGestureEvent syntheticGestureEvent = createGestureScrollEventFromFling(
        WebInputEvent::GestureScrollUpdate, WebGestureDeviceTouchscreen);
    syntheticGestureEvent.data.scrollUpdate.preventPropagation = true;
    syntheticGestureEvent.data.scrollUpdate.deltaX = delta.width;
    syntheticGestureEvent.data.scrollUpdate.deltaY = delta.height;
    syntheticGestureEvent.data.scrollUpdate.velocityX = velocity.width;
    syntheticGestureEvent.data.scrollUpdate.velocityY = velocity.height;
    syntheticGestureEvent.data.scrollUpdate.inertialPhase =
        WebGestureEvent::MomentumPhase;

    return handleGestureEvent(syntheticGestureEvent) !=
           WebInputEventResult::NotHandled;
  }
}

WebInputEventResult WebViewImpl::handleGestureEvent(
    const WebGestureEvent& event) {
  if (!m_client)
    return WebInputEventResult::NotHandled;

  WebInputEventResult eventResult = WebInputEventResult::NotHandled;
  bool eventCancelled = false;  // for disambiguation

  // Special handling for slow-path fling gestures.
  switch (event.type) {
    case WebInputEvent::GestureFlingStart: {
      if (mainFrameImpl()
              ->frame()
              ->eventHandler()
              .isScrollbarHandlingGestures())
        break;
      endActiveFlingAnimation();
      m_client->cancelScheduledContentIntents();
      m_positionOnFlingStart = WebPoint(event.x, event.y);
      m_globalPositionOnFlingStart = WebPoint(event.globalX, event.globalY);
      m_flingModifier = event.modifiers;
      m_flingSourceDevice = event.sourceDevice;
      DCHECK_NE(m_flingSourceDevice, WebGestureDeviceUninitialized);
      std::unique_ptr<WebGestureCurve> flingCurve =
          wrapUnique(Platform::current()->createFlingAnimationCurve(
              event.sourceDevice,
              WebFloatPoint(event.data.flingStart.velocityX,
                            event.data.flingStart.velocityY),
              WebSize()));
      DCHECK(flingCurve);
      m_gestureAnimation = WebActiveGestureAnimation::createAtAnimationStart(
          std::move(flingCurve), this);
      mainFrameImpl()->frameWidget()->scheduleAnimation();
      eventResult = WebInputEventResult::HandledSystem;

      // Plugins may need to see GestureFlingStart to balance
      // GestureScrollBegin (since the former replaces GestureScrollEnd when
      // transitioning to a fling).
      PlatformGestureEventBuilder platformEvent(mainFrameImpl()->frameView(),
                                                event);
      // TODO(dtapuska): Why isn't the response used?
      mainFrameImpl()->frame()->eventHandler().handleGestureScrollEvent(
          platformEvent);

      m_client->didHandleGestureEvent(event, eventCancelled);
      return WebInputEventResult::HandledSystem;
    }
    case WebInputEvent::GestureFlingCancel:
      if (endActiveFlingAnimation())
        eventResult = WebInputEventResult::HandledSuppressed;

      m_client->didHandleGestureEvent(event, eventCancelled);
      return eventResult;
    default:
      break;
  }

  PlatformGestureEventBuilder platformEvent(mainFrameImpl()->frameView(),
                                            event);

  // Special handling for double tap and scroll events as we don't want to
  // hit test for them.
  switch (event.type) {
    case WebInputEvent::GestureDoubleTap:
      if (m_webSettings->doubleTapToZoomEnabled() &&
          minimumPageScaleFactor() != maximumPageScaleFactor()) {
        m_client->cancelScheduledContentIntents();
        animateDoubleTapZoom(platformEvent.position());
      }
      // GestureDoubleTap is currently only used by Android for zooming. For
      // WebCore, GestureTap with tap count = 2 is used instead. So we drop
      // GestureDoubleTap here.
      eventResult = WebInputEventResult::HandledSystem;
      m_client->didHandleGestureEvent(event, eventCancelled);
      return eventResult;
    case WebInputEvent::GestureScrollBegin:
      m_client->cancelScheduledContentIntents();
    case WebInputEvent::GestureScrollEnd:
    case WebInputEvent::GestureScrollUpdate:
    case WebInputEvent::GestureFlingStart:
      // 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.
      eventResult =
          mainFrameImpl()->frame()->eventHandler().handleGestureScrollEvent(
              platformEvent);
      m_client->didHandleGestureEvent(event, eventCancelled);
      return eventResult;
    case WebInputEvent::GesturePinchBegin:
    case WebInputEvent::GesturePinchEnd:
    case WebInputEvent::GesturePinchUpdate:
      return WebInputEventResult::NotHandled;
    default:
      break;
  }

  // Hit test across all frames and do touch adjustment as necessary for the
  // event type.
  GestureEventWithHitTestResults targetedEvent =
      m_page->deprecatedLocalMainFrame()->eventHandler().targetGestureEvent(
          platformEvent);

  // Handle link highlighting outside the main switch to avoid getting lost in
  // the complicated set of cases handled below.
  switch (event.type) {
    case WebInputEvent::GestureShowPress:
      // Queue a highlight animation, then hand off to regular handler.
      enableTapHighlightAtPoint(targetedEvent);
      break;
    case WebInputEvent::GestureTapCancel:
    case WebInputEvent::GestureTap:
    case WebInputEvent::GestureLongPress:
      for (size_t i = 0; i < m_linkHighlights.size(); ++i)
        m_linkHighlights[i]->startHighlightAnimationIfNeeded();
      break;
    default:
      break;
  }

  switch (event.type) {
    case WebInputEvent::GestureTap: {
      // 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 tap on an
      // element from immediately reopening the same popup.
      RefPtr<WebPagePopupImpl> pagePopup = m_pagePopup;
      hidePopups();
      DCHECK(!m_pagePopup);

      m_client->cancelScheduledContentIntents();
      if (detectContentOnTouch(targetedEvent)) {
        eventResult = WebInputEventResult::HandledSystem;
        break;
      }

      // 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* devTools = mainFrameDevToolsAgentImpl();
      VisualViewport& visualViewport = page()->frameHost().visualViewport();
      bool screencastEnabled = devTools && devTools->screencastEnabled();
      if (event.data.tap.width > 0 &&
          !visualViewport.shouldDisableDesktopWorkarounds() &&
          !screencastEnabled) {
        IntRect boundingBox(visualViewport.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 visualViewportOffset =
            flooredIntSize(visualViewport.scrollOffset());

        if (m_webSettings->multiTargetTapNotificationEnabled()) {
          Vector<IntRect> goodTargets;
          HeapVector<Member<Node>> highlightNodes;
          findGoodTouchTargets(boundingBox, mainFrameImpl()->frame(),
                               goodTargets, highlightNodes);
          // 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 (goodTargets.size() >= 2 && m_client &&
              m_client->didTapMultipleTargets(visualViewportOffset, boundingBox,
                                              goodTargets)) {
            enableTapHighlights(highlightNodes);
            for (size_t i = 0; i < m_linkHighlights.size(); ++i)
              m_linkHighlights[i]->startHighlightAnimationIfNeeded();
            eventResult = WebInputEventResult::HandledSystem;
            eventCancelled = true;
            break;
          }
        }
      }

      eventResult = mainFrameImpl()->frame()->eventHandler().handleGestureEvent(
          targetedEvent);

      if (m_pagePopup && pagePopup &&
          m_pagePopup->hasSamePopupClient(pagePopup.get())) {
        // The tap triggered a page popup that is the same as the one we just
        // closed.  It needs to be closed.
        cancelPagePopup();
      }
      break;
    }
    case WebInputEvent::GestureTwoFingerTap:
    case WebInputEvent::GestureLongPress:
    case WebInputEvent::GestureLongTap: {
      if (!mainFrameImpl() || !mainFrameImpl()->frameView())
        break;

      m_client->cancelScheduledContentIntents();
      m_page->contextMenuController().clearContextMenu();
      {
        ContextMenuAllowedScope scope;
        eventResult =
            mainFrameImpl()->frame()->eventHandler().handleGestureEvent(
                targetedEvent);
      }

      break;
    }
    case WebInputEvent::GestureShowPress:
      m_client->cancelScheduledContentIntents();
    case WebInputEvent::GestureTapDown:
    case WebInputEvent::GestureTapCancel:
    case WebInputEvent::GestureTapUnconfirmed: {
      eventResult = mainFrameImpl()->frame()->eventHandler().handleGestureEvent(
          targetedEvent);
      break;
    }
    default:
      NOTREACHED();
  }
  m_client->didHandleGestureEvent(event, eventCancelled);
  return eventResult;
}

WebInputEventResult WebViewImpl::handleSyntheticWheelFromTouchpadPinchEvent(
    const WebGestureEvent& pinchEvent) {
  DCHECK_EQ(pinchEvent.type, WebInputEvent::GesturePinchUpdate);

  // 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 wheelEvent;
  wheelEvent.type = WebInputEvent::MouseWheel;
  wheelEvent.timeStampSeconds = pinchEvent.timeStampSeconds;
  wheelEvent.windowX = wheelEvent.x = pinchEvent.x;
  wheelEvent.windowY = wheelEvent.y = pinchEvent.y;
  wheelEvent.globalX = pinchEvent.globalX;
  wheelEvent.globalY = pinchEvent.globalY;
  wheelEvent.modifiers = pinchEvent.modifiers | WebInputEvent::ControlKey;
  wheelEvent.deltaX = 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(pinchEvent.data.pinchUpdate.scale, 0);
  wheelEvent.deltaY = 100.0f * log(pinchEvent.data.pinchUpdate.scale);
  wheelEvent.hasPreciseScrollingDeltas = true;
  wheelEvent.wheelTicksX = 0;
  wheelEvent.wheelTicksY = pinchEvent.data.pinchUpdate.scale > 1 ? 1 : -1;

  return handleInputEvent(wheelEvent);
}

void WebViewImpl::transferActiveWheelFlingAnimation(
    const WebActiveWheelFlingParameters& parameters) {
  TRACE_EVENT0("blink", "WebViewImpl::transferActiveWheelFlingAnimation");
  DCHECK(!m_gestureAnimation);
  m_positionOnFlingStart = parameters.point;
  m_globalPositionOnFlingStart = parameters.globalPoint;
  m_flingModifier = parameters.modifiers;
  std::unique_ptr<WebGestureCurve> curve =
      wrapUnique(Platform::current()->createFlingAnimationCurve(
          parameters.sourceDevice, WebFloatPoint(parameters.delta),
          parameters.cumulativeScroll));
  DCHECK(curve);
  m_gestureAnimation = WebActiveGestureAnimation::createWithTimeOffset(
      std::move(curve), this, parameters.startTime);
  DCHECK_NE(parameters.sourceDevice, WebGestureDeviceUninitialized);
  m_flingSourceDevice = parameters.sourceDevice;
  mainFrameImpl()->frameWidget()->scheduleAnimation();
}

bool WebViewImpl::endActiveFlingAnimation() {
  if (m_gestureAnimation) {
    m_gestureAnimation.reset();
    m_flingSourceDevice = WebGestureDeviceUninitialized;
    if (m_layerTreeView)
      m_layerTreeView->didStopFlinging();
    return true;
  }
  return false;
}

bool WebViewImpl::startPageScaleAnimation(const IntPoint& targetPosition,
                                          bool useAnchor,
                                          float newScale,
                                          double durationInSeconds) {
  VisualViewport& visualViewport = page()->frameHost().visualViewport();
  WebPoint clampedPoint = targetPosition;
  if (!useAnchor) {
    clampedPoint =
        visualViewport.clampDocumentOffsetAtScale(targetPosition, newScale);
    if (!durationInSeconds) {
      setPageScaleFactor(newScale);

      FrameView* view = mainFrameImpl()->frameView();
      if (view && view->getScrollableArea()) {
        view->getScrollableArea()->setScrollOffset(
            ScrollOffset(clampedPoint.x, clampedPoint.y), ProgrammaticScroll);
      }

      return false;
    }
  }
  if (useAnchor && newScale == pageScaleFactor())
    return false;

  if (m_enableFakePageScaleAnimationForTesting) {
    m_fakePageScaleAnimationTargetPosition = targetPosition;
    m_fakePageScaleAnimationUseAnchor = useAnchor;
    m_fakePageScaleAnimationPageScaleFactor = newScale;
  } else {
    if (!m_layerTreeView)
      return false;
    m_layerTreeView->startPageScaleAnimation(targetPosition, useAnchor,
                                             newScale, durationInSeconds);
  }
  return true;
}

void WebViewImpl::enableFakePageScaleAnimationForTesting(bool enable) {
  m_enableFakePageScaleAnimationForTesting = enable;
}

void WebViewImpl::setShowFPSCounter(bool show) {
  if (m_layerTreeView) {
    TRACE_EVENT0("blink", "WebViewImpl::setShowFPSCounter");
    m_layerTreeView->setShowFPSCounter(show);
  }
}

void WebViewImpl::setShowPaintRects(bool show) {
  if (m_layerTreeView) {
    TRACE_EVENT0("blink", "WebViewImpl::setShowPaintRects");
    m_layerTreeView->setShowPaintRects(show);
  }
  setFirstPaintInvalidationTrackingEnabledForShowPaintRects(show);
}

void WebViewImpl::setShowDebugBorders(bool show) {
  if (m_layerTreeView)
    m_layerTreeView->setShowDebugBorders(show);
}

void WebViewImpl::setShowScrollBottleneckRects(bool show) {
  if (m_layerTreeView)
    m_layerTreeView->setShowScrollBottleneckRects(show);
}

void WebViewImpl::acceptLanguagesChanged() {
  if (m_client)
    FontCache::acceptLanguagesChanged(m_client->acceptLanguages());

  if (!page())
    return;

  page()->acceptLanguagesChanged();
}

void WebViewImpl::ReportIntervention(const WebString& message) {
  if (!mainFrameImpl())
    return;
  WebConsoleMessage consoleMessage(WebConsoleMessage::LevelWarning, message);
  mainFrameImpl()->addMessageToConsole(consoleMessage);
}

float WebViewImpl::expensiveBackgroundThrottlingCPUBudget() {
  return settingsImpl()->expensiveBackgroundThrottlingCPUBudget();
}

float WebViewImpl::expensiveBackgroundThrottlingInitialBudget() {
  return settingsImpl()->expensiveBackgroundThrottlingInitialBudget();
}

float WebViewImpl::expensiveBackgroundThrottlingMaxBudget() {
  return settingsImpl()->expensiveBackgroundThrottlingMaxBudget();
}

float WebViewImpl::expensiveBackgroundThrottlingMaxDelay() {
  return settingsImpl()->expensiveBackgroundThrottlingMaxDelay();
}

WebInputEventResult WebViewImpl::handleKeyEvent(const WebKeyboardEvent& event) {
  DCHECK((event.type == WebInputEvent::RawKeyDown) ||
         (event.type == WebInputEvent::KeyDown) ||
         (event.type == WebInputEvent::KeyUp));
  TRACE_EVENT2("input", "WebViewImpl::handleKeyEvent", "type",
               WebInputEvent::GetName(event.type), "text",
               String(event.text).utf8());

  // Halt an in-progress fling on a key event.
  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.
  m_suppressNextKeypressEvent = false;

  // If there is a popup, it should be the one processing the event, not the
  // page.
  if (m_pagePopup) {
    m_pagePopup->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::RawKeyDown == event.type)
      m_suppressNextKeypressEvent = true;
    return WebInputEventResult::HandledSystem;
  }

  Frame* focusedFrame = focusedCoreFrame();
  if (focusedFrame && focusedFrame->isRemoteFrame()) {
    WebRemoteFrameImpl* webFrame =
        WebRemoteFrameImpl::fromFrame(*toRemoteFrame(focusedFrame));
    webFrame->client()->forwardInputEvent(&event);
    return WebInputEventResult::HandledSystem;
  }

  if (!focusedFrame || !focusedFrame->isLocalFrame())
    return WebInputEventResult::NotHandled;

  LocalFrame* frame = toLocalFrame(focusedFrame);

  WebInputEventResult result = frame->eventHandler().keyEvent(event);
  if (result != WebInputEventResult::NotHandled) {
    if (WebInputEvent::RawKeyDown == event.type) {
      // 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->layoutObject() &&
          element->layoutObject()->isEmbeddedObject()) {
        if (event.windowsKeyCode == VKEY_TAB) {
          // If the plugin supports keyboard focus then we should not send a tab
          // keypress event.
          Widget* widget = toLayoutPart(element->layoutObject())->widget();
          if (widget && widget->isPluginContainer()) {
            WebPluginContainerImpl* plugin = toWebPluginContainerImpl(widget);
            if (plugin && plugin->supportsKeyboardFocus())
              m_suppressNextKeypressEvent = true;
          }
        }
      } else {
        m_suppressNextKeypressEvent = true;
      }
    }
    return result;
  }

#if !OS(MACOSX)
  const WebInputEvent::Type contextMenuKeyTriggeringEventType =
#if OS(WIN)
      WebInputEvent::KeyUp;
#else
      WebInputEvent::RawKeyDown;
#endif
  const WebInputEvent::Type shiftF10TriggeringEventType =
      WebInputEvent::RawKeyDown;

  bool isUnmodifiedMenuKey =
      !(event.modifiers & WebInputEvent::InputModifiers) &&
      event.windowsKeyCode == VKEY_APPS;
  bool isShiftF10 = (event.modifiers & WebInputEvent::InputModifiers) ==
                        WebInputEvent::ShiftKey &&
                    event.windowsKeyCode == VKEY_F10;
  if ((isUnmodifiedMenuKey &&
       event.type == contextMenuKeyTriggeringEventType) ||
      (isShiftF10 && event.type == shiftF10TriggeringEventType)) {
    sendContextMenuEvent(event);
    return WebInputEventResult::HandledSystem;
  }
#endif  // !OS(MACOSX)

  return WebInputEventResult::NotHandled;
}

WebInputEventResult WebViewImpl::handleCharEvent(
    const WebKeyboardEvent& event) {
  DCHECK_EQ(event.type, WebInputEvent::Char);
  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 = m_suppressNextKeypressEvent;
  m_suppressNextKeypressEvent = false;

  // If there is a popup, it should be the one processing the event, not the
  // page.
  if (m_pagePopup)
    return m_pagePopup->handleKeyEvent(event);

  LocalFrame* frame = toLocalFrame(focusedCoreFrame());
  if (!frame)
    return suppress ? WebInputEventResult::HandledSuppressed
                    : WebInputEventResult::NotHandled;

  EventHandler& handler = frame->eventHandler();

  if (!event.isCharacterKey())
    return WebInputEventResult::HandledSuppressed;

  // Accesskeys are triggered by char events and can't be suppressed.
  if (handler.handleAccessKey(event))
    return WebInputEventResult::HandledSystem;

  // 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.isSystemKey)
    return WebInputEventResult::NotHandled;

  if (suppress)
    return WebInputEventResult::HandledSuppressed;

  WebInputEventResult result = handler.keyEvent(event);
  if (result != WebInputEventResult::NotHandled)
    return result;

  return WebInputEventResult::NotHandled;
}

WebRect WebViewImpl::computeBlockBound(const WebPoint& pointInRootFrame,
                                       bool ignoreClipping) {
  if (!mainFrameImpl())
    return WebRect();

  // Use the point-based hit test to find the node.
  IntPoint point = mainFrameImpl()->frameView()->rootFrameToContents(
      IntPoint(pointInRootFrame.x, pointInRootFrame.y));
  HitTestRequest::HitTestRequestType hitType =
      HitTestRequest::ReadOnly | HitTestRequest::Active |
      (ignoreClipping ? HitTestRequest::IgnoreClipping : 0);
  HitTestResult result =
      mainFrameImpl()->frame()->eventHandler().hitTestResultAtPoint(point,
                                                                    hitType);
  result.setToShadowHostIfInUserAgentShadowRoot();

  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->layoutObject() || node->layoutObject()->isInline()))
    node = LayoutTreeBuilderTraversal::parent(*node);

  // Return the bounding box in the root frame's coordinate space.
  if (node) {
    IntRect pointInRootFrame = node->Node::pixelSnappedBoundingBox();
    LocalFrame* frame = node->document().frame();
    return frame->view()->contentsToRootFrame(pointInRootFrame);
  }
  return WebRect();
}

WebRect WebViewImpl::widenRectWithinPageBounds(const WebRect& source,
                                               int targetMargin,
                                               int minimumMargin) {
  WebSize maxSize;
  if (mainFrame())
    maxSize = mainFrame()->contentsSize();
  IntSize scrollOffset;
  if (mainFrame())
    scrollOffset = mainFrame()->scrollOffset();
  int leftMargin = targetMargin;
  int rightMargin = targetMargin;

  const int absoluteSourceX = source.x + scrollOffset.width();
  if (leftMargin > absoluteSourceX) {
    leftMargin = absoluteSourceX;
    rightMargin = std::max(leftMargin, minimumMargin);
  }

  const int maximumRightMargin =
      maxSize.width - (source.width + absoluteSourceX);
  if (rightMargin > maximumRightMargin) {
    rightMargin = maximumRightMargin;
    leftMargin = std::min(leftMargin, std::max(rightMargin, minimumMargin));
  }

  const int newWidth = source.width + leftMargin + rightMargin;
  const int newX = source.x - leftMargin;

  DCHECK_GE(newWidth, 0);
  DCHECK_LE(scrollOffset.width() + newX + newWidth, maxSize.width);

  return WebRect(newX, source.y, newWidth, 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 (page())
    return m_maximumLegibleScale *
           page()->settings().accessibilityFontScaleFactor();
  return m_maximumLegibleScale;
}

void WebViewImpl::computeScaleAndScrollForBlockRect(
    const WebPoint& hitPointInRootFrame,
    const WebRect& blockRectInRootFrame,
    float padding,
    float defaultScaleWhenAlreadyLegible,
    float& scale,
    WebPoint& scroll) {
  scale = pageScaleFactor();
  scroll.x = scroll.y = 0;

  WebRect rect = blockRectInRootFrame;

  if (!rect.isEmpty()) {
    float defaultMargin = doubleTapZoomContentDefaultMargin;
    float minimumMargin = 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>(defaultMargin * rect.width / m_size.width),
        static_cast<int>(minimumMargin * rect.width / m_size.width));
    // Fit block to screen, respecting limits.
    scale = static_cast<float>(m_size.width) / rect.width;
    scale = std::min(scale, maximumLegiblePageScale());
    if (pageScaleFactor() < defaultScaleWhenAlreadyLegible)
      scale = std::max(scale, defaultScaleWhenAlreadyLegible);
    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 screenWidth = m_size.width / scale;
  float screenHeight = m_size.height / scale;

  // Scroll to vertically align the block.
  if (rect.height < screenHeight) {
    // Vertically center short blocks.
    rect.y -= 0.5 * (screenHeight - 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, hitPointInRootFrame.y + padding - screenHeight);
  }  // Otherwise top align the block.

  // Do the same thing for horizontal alignment.
  if (rect.width < screenWidth)
    rect.x -= 0.5 * (screenWidth - rect.width);
  else
    rect.x =
        std::max<float>(rect.x, hitPointInRootFrame.x + padding - screenWidth);
  scroll.x = rect.x;
  scroll.y = rect.y;

  scale = clampPageScaleFactorToLimits(scale);
  scroll = mainFrameImpl()->frameView()->rootFrameToContents(scroll);
  scroll = page()->frameHost().visualViewport().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->layoutObject()) {
      ECursor cursor = node->layoutObject()->style()->cursor();
      if (cursor != ECursor::Auto ||
          frame->eventHandler().useHandCursor(node, node->isLink()))
        break;
    }
    node = LayoutTreeBuilderTraversal::parent(*node);
  }

  return node;
}

static bool showsHandCursor(Node* node, LocalFrame* frame) {
  if (!node || !node->layoutObject())
    return false;

  ECursor cursor = node->layoutObject()->style()->cursor();
  return cursor == ECursor::Pointer ||
         (cursor == ECursor::Auto &&
          frame->eventHandler().useHandCursor(node, node->isLink()));
}

Node* WebViewImpl::bestTapNode(
    const GestureEventWithHitTestResults& targetedTapEvent) {
  TRACE_EVENT0("input", "WebViewImpl::bestTapNode");

  if (!m_page || !m_page->mainFrame())
    return nullptr;

  Node* bestTouchNode = targetedTapEvent.hitTestResult().innerNode();
  if (!bestTouchNode)
    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 (!bestTouchNode->layoutObject()) {
    bestTouchNode = LayoutTreeBuilderTraversal::parent(*bestTouchNode);
    if (!bestTouchNode)
      return nullptr;
  }

  // Editable nodes should not be highlighted (e.g., <input>)
  if (hasEditableStyle(*bestTouchNode))
    return nullptr;

  Node* cursorDefiningAncestor = findCursorDefiningAncestor(
      bestTouchNode, m_page->deprecatedLocalMainFrame());
  // We show a highlight on tap only when the current node shows a hand cursor
  if (!cursorDefiningAncestor ||
      !showsHandCursor(cursorDefiningAncestor,
                       m_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 {
    bestTouchNode = cursorDefiningAncestor;
    cursorDefiningAncestor = findCursorDefiningAncestor(
        LayoutTreeBuilderTraversal::parent(*bestTouchNode),
        m_page->deprecatedLocalMainFrame());
  } while (cursorDefiningAncestor &&
           showsHandCursor(cursorDefiningAncestor,
                           m_page->deprecatedLocalMainFrame()));

  return bestTouchNode;
}

void WebViewImpl::enableTapHighlightAtPoint(
    const GestureEventWithHitTestResults& targetedTapEvent) {
  Node* touchNode = bestTapNode(targetedTapEvent);

  HeapVector<Member<Node>> highlightNodes;
  highlightNodes.append(touchNode);

  enableTapHighlights(highlightNodes);
}

void WebViewImpl::enableTapHighlights(
    HeapVector<Member<Node>>& highlightNodes) {
  if (highlightNodes.isEmpty())
    return;

  // Always clear any existing highlight when this is invoked, even if we
  // don't get a new target to highlight.
  m_linkHighlights.clear();

  for (size_t i = 0; i < highlightNodes.size(); ++i) {
    Node* node = highlightNodes[i];

    if (!node || !node->layoutObject())
      continue;

    Color highlightColor = node->layoutObject()->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 (!highlightColor.alpha())
      continue;

    m_linkHighlights.append(LinkHighlightImpl::create(node, this));
  }

  updateAllLifecyclePhases();
}

void WebViewImpl::animateDoubleTapZoom(const IntPoint& pointInRootFrame) {
  if (!mainFrameImpl())
    return;

  WebRect blockBounds = computeBlockBound(pointInRootFrame, false);
  float scale;
  WebPoint scroll;

  computeScaleAndScrollForBlockRect(
      pointInRootFrame, blockBounds, touchPointPadding,
      minimumPageScaleFactor() * doubleTapZoomAlreadyLegibleRatio, scale,
      scroll);

  bool stillAtPreviousDoubleTapScale =
      (pageScaleFactor() == m_doubleTapZoomPageScaleFactor &&
       m_doubleTapZoomPageScaleFactor != minimumPageScaleFactor()) ||
      m_doubleTapZoomPending;

  bool scaleUnchanged = fabs(pageScaleFactor() - scale) < minScaleDifference;
  bool shouldZoomOut =
      blockBounds.isEmpty() || scaleUnchanged || stillAtPreviousDoubleTapScale;

  bool isAnimating;

  if (shouldZoomOut) {
    scale = minimumPageScaleFactor();
    IntPoint targetPosition =
        mainFrameImpl()->frameView()->rootFrameToContents(pointInRootFrame);
    isAnimating = startPageScaleAnimation(
        targetPosition, true, scale, doubleTapZoomAnimationDurationInSeconds);
  } else {
    isAnimating = 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 (isAnimating) {
    m_doubleTapZoomPageScaleFactor = scale;
    m_doubleTapZoomPending = true;
  }
}

void WebViewImpl::zoomToFindInPageRect(const WebRect& rectInRootFrame) {
  if (!mainFrameImpl())
    return;

  WebRect blockBounds = computeBlockBound(
      WebPoint(rectInRootFrame.x + rectInRootFrame.width / 2,
               rectInRootFrame.y + rectInRootFrame.height / 2),
      true);

  if (blockBounds.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(rectInRootFrame.x, rectInRootFrame.y), blockBounds,
      nonUserInitiatedPointPadding, minimumPageScaleFactor(), scale, scroll);

  startPageScaleAnimation(scroll, false, scale,
                          findInPageAnimationDurationInSeconds);
}

bool WebViewImpl::zoomToMultipleTargetsRect(const WebRect& rectInRootFrame) {
  if (!mainFrameImpl())
    return false;

  float scale;
  WebPoint scroll;

  computeScaleAndScrollForBlockRect(
      WebPoint(rectInRootFrame.x, rectInRootFrame.y), rectInRootFrame,
      nonUserInitiatedPointPadding, minimumPageScaleFactor(), scale, scroll);

  if (scale <= pageScaleFactor())
    return false;

  startPageScaleAnimation(scroll, false, scale,
                          multipleTargetsZoomAnimationDurationInSeconds);
  return true;
}

void WebViewImpl::hasTouchEventHandlers(bool hasTouchHandlers) {
  if (m_client)
    m_client->hasTouchEventHandlers(hasTouchHandlers);
}

bool WebViewImpl::hasTouchEventHandlersAt(const WebPoint& point) {
  // FIXME: Implement this. Note that the point must be divided by
  // pageScaleFactor.
  return true;
}

#if !OS(MACOSX)
// Mac has no way to open a context menu based on a keyboard event.
WebInputEventResult WebViewImpl::sendContextMenuEvent(
    const WebKeyboardEvent& event) {
  // 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.
  page()->contextMenuController().clearContextMenu();

  {
    ContextMenuAllowedScope scope;
    Frame* focusedFrame = page()->focusController().focusedOrMainFrame();
    if (!focusedFrame->isLocalFrame())
      return WebInputEventResult::NotHandled;
    // Firefox reveal focus based on "keydown" event but not "contextmenu"
    // event, we match FF.
    if (Element* focusedElement =
            toLocalFrame(focusedFrame)->document()->focusedElement())
      focusedElement->scrollIntoViewIfNeeded();
    return toLocalFrame(focusedFrame)
        ->eventHandler()
        .sendContextMenuEventForKey(nullptr);
  }
}
#endif

void WebViewImpl::showContextMenuAtPoint(float x,
                                         float y,
                                         ContextMenuProvider* menuProvider) {
  if (!page()->mainFrame()->isLocalFrame())
    return;
  {
    ContextMenuAllowedScope scope;
    page()->contextMenuController().clearContextMenu();
    page()->contextMenuController().showContextMenuAtPoint(
        page()->deprecatedLocalMainFrame(), x, y, menuProvider);
  }
}

void WebViewImpl::showContextMenuForElement(WebElement element) {
  if (!page())
    return;

  page()->contextMenuController().clearContextMenu();
  {
    ContextMenuAllowedScope scope;
    if (LocalFrame* focusedFrame =
            toLocalFrame(page()->focusController().focusedOrMainFrame()))
      focusedFrame->eventHandler().sendContextMenuEventForKey(
          element.unwrap<Element>());
  }
}

PagePopup* WebViewImpl::openPagePopup(PagePopupClient* client) {
  DCHECK(client);
  if (hasOpenedPopup())
    hidePopups();
  DCHECK(!m_pagePopup);

  WebWidget* popupWidget = m_client->createPopupMenu(WebPopupTypePage);
  // createPopupMenu returns nullptr if this renderer process is about to die.
  if (!popupWidget)
    return nullptr;
  m_pagePopup = toWebPagePopupImpl(popupWidget);
  if (!m_pagePopup->initialize(this, client)) {
    m_pagePopup->closePopup();
    m_pagePopup = nullptr;
  }
  enablePopupMouseWheelEventListener();
  return m_pagePopup.get();
}

void WebViewImpl::closePagePopup(PagePopup* popup) {
  DCHECK(popup);
  WebPagePopupImpl* popupImpl = toWebPagePopupImpl(popup);
  DCHECK_EQ(m_pagePopup.get(), popupImpl);
  if (m_pagePopup.get() != popupImpl)
    return;
  m_pagePopup->closePopup();
}

void WebViewImpl::cleanupPagePopup() {
  m_pagePopup = nullptr;
  disablePopupMouseWheelEventListener();
}

void WebViewImpl::cancelPagePopup() {
  if (m_pagePopup)
    m_pagePopup->cancel();
}

void WebViewImpl::enablePopupMouseWheelEventListener() {
  // TODO(kenrb): Popup coordination for out-of-process iframes needs to be
  // added. Because of the early return here a select element
  // popup can remain visible even when the element underneath it is
  // scrolled to a new position. This is part of a larger set of issues with
  // popups.
  // See https://crbug.com/566130
  if (!mainFrameImpl())
    return;
  DCHECK(!m_popupMouseWheelEventListener);
  Document* document = mainFrameImpl()->frame()->document();
  DCHECK(document);
  // We register an empty event listener, EmptyEventListener, so that mouse
  // wheel events get sent to the WebView.
  m_popupMouseWheelEventListener = EmptyEventListener::create();
  document->addEventListener(EventTypeNames::mousewheel,
                             m_popupMouseWheelEventListener, false);
}

void WebViewImpl::disablePopupMouseWheelEventListener() {
  // TODO(kenrb): Concerns the same as in enablePopupMouseWheelEventListener.
  // See https://crbug.com/566130
  if (!mainFrameImpl())
    return;
  DCHECK(m_popupMouseWheelEventListener);
  Document* document = mainFrameImpl()->frame()->document();
  DCHECK(document);
  // Document may have already removed the event listener, for instance, due
  // to a navigation, but remove it anyway.
  document->removeEventListener(EventTypeNames::mousewheel,
                                m_popupMouseWheelEventListener.release(),
                                false);
}

LocalDOMWindow* WebViewImpl::pagePopupWindow() const {
  return m_pagePopup ? m_pagePopup->window() : nullptr;
}

Frame* WebViewImpl::focusedCoreFrame() const {
  return m_page ? m_page->focusController().focusedOrMainFrame() : nullptr;
}

WebViewImpl* WebViewImpl::fromPage(Page* page) {
  return page ? static_cast<WebViewImpl*>(page->chromeClient().webView())
              : nullptr;
}

// WebWidget ------------------------------------------------------------------

void WebViewImpl::close() {
  WebDevToolsAgentImpl::webViewImplClosed(this);
  DCHECK(allInstances().contains(this));
  allInstances().remove(this);

  if (m_page) {
    // Initiate shutdown for the entire frameset.  This will cause a lot of
    // notifications to be sent.
    m_page->willBeDestroyed();
    m_page.clear();
  }

  // Reset the delegate to prevent notifications being sent as we're being
  // deleted.
  m_client = nullptr;

  deref();  // Balances ref() acquired in WebView::create
}

WebSize WebViewImpl::size() {
  return m_size;
}

void WebViewImpl::resizeVisualViewport(const WebSize& newSize) {
  page()->frameHost().visualViewport().setSize(newSize);
  page()->frameHost().visualViewport().clampToBoundaries();
}

void WebViewImpl::performResize() {
  // 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 ICBSize = m_size;
  if (RuntimeEnabledFeatures::inertTopControlsEnabled() &&
      browserControls().permittedState() == WebBrowserControlsBoth &&
      !browserControls().shrinkViewport())
    ICBSize.expand(0, -browserControls().height());

  pageScaleConstraintsSet().didChangeInitialContainingBlockSize(ICBSize);

  updatePageDefinedViewportConstraints(
      mainFrameImpl()->frame()->document()->viewportDescription());
  updateMainFrameLayoutSize();

  page()->frameHost().visualViewport().setSize(m_size);

  if (mainFrameImpl()->frameView()) {
    mainFrameImpl()->frameView()->setInitialViewportSize(ICBSize);
    if (!mainFrameImpl()->frameView()->needsLayout())
      postLayoutResize(mainFrameImpl());
  }
}

void WebViewImpl::updateBrowserControlsState(WebBrowserControlsState constraint,
                                             WebBrowserControlsState current,
                                             bool animate) {
  WebBrowserControlsState oldPermittedState =
      browserControls().permittedState();

  browserControls().updateConstraintsAndState(constraint, current, animate);

  // If the controls are going from a locked to an unlocked state, or
  // vice-versa, then we need to force a recompute of the ICB size since that
  // depends on the permitted browser controls state.
  if (oldPermittedState != constraint &&
      (oldPermittedState == WebBrowserControlsBoth ||
       constraint == WebBrowserControlsBoth)) {
    performResize();
  }

  if (m_layerTreeView)
    m_layerTreeView->updateBrowserControlsState(constraint, current, animate);
}

void WebViewImpl::didUpdateBrowserControls() {
  if (m_layerTreeView) {
    m_layerTreeView->setBrowserControlsShownRatio(
        browserControls().shownRatio());
    m_layerTreeView->setBrowserControlsHeight(
        browserControls().height(), browserControls().shrinkViewport());
  }

  WebLocalFrameImpl* mainFrame = mainFrameImpl();
  if (!mainFrame)
    return;

  FrameView* view = mainFrame->frameView();
  if (!view)
    return;

  VisualViewport& visualViewport = page()->frameHost().visualViewport();

  {
    // 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 resizeScope(*m_resizeViewportAnchor);

    float browserControlsViewportAdjustment =
        browserControls().layoutHeight() - browserControls().contentOffset();
    visualViewport.setBrowserControlsAdjustment(
        browserControlsViewportAdjustment);

    // Since the FrameView is sized to be the visual viewport at minimum
    // scale, its adjustment must also be scaled by the minimum scale.
    view->setBrowserControlsViewportAdjustment(
        browserControlsViewportAdjustment / minimumPageScaleFactor());
  }
}

BrowserControls& WebViewImpl::browserControls() {
  return page()->frameHost().browserControls();
}

void WebViewImpl::resizeViewWhileAnchored(float browserControlsHeight,
                                          bool browserControlsShrinkLayout) {
  DCHECK(mainFrameImpl());

  browserControls().setHeight(browserControlsHeight,
                              browserControlsShrinkLayout);

  {
    // Avoids unnecessary invalidations while various bits of state in
    // TextAutosizer are updated.
    TextAutosizer::DeferUpdatePageInfo deferUpdatePageInfo(page());
    performResize();
  }

  m_fullscreenController->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& newSize,
                                            float browserControlsHeight,
                                            bool browserControlsShrinkLayout) {
  if (m_shouldAutoResize)
    return;

  if (m_size == newSize &&
      browserControls().height() == browserControlsHeight &&
      browserControls().shrinkViewport() == browserControlsShrinkLayout)
    return;

  if (page()->mainFrame() && !page()->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.
    m_size = newSize;
    pageScaleConstraintsSet().didChangeInitialContainingBlockSize(m_size);
    page()->frameHost().visualViewport().setSize(m_size);
    return;
  }

  WebLocalFrameImpl* mainFrame = mainFrameImpl();
  if (!mainFrame)
    return;

  FrameView* view = mainFrame->frameView();
  if (!view)
    return;

  VisualViewport& visualViewport = page()->frameHost().visualViewport();

  bool isRotation =
      page()->settings().mainFrameResizesAreOrientationChanges() &&
      m_size.width && contentsSize().width() && newSize.width != m_size.width &&
      !m_fullscreenController->isFullscreen();
  m_size = newSize;

  FloatSize viewportAnchorCoords(viewportAnchorCoordX, viewportAnchorCoordY);
  if (isRotation) {
    RotationViewportAnchor anchor(*view, visualViewport, viewportAnchorCoords,
                                  pageScaleConstraintsSet());
    resizeViewWhileAnchored(browserControlsHeight, browserControlsShrinkLayout);
  } else {
    ResizeViewportAnchor::ResizeScope resizeScope(*m_resizeViewportAnchor);
    resizeViewWhileAnchored(browserControlsHeight, browserControlsShrinkLayout);
  }
  sendResizeEventAndRepaint();
}

void WebViewImpl::resize(const WebSize& newSize) {
  if (m_shouldAutoResize || m_size == newSize)
    return;

  resizeWithBrowserControls(newSize, browserControls().height(),
                            browserControls().shrinkViewport());
}

void WebViewImpl::didEnterFullscreen() {
  m_fullscreenController->didEnterFullscreen();
}

void WebViewImpl::didExitFullscreen() {
  m_fullscreenController->didExitFullscreen();
}

void WebViewImpl::didUpdateFullscreenSize() {
  m_fullscreenController->updateSize();
}

void WebViewImpl::beginFrame(double lastFrameTimeMonotonic) {
  TRACE_EVENT1("blink", "WebViewImpl::beginFrame", "frameTime",
               lastFrameTimeMonotonic);
  DCHECK(lastFrameTimeMonotonic);

  // Create synthetic wheel events as necessary for fling.
  if (m_gestureAnimation) {
    if (m_gestureAnimation->animate(lastFrameTimeMonotonic))
      mainFrameImpl()->frameWidget()->scheduleAnimation();
    else {
      DCHECK_NE(m_flingSourceDevice, WebGestureDeviceUninitialized);
      WebGestureDevice lastFlingSourceDevice = m_flingSourceDevice;
      endActiveFlingAnimation();

      PlatformGestureEvent endScrollEvent(
          PlatformEvent::GestureScrollEnd, m_positionOnFlingStart,
          m_globalPositionOnFlingStart, IntSize(), 0,
          PlatformEvent::NoModifiers,
          lastFlingSourceDevice == WebGestureDeviceTouchpad
              ? PlatformGestureSourceTouchpad
              : PlatformGestureSourceTouchscreen);
      endScrollEvent.setScrollGestureData(0, 0, ScrollByPrecisePixel, 0, 0,
                                          ScrollInertialPhaseMomentum, false,
                                          -1 /* null plugin id */);

      mainFrameImpl()->frame()->eventHandler().handleGestureScrollEnd(
          endScrollEvent);
    }
  }

  if (!mainFrameImpl())
    return;

  m_lastFrameTimeMonotonic = lastFrameTimeMonotonic;

  DocumentLifecycle::AllowThrottlingScope throttlingScope(
      mainFrameImpl()->frame()->document()->lifecycle());
  PageWidgetDelegate::animate(*m_page, lastFrameTimeMonotonic);
}

void WebViewImpl::updateAllLifecyclePhases() {
  TRACE_EVENT0("blink", "WebViewImpl::updateAllLifecyclePhases");
  if (!mainFrameImpl())
    return;

  DocumentLifecycle::AllowThrottlingScope throttlingScope(
      mainFrameImpl()->frame()->document()->lifecycle());
  updateLayerTreeBackgroundColor();

  PageWidgetDelegate::updateAllLifecyclePhases(*m_page,
                                               *mainFrameImpl()->frame());

  if (InspectorOverlay* overlay = inspectorOverlay()) {
    overlay->updateAllLifecyclePhases();
    // TODO(chrishtr): integrate paint into the overlay's lifecycle.
    if (overlay->pageOverlay() && overlay->pageOverlay()->graphicsLayer())
      overlay->pageOverlay()->graphicsLayer()->paint(nullptr);
  }
  if (m_pageColorOverlay)
    m_pageColorOverlay->graphicsLayer()->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 < m_linkHighlights.size(); ++i)
    m_linkHighlights[i]->updateGeometry();

  if (FrameView* view = mainFrameImpl()->frameView()) {
    LocalFrame* frame = mainFrameImpl()->frame();
    WebWidgetClient* client =
        WebLocalFrameImpl::fromFrame(frame)->frameWidget()->client();

    if (m_shouldDispatchFirstVisuallyNonEmptyLayout &&
        view->isVisuallyNonEmpty()) {
      m_shouldDispatchFirstVisuallyNonEmptyLayout = false;
      // TODO(esprehn): Move users of this callback to something
      // better, the heuristic for "visually non-empty" is bad.
      client->didMeaningfulLayout(WebMeaningfulLayout::VisuallyNonEmpty);
    }

    if (m_shouldDispatchFirstLayoutAfterFinishedParsing &&
        frame->document()->hasFinishedParsing()) {
      m_shouldDispatchFirstLayoutAfterFinishedParsing = false;
      client->didMeaningfulLayout(WebMeaningfulLayout::FinishedParsing);
    }

    if (m_shouldDispatchFirstLayoutAfterFinishedLoading &&
        frame->document()->isLoadCompleted()) {
      m_shouldDispatchFirstLayoutAfterFinishedLoading = false;
      client->didMeaningfulLayout(WebMeaningfulLayout::FinishedLoading);
    }
  }
}

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());

  double paintStart = currentTime();
  PageWidgetDelegate::paint(*m_page, canvas, rect,
                            *m_page->deprecatedLocalMainFrame());
  double paintEnd = currentTime();
  double pixelsPerSec = (rect.width * rect.height) / (paintEnd - paintStart);
  DEFINE_STATIC_LOCAL(CustomCountHistogram, softwarePaintDurationHistogram,
                      ("Renderer4.SoftwarePaintDurationMS", 0, 120, 30));
  softwarePaintDurationHistogram.count((paintEnd - paintStart) * 1000);
  DEFINE_STATIC_LOCAL(CustomCountHistogram, softwarePaintRateHistogram,
                      ("Renderer4.SoftwarePaintMegapixPerSecond", 10, 210, 30));
  softwarePaintRateHistogram.count(pixelsPerSec / 1000000);
}

#if 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(
      *m_page, canvas, rect, *m_page->deprecatedLocalMainFrame());
}
#endif

void WebViewImpl::layoutAndPaintAsync(
    WebLayoutAndPaintAsyncCallback* callback) {
  m_layerTreeView->layoutAndPaintAsync(callback);
}

void WebViewImpl::compositeAndReadbackAsync(
    WebCompositeAndReadbackAsyncCallback* callback) {
  m_layerTreeView->compositeAndReadbackAsync(callback);
}

void WebViewImpl::themeChanged() {
  if (!page())
    return;
  if (!page()->mainFrame()->isLocalFrame())
    return;
  FrameView* view = page()->deprecatedLocalMainFrame()->view();

  WebRect damagedRect(0, 0, m_size.width, m_size.height);
  view->invalidateRect(damagedRect);
}

void WebViewImpl::enterFullscreenForElement(Element* element) {
  m_fullscreenController->enterFullscreenForElement(element);
}

void WebViewImpl::exitFullscreen(LocalFrame* frame) {
  m_fullscreenController->exitFullscreen(frame);
}

bool WebViewImpl::hasHorizontalScrollbar() {
  return mainFrameImpl()
      ->frameView()
      ->layoutViewportScrollableArea()
      ->horizontalScrollbar();
}

bool WebViewImpl::hasVerticalScrollbar() {
  return mainFrameImpl()
      ->frameView()
      ->layoutViewportScrollableArea()
      ->verticalScrollbar();
}

const WebInputEvent* WebViewImpl::m_currentInputEvent = nullptr;

WebInputEventResult WebViewImpl::handleInputEvent(
    const WebInputEvent& inputEvent) {
  // 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::NotHandled;

  WebAutofillClient* autofillClient = mainFrameImpl()->autofillClient();
  UserGestureNotifier notifier(this);
  // On the first input event since page load, |notifier| instructs the
  // autofill client to unblock values of password input fields of any forms
  // on the page. There is a single input event, GestureTap, which can both
  // be the first event after page load, and cause a form submission. In that
  // case, the form submission happens before the autofill client is told
  // to unblock the password values, and so the password values are not
  // submitted. To avoid that, GestureTap is handled explicitly:
  if (inputEvent.type == WebInputEvent::GestureTap && autofillClient) {
    m_userGestureObserved = true;
    autofillClient->firstUserGestureObserved();
  }

  page()->frameHost().visualViewport().startTrackingPinchStats();

  TRACE_EVENT1("input,rail", "WebViewImpl::handleInputEvent", "type",
               WebInputEvent::GetName(inputEvent.type));

  // If a drag-and-drop operation is in progress, ignore input events.
  if (mainFrameImpl()->frameWidget()->doingDragAndDrop())
    return WebInputEventResult::HandledSuppressed;

  if (m_devToolsEmulator->handleInputEvent(inputEvent))
    return WebInputEventResult::HandledSuppressed;

  if (InspectorOverlay* overlay = inspectorOverlay()) {
    if (overlay->handleInputEvent(inputEvent))
      return WebInputEventResult::HandledSuppressed;
  }

  // Report the event to be NOT processed by WebKit, so that the browser can
  // handle it appropriately.
  if (m_ignoreInputEvents)
    return WebInputEventResult::NotHandled;

  AutoReset<const WebInputEvent*> currentEventChange(&m_currentInputEvent,
                                                     &inputEvent);
  UIEventWithKeyState::clearNewTabModifierSetFromIsolatedWorld();

  bool isPointerLocked = false;
  if (WebFrameWidgetBase* widget = mainFrameImpl()->frameWidget()) {
    if (WebWidgetClient* client = widget->client())
      isPointerLocked = client->isPointerLocked();
  }

  if (isPointerLocked && WebInputEvent::isMouseEventType(inputEvent.type)) {
    pointerLockMouseEvent(inputEvent);
    return WebInputEventResult::HandledSystem;
  }

  if (m_mouseCaptureNode && WebInputEvent::isMouseEventType(inputEvent.type)) {
    TRACE_EVENT1("input", "captured mouse event", "type", inputEvent.type);
    // Save m_mouseCaptureNode since mouseCaptureLost() will clear it.
    Node* node = m_mouseCaptureNode;

    // Not all platforms call mouseCaptureLost() directly.
    if (inputEvent.type == WebInputEvent::MouseUp)
      mouseCaptureLost();

    std::unique_ptr<UserGestureIndicator> gestureIndicator;

    AtomicString eventType;
    switch (inputEvent.type) {
      case WebInputEvent::MouseMove:
        eventType = EventTypeNames::mousemove;
        break;
      case WebInputEvent::MouseLeave:
        eventType = EventTypeNames::mouseout;
        break;
      case WebInputEvent::MouseDown:
        eventType = EventTypeNames::mousedown;
        gestureIndicator = wrapUnique(
            new UserGestureIndicator(DocumentUserGestureToken::create(
                &node->document(), UserGestureToken::NewGesture)));
        m_mouseCaptureGestureToken = gestureIndicator->currentToken();
        break;
      case WebInputEvent::MouseUp:
        eventType = EventTypeNames::mouseup;
        gestureIndicator = wrapUnique(
            new UserGestureIndicator(m_mouseCaptureGestureToken.release()));
        break;
      default:
        NOTREACHED();
    }

    node->dispatchMouseEvent(
        PlatformMouseEventBuilder(
            mainFrameImpl()->frameView(),
            static_cast<const WebMouseEvent&>(inputEvent)),
        eventType, static_cast<const WebMouseEvent&>(inputEvent).clickCount);
    return WebInputEventResult::HandledSystem;
  }

  // FIXME: This should take in the intended frame, not the local frame root.
  WebInputEventResult result = PageWidgetDelegate::handleInputEvent(
      *this, inputEvent, mainFrameImpl()->frame());
  if (result != WebInputEventResult::NotHandled)
    return result;

  // Unhandled pinch events should adjust the scale.
  if (inputEvent.type == WebInputEvent::GesturePinchUpdate) {
    const WebGestureEvent& pinchEvent =
        static_cast<const WebGestureEvent&>(inputEvent);

    // 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 (pinchEvent.sourceDevice == WebGestureDeviceTouchpad) {
      result = handleSyntheticWheelFromTouchpadPinchEvent(pinchEvent);
      if (result != WebInputEventResult::NotHandled)
        return result;
    }

    if (pinchEvent.data.pinchUpdate.zoomDisabled)
      return WebInputEventResult::NotHandled;

    if (page()->frameHost().visualViewport().magnifyScaleAroundAnchor(
            pinchEvent.data.pinchUpdate.scale,
            FloatPoint(pinchEvent.x, pinchEvent.y)))
      return WebInputEventResult::HandledSystem;
  }

  return WebInputEventResult::NotHandled;
}

void WebViewImpl::setCursorVisibilityState(bool isVisible) {
  if (m_page)
    m_page->setIsCursorVisible(isVisible);
}

void WebViewImpl::mouseCaptureLost() {
  TRACE_EVENT_ASYNC_END0("input", "capturing mouse", this);
  m_mouseCaptureNode = nullptr;
}

void WebViewImpl::setFocus(bool enable) {
  m_page->focusController().setFocused(enable);
  if (enable) {
    m_page->focusController().setActive(true);
    LocalFrame* focusedFrame = m_page->focusController().focusedFrame();
    if (focusedFrame) {
      Element* element = focusedFrame->document()->focusedElement();
      if (element && focusedFrame->selection().selection().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.
        focusedFrame->document()->updateStyleAndLayoutTree();
        if (element->isTextControl()) {
          element->updateFocusAppearance(SelectionBehaviorOnFocus::Restore);
        } 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);
          focusedFrame->selection().setSelection(
              SelectionInDOMTree::Builder().collapse(position).build());
        }
      }
    }
    m_imeAcceptEvents = true;
  } else {
    hidePopups();

    // Clear focus on the currently focused frame if any.
    if (!m_page)
      return;

    LocalFrame* frame =
        m_page->mainFrame() && m_page->mainFrame()->isLocalFrame()
            ? m_page->deprecatedLocalMainFrame()
            : nullptr;
    if (!frame)
      return;

    LocalFrame* focusedFrame = focusedLocalFrameInWidget();
    if (focusedFrame) {
      // Finish an ongoing composition to delete the composition node.
      if (focusedFrame->inputMethodController().hasComposition()) {
        WebAutofillClient* autofillClient =
            WebLocalFrameImpl::fromFrame(focusedFrame)->autofillClient();

        if (autofillClient)
          autofillClient->setIgnoreTextChanges(true);

        // TODO(xiaochengh): The use of
        // updateStyleAndLayoutIgnorePendingStylesheets
        // needs to be audited.  See http://crbug.com/590369 for more details.
        focusedFrame->document()
            ->updateStyleAndLayoutIgnorePendingStylesheets();

        focusedFrame->inputMethodController().finishComposingText(
            InputMethodController::KeepSelection);

        if (autofillClient)
          autofillClient->setIgnoreTextChanges(false);
      }
      m_imeAcceptEvents = false;
    }
  }
}

// TODO(ekaramad):This method is almost duplicated in WebFrameWidgetImpl as
// well. This code needs to be refactored  (http://crbug.com/629721).
WebRange WebViewImpl::compositionRange() {
  LocalFrame* focused = focusedLocalFrameAvailableForIme();
  if (!focused)
    return WebRange();

  const EphemeralRange range =
      focused->inputMethodController().compositionEphemeralRange();
  if (range.isNull())
    return WebRange();

  Element* editable =
      focused->selection().rootEditableElementOrDocumentElement();
  DCHECK(editable);

  // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
  // needs to be audited.  See http://crbug.com/590369 for more details.
  editable->document().updateStyleAndLayoutIgnorePendingStylesheets();

  return PlainTextRange::create(*editable, range);
}

WebTextInputInfo WebViewImpl::textInputInfo() {
  LocalFrame* focused = focusedLocalFrameInWidget();
  if (!focused)
    return WebTextInputInfo();
  return focused->inputMethodController().textInputInfo();
}

WebTextInputType WebViewImpl::textInputType() {
  LocalFrame* focused = focusedLocalFrameInWidget();
  if (!focused)
    return WebTextInputTypeNone;
  return focused->inputMethodController().textInputType();
}

// TODO(ekaramad):This method is almost duplicated in WebFrameWidgetImpl as
// well. This code needs to be refactored  (http://crbug.com/629721).
bool WebViewImpl::selectionBounds(WebRect& anchor, WebRect& focus) const {
  const Frame* frame = focusedCoreFrame();
  if (!frame || !frame->isLocalFrame())
    return false;

  const LocalFrame* localFrame = toLocalFrame(frame);
  if (!localFrame)
    return false;
  FrameSelection& selection = localFrame->selection();
  if (!selection.isAvailable() || selection.isNone()) {
    // plugins/mouse-capture-inside-shadow.html reaches here.
    return false;
  }

  // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
  // needs to be audited.  See http://crbug.com/590369 for more details.
  localFrame->document()->updateStyleAndLayoutIgnorePendingStylesheets();

  DocumentLifecycle::DisallowTransitionScope disallowTransition(
      localFrame->document()->lifecycle());

  if (selection.isCaret()) {
    anchor = focus = selection.absoluteCaretBounds();
  } else {
    const EphemeralRange selectedRange =
        selection.selection().toNormalizedEphemeralRange();
    if (selectedRange.isNull())
      return false;
    anchor = localFrame->editor().firstRectForRange(
        EphemeralRange(selectedRange.startPosition()));
    focus = localFrame->editor().firstRectForRange(
        EphemeralRange(selectedRange.endPosition()));
  }

  anchor = localFrame->view()->contentsToViewport(anchor);
  focus = localFrame->view()->contentsToViewport(focus);

  if (!selection.selection().isBaseFirst())
    std::swap(anchor, focus);
  return true;
}

// TODO(ekaramad):This method is almost duplicated in WebFrameWidgetImpl as
// well. This code needs to be refactored  (http://crbug.com/629721).
WebPlugin* WebViewImpl::focusedPluginIfInputMethodSupported(LocalFrame* frame) {
  WebPluginContainerImpl* container =
      WebLocalFrameImpl::currentPluginContainer(frame);
  if (container && container->supportsInputMethod())
    return container->plugin();
  return nullptr;
}

// TODO(ekaramad):This method is almost duplicated in WebFrameWidgetImpl as
// well. This code needs to be refactored  (http://crbug.com/629721).
bool WebViewImpl::selectionTextDirection(WebTextDirection& start,
                                         WebTextDirection& end) const {
  const LocalFrame* frame = focusedLocalFrameInWidget();
  if (!frame)
    return false;

  const FrameSelection& selection = frame->selection();
  if (!selection.isAvailable()) {
    // plugins/mouse-capture-inside-shadow.html reaches here.
    return false;
  }

  // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
  // needs to be audited.  See http://crbug.com/590369 for more details.
  frame->document()->updateStyleAndLayoutIgnorePendingStylesheets();

  if (selection.selection().toNormalizedEphemeralRange().isNull())
    return false;
  start =
      toWebTextDirection(primaryDirectionOf(*selection.start().anchorNode()));
  end = toWebTextDirection(primaryDirectionOf(*selection.end().anchorNode()));
  return true;
}

// TODO(ekaramad):This method is almost duplicated in WebFrameWidgetImpl as
// well. This code needs to be refactored  (http://crbug.com/629721).
bool WebViewImpl::isSelectionAnchorFirst() const {
  const LocalFrame* frame = focusedLocalFrameInWidget();
  if (!frame)
    return false;

  FrameSelection& selection = frame->selection();
  if (!selection.isAvailable()) {
    // plugins/mouse-capture-inside-shadow.html reaches here.
    return false;
  }
  return selection.selection().isBaseFirst();
}

WebColor WebViewImpl::backgroundColor() const {
  if (isTransparent())
    return Color::transparent;
  if (!m_page)
    return m_baseBackgroundColor;
  if (!m_page->mainFrame())
    return m_baseBackgroundColor;
  if (!m_page->mainFrame()->isLocalFrame())
    return m_baseBackgroundColor;
  FrameView* view = m_page->deprecatedLocalMainFrame()->view();
  return view->documentBackgroundColor().rgb();
}

WebPagePopup* WebViewImpl::pagePopup() const {
  return m_pagePopup.get();
}

// TODO(ekaramad):This method is almost duplicated in WebFrameWidgetImpl as
// well. This code needs to be refactored  (http://crbug.com/629721).
WebRange WebViewImpl::caretOrSelectionRange() {
  const LocalFrame* focused = focusedLocalFrameInWidget();
  if (!focused)
    return WebRange();

  // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
  // needs to be audited.  See http://crbug.com/590369 for more details.
  focused->document()->updateStyleAndLayoutIgnorePendingStylesheets();

  return focused->inputMethodController().getSelectionOffsets();
}

// TODO(ekaramad):This method is almost duplicated in WebFrameWidgetImpl as
// well. This code needs to be refactored  (http://crbug.com/629721).
void WebViewImpl::setTextDirection(WebTextDirection direction) {
  // The Editor::setBaseWritingDirection() function checks if we can change
  // the text direction of the selected node and updates its DOM "dir"
  // attribute and its CSS "direction" property.
  // So, we just call the function as Safari does.
  const LocalFrame* focused = focusedLocalFrameInWidget();
  if (!focused)
    return;

  Editor& editor = focused->editor();
  if (!editor.canEdit())
    return;

  switch (direction) {
    case WebTextDirectionDefault:
      editor.setBaseWritingDirection(NaturalWritingDirection);
      break;

    case WebTextDirectionLeftToRight:
      editor.setBaseWritingDirection(LeftToRightWritingDirection);
      break;

    case WebTextDirectionRightToLeft:
      editor.setBaseWritingDirection(RightToLeftWritingDirection);
      break;

    default:
      NOTIMPLEMENTED();
      break;
  }
}

bool WebViewImpl::isAcceleratedCompositingActive() const {
  return m_rootLayer;
}

void WebViewImpl::willCloseLayerTreeView() {
  if (m_linkHighlightsTimeline) {
    m_linkHighlights.clear();
    detachCompositorAnimationTimeline(m_linkHighlightsTimeline.get());
    m_linkHighlightsTimeline.reset();
  }

  if (m_layerTreeView)
    page()->willCloseLayerTreeView(*m_layerTreeView);

  setRootLayer(nullptr);

  m_mutator = nullptr;
  m_layerTreeView = nullptr;
}

void WebViewImpl::didAcquirePointerLock() {
  if (page())
    page()->pointerLockController().didAcquirePointerLock();
}

void WebViewImpl::didNotAcquirePointerLock() {
  if (page())
    page()->pointerLockController().didNotAcquirePointerLock();
}

void WebViewImpl::didLosePointerLock() {
  m_pointerLockGestureToken.clear();
  if (page())
    page()->pointerLockController().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) {
  WebRange range = 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 characterCount = range.length();
  size_t offset = range.startOffset();
  WebVector<WebRect> result(characterCount);
  WebRect webrect;
  for (size_t i = 0; i < characterCount; ++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;
}

// TODO(ekaramad):This method is almost duplicated in WebFrameWidgetImpl as
// well. This code needs to be refactored  (http://crbug.com/629721).
void WebViewImpl::applyReplacementRange(const WebRange& range) {
  if (WebLocalFrame* frame = focusedFrame())
    frame->selectRange(range);
}

// WebView --------------------------------------------------------------------

WebSettingsImpl* WebViewImpl::settingsImpl() {
  if (!m_webSettings)
    m_webSettings = wrapUnique(
        new WebSettingsImpl(&m_page->settings(), m_devToolsEmulator.get()));
  DCHECK(m_webSettings);
  return m_webSettings.get();
}

WebSettings* WebViewImpl::settings() {
  return settingsImpl();
}

WebString WebViewImpl::pageEncoding() const {
  if (!m_page)
    return WebString();

  if (!m_page->mainFrame()->isLocalFrame())
    return WebString();

  // FIXME: Is this check needed?
  if (!m_page->deprecatedLocalMainFrame()->document()->loader())
    return WebString();

  return m_page->deprecatedLocalMainFrame()->document()->encodingName();
}

WebFrame* WebViewImpl::mainFrame() {
  return WebFrame::fromFrame(m_page ? m_page->mainFrame() : nullptr);
}

WebFrame* WebViewImpl::findFrameByName(const WebString& name,
                                       WebFrame* relativeToFrame) {
  // FIXME: Either this should only deal with WebLocalFrames or it should move
  // to WebFrame.
  if (!relativeToFrame)
    relativeToFrame = mainFrame();
  Frame* frame = toWebLocalFrameImpl(relativeToFrame)->frame();
  frame = frame->tree().find(name);
  if (!frame || !frame->isLocalFrame())
    return nullptr;
  return WebLocalFrameImpl::fromFrame(toLocalFrame(frame));
}

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* focusedFrame = focusedCoreFrame();
    if (focusedFrame && focusedFrame->isLocalFrame())
      toLocalFrame(focusedFrame)->selection().setFocused(false);
    return;
  }
  LocalFrame* coreFrame = toWebLocalFrameImpl(frame)->frame();
  coreFrame->page()->focusController().setFocusedFrame(coreFrame);
}

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.
  page()->focusController().focusDocumentView(frame->toImplBase()->frame(),
                                              false /* notifyEmbedder */);
}

void WebViewImpl::setInitialFocus(bool reverse) {
  if (!m_page)
    return;
  Frame* frame = page()->focusController().focusedOrMainFrame();
  if (frame->isLocalFrame()) {
    if (Document* document = toLocalFrame(frame)->document())
      document->clearFocusedElement();
  }
  page()->focusController().setInitialFocus(reverse ? WebFocusTypeBackward
                                                    : WebFocusTypeForward);
}

void WebViewImpl::clearFocusedElement() {
  Frame* frame = focusedCoreFrame();
  if (!frame || !frame->isLocalFrame())
    return;

  LocalFrame* localFrame = toLocalFrame(frame);

  Document* document = localFrame->document();
  if (!document)
    return;

  Element* oldFocusedElement = document->focusedElement();
  document->clearFocusedElement();
  if (!oldFocusedElement)
    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(*oldFocusedElement) ||
      oldFocusedElement->isTextControl())
    localFrame->selection().clear();
}

// TODO(dglazkov): Remove and replace with Node:hasEditableStyle.
// http://crbug.com/612560
static bool isElementEditable(const Element* element) {
  element->document().updateStyleAndLayoutTree();
  if (hasEditableStyle(*element))
    return true;

  if (element->isTextControl()) {
    if (!toTextControlElement(element)->isDisabledOrReadOnly())
      return true;
  }

  return equalIgnoringASCIICase(element->getAttribute(HTMLNames::roleAttr),
                                "textbox");
}

bool WebViewImpl::scrollFocusedEditableElementIntoRect(
    const WebRect& rectInViewport) {
  LocalFrame* frame = page()->mainFrame() && page()->mainFrame()->isLocalFrame()
                          ? page()->deprecatedLocalMainFrame()
                          : nullptr;
  Element* element = focusedElement();
  if (!frame || !frame->view() || !element)
    return false;

  if (!isElementEditable(element))
    return false;

  element->document().updateStyleAndLayoutIgnorePendingStylesheets();

  bool zoomInToLegibleScale =
      m_webSettings->autoZoomFocusedNodeToLegibleScale() &&
      !page()->frameHost().visualViewport().shouldDisableDesktopWorkarounds();

  if (zoomInToLegibleScale) {
    // 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 & TouchActionPinchZoom))
      zoomInToLegibleScale = false;
  }

  float scale;
  IntPoint scroll;
  bool needAnimation;
  computeScaleAndScrollForFocusedNode(element, zoomInToLegibleScale, scale,
                                      scroll, needAnimation);
  if (needAnimation)
    startPageScaleAnimation(scroll, false, scale,
                            scrollAndScaleAnimationDurationInSeconds);

  return true;
}

void WebViewImpl::smoothScroll(int targetX, int targetY, long durationMs) {
  IntPoint targetPosition(targetX, targetY);
  startPageScaleAnimation(targetPosition, false, pageScaleFactor(),
                          (double)durationMs / 1000);
}

void WebViewImpl::computeScaleAndScrollForFocusedNode(Node* focusedNode,
                                                      bool zoomInToLegibleScale,
                                                      float& newScale,
                                                      IntPoint& newScroll,
                                                      bool& needAnimation) {
  VisualViewport& visualViewport = page()->frameHost().visualViewport();

  WebRect caretInViewport, unusedEnd;
  selectionBounds(caretInViewport, unusedEnd);

  // 'caretInDocument' is rect encompassing the blinking cursor relative to the
  // root document.
  IntRect caretInDocument = mainFrameImpl()->frameView()->frameToContents(
      visualViewport.viewportToRootFrame(caretInViewport));
  IntRect textboxRectInDocument = mainFrameImpl()->frameView()->frameToContents(
      focusedNode->document().view()->contentsToRootFrame(
          pixelSnappedIntRect(focusedNode->Node::boundingBox())));

  if (!zoomInToLegibleScale) {
    newScale = 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 minReadableCaretHeightForNode =
        textboxRectInDocument.height() >= 2 * caretInDocument.height()
            ? minReadableCaretHeightForTextArea
            : minReadableCaretHeight;
    newScale = clampPageScaleFactorToLimits(maximumLegiblePageScale() *
                                            minReadableCaretHeightForNode /
                                            caretInDocument.height());
    newScale = std::max(newScale, pageScaleFactor());
  }
  const float deltaScale = newScale / pageScaleFactor();

  needAnimation = false;

  // If we are at less than the target zoom level, zoom in.
  if (deltaScale > minScaleChangeToTriggerZoom)
    needAnimation = true;
  else
    newScale = pageScaleFactor();

  // If the caret is offscreen, then animate.
  if (!visualViewport.visibleRectInDocument().contains(caretInDocument))
    needAnimation = true;

  // If the box is partially offscreen and it's possible to bring it fully
  // onscreen, then animate.
  if (visualViewport.visibleRect().width() >= textboxRectInDocument.width() &&
      visualViewport.visibleRect().height() >= textboxRectInDocument.height() &&
      !visualViewport.visibleRectInDocument().contains(textboxRectInDocument))
    needAnimation = true;

  if (!needAnimation)
    return;

  FloatSize targetViewportSize(visualViewport.size());
  targetViewportSize.scale(1 / newScale);

  if (textboxRectInDocument.width() <= targetViewportSize.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 idealLeftPadding = targetViewportSize.width() * leftBoxRatio;
    int maxLeftPaddingKeepingBoxOnscreen =
        targetViewportSize.width() - textboxRectInDocument.width();
    newScroll.setX(
        textboxRectInDocument.x() -
        std::min<int>(idealLeftPadding, maxLeftPaddingKeepingBoxOnscreen));
  } else {
    // Field is wider than screen. Try to left-align field, unless caret would
    // be offscreen, in which case right-align the caret.
    newScroll.setX(std::max<int>(textboxRectInDocument.x(),
                                 caretInDocument.x() + caretInDocument.width() +
                                     caretPadding -
                                     targetViewportSize.width()));
  }
  if (textboxRectInDocument.height() <= targetViewportSize.height()) {
    // Field is shorter than screen. Vertically center it.
    newScroll.setY(
        textboxRectInDocument.y() -
        (targetViewportSize.height() - textboxRectInDocument.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.
    newScroll.setY(std::max<int>(textboxRectInDocument.y(),
                                 caretInDocument.y() +
                                     caretInDocument.height() + caretPadding -
                                     targetViewportSize.height()));
  }
}

void WebViewImpl::advanceFocus(bool reverse) {
  page()->focusController().advanceFocus(reverse ? WebFocusTypeBackward
                                                 : WebFocusTypeForward);
}

void WebViewImpl::advanceFocusAcrossFrames(WebFocusType type,
                                           WebRemoteFrame* from,
                                           WebLocalFrame* to) {
  // TODO(alexmos): Pass in proper with sourceCapabilities.
  page()->focusController().advanceFocusAcrossFrames(
      type, toWebRemoteFrameImpl(from)->frame(),
      toWebLocalFrameImpl(to)->frame());
}

double WebViewImpl::zoomLevel() {
  return m_zoomLevel;
}

void WebViewImpl::propagateZoomFactorToLocalFrameRoots(Frame* frame,
                                                       float zoomFactor) {
  if (frame->isLocalRoot()) {
    LocalFrame* localFrame = toLocalFrame(frame);
    if (!WebLocalFrameImpl::pluginContainerFromFrame(localFrame))
      localFrame->setPageZoomFactor(zoomFactor);
  }

  for (Frame* child = frame->tree().firstChild(); child;
       child = child->tree().nextSibling())
    propagateZoomFactorToLocalFrameRoots(child, zoomFactor);
}

double WebViewImpl::setZoomLevel(double zoomLevel) {
  if (zoomLevel < m_minimumZoomLevel)
    m_zoomLevel = m_minimumZoomLevel;
  else if (zoomLevel > m_maximumZoomLevel)
    m_zoomLevel = m_maximumZoomLevel;
  else
    m_zoomLevel = zoomLevel;

  float zoomFactor =
      m_zoomFactorOverride
          ? m_zoomFactorOverride
          : static_cast<float>(zoomLevelToZoomFactor(m_zoomLevel));
  if (m_zoomFactorForDeviceScaleFactor) {
    if (m_compositorDeviceScaleFactorOverride) {
      // Adjust the page's DSF so that DevicePixelRatio becomes
      // m_zoomFactorForDeviceScaleFactor.
      page()->setDeviceScaleFactor(m_zoomFactorForDeviceScaleFactor /
                                   m_compositorDeviceScaleFactorOverride);
      zoomFactor *= m_compositorDeviceScaleFactorOverride;
    } else {
      page()->setDeviceScaleFactor(1.f);
      zoomFactor *= m_zoomFactorForDeviceScaleFactor;
    }
  }
  propagateZoomFactorToLocalFrameRoots(m_page->mainFrame(), zoomFactor);

  return m_zoomLevel;
}

void WebViewImpl::zoomLimitsChanged(double minimumZoomLevel,
                                    double maximumZoomLevel) {
  m_minimumZoomLevel = minimumZoomLevel;
  m_maximumZoomLevel = maximumZoomLevel;
  m_client->zoomLimitsChanged(m_minimumZoomLevel, m_maximumZoomLevel);
}

float WebViewImpl::textZoomFactor() {
  return mainFrameImpl()->frame()->textZoomFactor();
}

float WebViewImpl::setTextZoomFactor(float textZoomFactor) {
  LocalFrame* frame = mainFrameImpl()->frame();
  if (WebLocalFrameImpl::pluginContainerFromFrame(frame))
    return 1;

  frame->setTextZoomFactor(textZoomFactor);

  return textZoomFactor;
}

double WebView::zoomLevelToZoomFactor(double zoomLevel) {
  return pow(textSizeMultiplierRatio, zoomLevel);
}

double WebView::zoomFactorToZoomLevel(double factor) {
  // Since factor = 1.2^level, level = log(factor) / log(1.2)
  return log(factor) / log(textSizeMultiplierRatio);
}

float WebViewImpl::pageScaleFactor() const {
  if (!page())
    return 1;

  return page()->frameHost().visualViewport().scale();
}

float WebViewImpl::clampPageScaleFactorToLimits(float scaleFactor) const {
  return pageScaleConstraintsSet().finalConstraints().clampToConstraints(
      scaleFactor);
}

void WebViewImpl::setVisualViewportOffset(const WebFloatPoint& offset) {
  DCHECK(page());
  page()->frameHost().visualViewport().setLocation(offset);
}

WebFloatPoint WebViewImpl::visualViewportOffset() const {
  DCHECK(page());
  return page()->frameHost().visualViewport().visibleRect().location();
}

WebFloatSize WebViewImpl::visualViewportSize() const {
  DCHECK(page());
  return page()->frameHost().visualViewport().visibleRect().size();
}

void WebViewImpl::scrollAndRescaleViewports(
    float scaleFactor,
    const IntPoint& mainFrameOrigin,
    const FloatPoint& visualViewportOrigin) {
  if (!page())
    return;

  if (!mainFrameImpl())
    return;

  FrameView* view = mainFrameImpl()->frameView();
  if (!view)
    return;

  // Order is important: visual viewport location is clamped based on
  // main frame scroll position and visual viewport scale.

  view->setScrollOffset(toScrollOffset(mainFrameOrigin), ProgrammaticScroll);

  setPageScaleFactor(scaleFactor);

  page()->frameHost().visualViewport().setLocation(visualViewportOrigin);
}

void WebViewImpl::setPageScaleFactorAndLocation(float scaleFactor,
                                                const FloatPoint& location) {
  DCHECK(page());

  page()->frameHost().visualViewport().setScaleAndLocation(
      clampPageScaleFactorToLimits(scaleFactor), location);
}

void WebViewImpl::setPageScaleFactor(float scaleFactor) {
  DCHECK(page());

  scaleFactor = clampPageScaleFactorToLimits(scaleFactor);
  if (scaleFactor == pageScaleFactor())
    return;

  page()->frameHost().visualViewport().setScale(scaleFactor);
}

void WebViewImpl::setDeviceScaleFactor(float scaleFactor) {
  if (!page())
    return;

  page()->setDeviceScaleFactor(scaleFactor);

  if (m_layerTreeView)
    updateLayerTreeDeviceScaleFactor();
}

void WebViewImpl::setZoomFactorForDeviceScaleFactor(
    float zoomFactorForDeviceScaleFactor) {
  m_zoomFactorForDeviceScaleFactor = zoomFactorForDeviceScaleFactor;
  if (!m_layerTreeView)
    return;
  setZoomLevel(m_zoomLevel);
}

void WebViewImpl::setDeviceColorProfile(const WebVector<char>& colorProfile) {
  Vector<char> deviceProfile;
  deviceProfile.append(colorProfile.data(), colorProfile.size());
  ImageDecoder::setTargetColorProfile(deviceProfile);
}

void WebViewImpl::enableAutoResizeMode(const WebSize& minSize,
                                       const WebSize& maxSize) {
  m_shouldAutoResize = true;
  m_minAutoSize = minSize;
  m_maxAutoSize = maxSize;
  configureAutoResizeMode();
}

void WebViewImpl::disableAutoResizeMode() {
  m_shouldAutoResize = false;
  configureAutoResizeMode();
}

void WebViewImpl::setDefaultPageScaleLimits(float minScale, float maxScale) {
  return page()->frameHost().setDefaultPageScaleLimits(minScale, maxScale);
}

void WebViewImpl::setInitialPageScaleOverride(
    float initialPageScaleFactorOverride) {
  PageScaleConstraints constraints =
      pageScaleConstraintsSet().userAgentConstraints();
  constraints.initialScale = initialPageScaleFactorOverride;

  if (constraints == pageScaleConstraintsSet().userAgentConstraints())
    return;

  pageScaleConstraintsSet().setNeedsReset(true);
  page()->frameHost().setUserAgentPageScaleConstraints(constraints);
}

void WebViewImpl::setMaximumLegibleScale(float maximumLegibleScale) {
  m_maximumLegibleScale = maximumLegibleScale;
}

void WebViewImpl::setIgnoreViewportTagScaleLimits(bool ignore) {
  PageScaleConstraints constraints =
      pageScaleConstraintsSet().userAgentConstraints();
  if (ignore) {
    constraints.minimumScale =
        pageScaleConstraintsSet().defaultConstraints().minimumScale;
    constraints.maximumScale =
        pageScaleConstraintsSet().defaultConstraints().maximumScale;
  } else {
    constraints.minimumScale = -1;
    constraints.maximumScale = -1;
  }
  page()->frameHost().setUserAgentPageScaleConstraints(constraints);
}

IntSize WebViewImpl::mainFrameSize() {
  // The frame size should match the viewport size at minimum scale, since the
  // viewport must always be contained by the frame.
  FloatSize frameSize(m_size);
  frameSize.scale(1 / minimumPageScaleFactor());
  return expandedIntSize(frameSize);
}

PageScaleConstraintsSet& WebViewImpl::pageScaleConstraintsSet() const {
  return page()->frameHost().pageScaleConstraintsSet();
}

void WebViewImpl::refreshPageScaleFactorAfterLayout() {
  if (!mainFrame() || !page() || !page()->mainFrame() ||
      !page()->mainFrame()->isLocalFrame() ||
      !page()->deprecatedLocalMainFrame()->view())
    return;
  FrameView* view = page()->deprecatedLocalMainFrame()->view();

  updatePageDefinedViewportConstraints(
      mainFrameImpl()->frame()->document()->viewportDescription());
  pageScaleConstraintsSet().computeFinalConstraints();

  int verticalScrollbarWidth = 0;
  if (view->verticalScrollbar() &&
      !view->verticalScrollbar()->isOverlayScrollbar())
    verticalScrollbarWidth = view->verticalScrollbar()->width();
  pageScaleConstraintsSet().adjustFinalConstraintsToContentsSize(
      contentsSize(), verticalScrollbarWidth,
      settings()->shrinksViewportContentToFit());

  float newPageScaleFactor = pageScaleFactor();
  if (pageScaleConstraintsSet().needsReset() &&
      pageScaleConstraintsSet().finalConstraints().initialScale != -1) {
    newPageScaleFactor =
        pageScaleConstraintsSet().finalConstraints().initialScale;
    pageScaleConstraintsSet().setNeedsReset(false);
  }
  setPageScaleFactor(newPageScaleFactor);

  updateLayerTreeViewport();

  // Changes to page-scale during layout may require an additional frame.
  // We can't update the lifecycle here because we may be in the middle of
  // layout in the caller of this method.
  // TODO(chrishtr): clean all this up. All layout should happen in one
  // lifecycle run (crbug.com/578239).
  if (mainFrameImpl()->frameView()->needsLayout())
    mainFrameImpl()->frameWidget()->scheduleAnimation();
}

void WebViewImpl::updatePageDefinedViewportConstraints(
    const ViewportDescription& description) {
  if (!page() || (!m_size.width && !m_size.height) ||
      !page()->mainFrame()->isLocalFrame())
    return;

  if (!settings()->viewportEnabled()) {
    pageScaleConstraintsSet().clearPageDefinedConstraints();
    updateMainFrameLayoutSize();

    // If we don't support mobile viewports, allow GPU rasterization.
    m_matchesHeuristicsForGpuRasterization = true;
    if (m_layerTreeView)
      m_layerTreeView->heuristicsForGpuRasterizationUpdated(
          m_matchesHeuristicsForGpuRasterization);
    return;
  }

  Document* document = page()->deprecatedLocalMainFrame()->document();

  m_matchesHeuristicsForGpuRasterization =
      description.matchesHeuristicsForGpuRasterization();
  if (m_layerTreeView)
    m_layerTreeView->heuristicsForGpuRasterizationUpdated(
        m_matchesHeuristicsForGpuRasterization);

  Length defaultMinWidth = document->viewportDefaultMinWidth();
  if (defaultMinWidth.isAuto())
    defaultMinWidth = Length(ExtendToZoom);

  ViewportDescription adjustedDescription = description;
  if (settingsImpl()->viewportMetaLayoutSizeQuirk() &&
      adjustedDescription.type == ViewportDescription::ViewportMeta) {
    const int legacyWidthSnappingMagicNumber = 320;
    if (adjustedDescription.maxWidth.isFixed() &&
        adjustedDescription.maxWidth.value() <= legacyWidthSnappingMagicNumber)
      adjustedDescription.maxWidth = Length(DeviceWidth);
    if (adjustedDescription.maxHeight.isFixed() &&
        adjustedDescription.maxHeight.value() <= m_size.height)
      adjustedDescription.maxHeight = Length(DeviceHeight);
    adjustedDescription.minWidth = adjustedDescription.maxWidth;
    adjustedDescription.minHeight = adjustedDescription.maxHeight;
  }

  float oldInitialScale =
      pageScaleConstraintsSet().pageDefinedConstraints().initialScale;
  pageScaleConstraintsSet().updatePageDefinedConstraints(adjustedDescription,
                                                         defaultMinWidth);

  if (settingsImpl()->clobberUserAgentInitialScaleQuirk() &&
      pageScaleConstraintsSet().userAgentConstraints().initialScale != -1 &&
      pageScaleConstraintsSet().userAgentConstraints().initialScale *
              deviceScaleFactor() <=
          1) {
    if (description.maxWidth == Length(DeviceWidth) ||
        (description.maxWidth.type() == Auto &&
         pageScaleConstraintsSet().pageDefinedConstraints().initialScale ==
             1.0f))
      setInitialPageScaleOverride(-1);
  }

  Settings& pageSettings = page()->settings();
  pageScaleConstraintsSet().adjustForAndroidWebViewQuirks(
      adjustedDescription, defaultMinWidth.intValue(), deviceScaleFactor(),
      settingsImpl()->supportDeprecatedTargetDensityDPI(),
      pageSettings.wideViewportQuirkEnabled(), pageSettings.useWideViewport(),
      pageSettings.loadWithOverviewMode(),
      settingsImpl()->viewportMetaNonUserScalableQuirk());
  float newInitialScale =
      pageScaleConstraintsSet().pageDefinedConstraints().initialScale;
  if (oldInitialScale != newInitialScale && newInitialScale != -1) {
    pageScaleConstraintsSet().setNeedsReset(true);
    if (mainFrameImpl() && mainFrameImpl()->frameView())
      mainFrameImpl()->frameView()->setNeedsLayout();
  }

  if (LocalFrame* frame = page()->deprecatedLocalMainFrame()) {
    if (TextAutosizer* textAutosizer = frame->document()->textAutosizer())
      textAutosizer->updatePageInfoInAllFrames();
  }

  updateMainFrameLayoutSize();
}

void WebViewImpl::updateMainFrameLayoutSize() {
  if (m_shouldAutoResize || !mainFrameImpl())
    return;

  FrameView* view = mainFrameImpl()->frameView();
  if (!view)
    return;

  WebSize layoutSize = m_size;

  if (settings()->viewportEnabled())
    layoutSize = pageScaleConstraintsSet().layoutSize();

  if (page()->settings().forceZeroLayoutHeight())
    layoutSize.height = 0;

  view->setLayoutSize(layoutSize);
}

IntSize WebViewImpl::contentsSize() const {
  if (!page()->mainFrame()->isLocalFrame())
    return IntSize();
  LayoutViewItem root = page()->deprecatedLocalMainFrame()->contentLayoutItem();
  if (root.isNull())
    return IntSize();
  return root.documentRect().size();
}

WebSize WebViewImpl::contentsPreferredMinimumSize() {
  if (mainFrameImpl())
    mainFrameImpl()
        ->frame()
        ->view()
        ->updateLifecycleToCompositingCleanPlusScrolling();

  Document* document = m_page->mainFrame()->isLocalFrame()
                           ? m_page->deprecatedLocalMainFrame()->document()
                           : nullptr;
  if (!document || document->layoutViewItem().isNull() ||
      !document->documentElement() || !document->documentElement()->layoutBox())
    return WebSize();

  int widthScaled = document->layoutViewItem()
                        .minPreferredLogicalWidth()
                        .round();  // Already accounts for zoom.
  int heightScaled =
      document->documentElement()->layoutBox()->scrollHeight().round();
  return IntSize(widthScaled, heightScaled);
}

float WebViewImpl::defaultMinimumPageScaleFactor() const {
  return pageScaleConstraintsSet().defaultConstraints().minimumScale;
}

float WebViewImpl::defaultMaximumPageScaleFactor() const {
  return pageScaleConstraintsSet().defaultConstraints().maximumScale;
}

float WebViewImpl::minimumPageScaleFactor() const {
  return pageScaleConstraintsSet().finalConstraints().minimumScale;
}

float WebViewImpl::maximumPageScaleFactor() const {
  return pageScaleConstraintsSet().finalConstraints().maximumScale;
}

void WebViewImpl::resetScaleStateImmediately() {
  pageScaleConstraintsSet().setNeedsReset(true);
}

void WebViewImpl::resetScrollAndScaleState() {
  page()->frameHost().visualViewport().reset();

  if (!page()->mainFrame()->isLocalFrame())
    return;

  if (FrameView* frameView = toLocalFrame(page()->mainFrame())->view()) {
    ScrollableArea* scrollableArea = frameView->layoutViewportScrollableArea();

    if (!scrollableArea->scrollOffset().isZero())
      scrollableArea->setScrollOffset(ScrollOffset(), ProgrammaticScroll);
  }

  pageScaleConstraintsSet().setNeedsReset(true);
}

void WebViewImpl::performMediaPlayerAction(const WebMediaPlayerAction& action,
                                           const WebPoint& location) {
  HitTestResult result = hitTestResultForViewportPos(location);
  Node* node = result.innerNode();
  if (!isHTMLVideoElement(*node) && !isHTMLAudioElement(*node))
    return;

  HTMLMediaElement* mediaElement = toHTMLMediaElement(node);
  switch (action.type) {
    case WebMediaPlayerAction::Play:
      if (action.enable)
        mediaElement->play();
      else
        mediaElement->pause();
      break;
    case WebMediaPlayerAction::Mute:
      mediaElement->setMuted(action.enable);
      break;
    case WebMediaPlayerAction::Loop:
      mediaElement->setLoop(action.enable);
      break;
    case WebMediaPlayerAction::Controls:
      mediaElement->setBooleanAttribute(HTMLNames::controlsAttr, action.enable);
      break;
    default:
      NOTREACHED();
  }
}

void WebViewImpl::performPluginAction(const WebPluginAction& action,
                                      const WebPoint& location) {
  // FIXME: Location is probably in viewport coordinates
  HitTestResult result = hitTestResultForRootFramePos(location);
  Node* node = result.innerNode();
  if (!isHTMLObjectElement(*node) && !isHTMLEmbedElement(*node))
    return;

  LayoutObject* object = node->layoutObject();
  if (object && object->isLayoutPart()) {
    Widget* widget = toLayoutPart(object)->widget();
    if (widget && widget->isPluginContainer()) {
      WebPluginContainerImpl* plugin = toWebPluginContainerImpl(widget);
      switch (action.type) {
        case WebPluginAction::Rotate90Clockwise:
          plugin->plugin()->rotateView(WebPlugin::RotationType90Clockwise);
          break;
        case WebPluginAction::Rotate90Counterclockwise:
          plugin->plugin()->rotateView(
              WebPlugin::RotationType90Counterclockwise);
          break;
        default:
          NOTREACHED();
      }
    }
  }
}

void WebViewImpl::audioStateChanged(bool isAudioPlaying) {
  m_scheduler->audioStateChanged(isAudioPlaying);
}

WebHitTestResult WebViewImpl::hitTestResultAt(const WebPoint& point) {
  return coreHitTestResultAt(point);
}

HitTestResult WebViewImpl::coreHitTestResultAt(
    const WebPoint& pointInViewport) {
  DocumentLifecycle::AllowThrottlingScope throttlingScope(
      mainFrameImpl()->frame()->document()->lifecycle());
  FrameView* view = mainFrameImpl()->frameView();
  IntPoint pointInRootFrame =
      view->contentsToFrame(view->viewportToContents(pointInViewport));
  return hitTestResultForRootFramePos(pointInRootFrame);
}

void WebViewImpl::spellingMarkers(WebVector<uint32_t>* markers) {
  Vector<uint32_t> result;
  for (Frame* frame = m_page->mainFrame(); frame;
       frame = frame->tree().traverseNext()) {
    if (!frame->isLocalFrame())
      continue;
    const DocumentMarkerVector& documentMarkers =
        toLocalFrame(frame)->document()->markers().markers();
    for (size_t i = 0; i < documentMarkers.size(); ++i)
      result.append(documentMarkers[i]->hash());
  }
  markers->assign(result);
}

void WebViewImpl::removeSpellingMarkersUnderWords(
    const WebVector<WebString>& words) {
  Vector<String> convertedWords;
  convertedWords.append(words.data(), words.size());

  for (Frame* frame = m_page->mainFrame(); frame;
       frame = frame->tree().traverseNext()) {
    if (frame->isLocalFrame())
      toLocalFrame(frame)->removeSpellingMarkersUnderWords(convertedWords);
  }
}

void WebViewImpl::sendResizeEventAndRepaint() {
  // FIXME: This is wrong. The FrameView is responsible sending a resizeEvent
  // as part of layout. Layout is also responsible for sending invalidations
  // to the embedder. This method and all callers may be wrong. -- eseidel.
  if (mainFrameImpl()->frameView()) {
    // Enqueues the resize event.
    mainFrameImpl()->frame()->document()->enqueueResizeEvent();
  }

  if (m_client) {
    if (m_layerTreeView) {
      updateLayerTreeViewport();
    } else {
      WebRect damagedRect(0, 0, m_size.width, m_size.height);
      m_client->widgetClient()->didInvalidateRect(damagedRect);
    }
  }
}

void WebViewImpl::configureAutoResizeMode() {
  if (!mainFrameImpl() || !mainFrameImpl()->frame() ||
      !mainFrameImpl()->frame()->view())
    return;

  if (m_shouldAutoResize)
    mainFrameImpl()->frame()->view()->enableAutoSizeMode(m_minAutoSize,
                                                         m_maxAutoSize);
  else
    mainFrameImpl()->frame()->view()->disableAutoSizeMode();
}

unsigned long WebViewImpl::createUniqueIdentifierForRequest() {
  return createUniqueIdentifier();
}

void WebViewImpl::setCompositorDeviceScaleFactorOverride(
    float deviceScaleFactor) {
  if (m_compositorDeviceScaleFactorOverride == deviceScaleFactor)
    return;
  m_compositorDeviceScaleFactorOverride = deviceScaleFactor;
  if (m_zoomFactorForDeviceScaleFactor) {
    setZoomLevel(zoomLevel());
    return;
  }
  if (page() && m_layerTreeView)
    updateLayerTreeDeviceScaleFactor();
}

void WebViewImpl::setDeviceEmulationTransform(
    const TransformationMatrix& transform) {
  if (transform == m_deviceEmulationTransform)
    return;
  m_deviceEmulationTransform = transform;
  updateDeviceEmulationTransform();
}

TransformationMatrix WebViewImpl::getDeviceEmulationTransformForTesting()
    const {
  return m_deviceEmulationTransform;
}

void WebViewImpl::enableDeviceEmulation(
    const WebDeviceEmulationParams& params) {
  m_devToolsEmulator->enableDeviceEmulation(params);
}

void WebViewImpl::disableDeviceEmulation() {
  m_devToolsEmulator->disableDeviceEmulation();
}

WebAXObject WebViewImpl::accessibilityObject() {
  if (!mainFrameImpl())
    return WebAXObject();

  Document* document = mainFrameImpl()->frame()->document();
  return WebAXObject(toAXObjectCacheImpl(document->axObjectCache())->root());
}

void WebViewImpl::performCustomContextMenuAction(unsigned action) {
  if (!m_page)
    return;
  ContextMenu* menu = m_page->contextMenuController().contextMenu();
  if (!menu)
    return;
  const ContextMenuItem* item = menu->itemWithAction(
      static_cast<ContextMenuAction>(ContextMenuItemBaseCustomTag + action));
  if (item)
    m_page->contextMenuController().contextMenuItemSelected(item);
  m_page->contextMenuController().clearContextMenu();
}

void WebViewImpl::showContextMenu() {
  if (!page())
    return;

  page()->contextMenuController().clearContextMenu();
  {
    ContextMenuAllowedScope scope;
    if (LocalFrame* focusedFrame =
            toLocalFrame(page()->focusController().focusedOrMainFrame()))
      focusedFrame->eventHandler().sendContextMenuEventForKey(nullptr);
  }
}

void WebViewImpl::didCloseContextMenu() {
  LocalFrame* frame = m_page->focusController().focusedFrame();
  if (frame)
    frame->selection().setCaretBlinkingSuspended(false);
}

void WebViewImpl::extractSmartClipData(WebRect rectInViewport,
                                       WebString& clipText,
                                       WebString& clipHtml,
                                       WebRect& clipRectInViewport) {
  LocalFrame* localFrame = toLocalFrame(focusedCoreFrame());
  if (!localFrame)
    return;
  SmartClipData clipData = SmartClip(localFrame).dataForRect(rectInViewport);
  clipText = clipData.clipData();
  clipRectInViewport = clipData.rectInViewport();

  WebLocalFrameImpl* frame = mainFrameImpl();
  if (!frame)
    return;
  WebPoint startPoint(rectInViewport.x, rectInViewport.y);
  WebPoint endPoint(rectInViewport.x + rectInViewport.width,
                    rectInViewport.y + rectInViewport.height);
  VisiblePosition startVisiblePosition =
      frame->visiblePositionForViewportPoint(startPoint);
  VisiblePosition endVisiblePosition =
      frame->visiblePositionForViewportPoint(endPoint);

  Position startPosition = startVisiblePosition.deepEquivalent();
  Position endPosition = endVisiblePosition.deepEquivalent();

  // document() will return null if -webkit-user-select is set to none.
  if (!startPosition.document() || !endPosition.document())
    return;

  if (startPosition.compareTo(endPosition) <= 0) {
    clipHtml =
        createMarkup(startPosition, endPosition, AnnotateForInterchange,
                     ConvertBlocksToInlines::NotConvert, ResolveNonLocalURLs);
  } else {
    clipHtml =
        createMarkup(endPosition, startPosition, AnnotateForInterchange,
                     ConvertBlocksToInlines::NotConvert, ResolveNonLocalURLs);
  }
}

void WebViewImpl::hidePopups() {
  cancelPagePopup();
}

void WebViewImpl::setIsTransparent(bool isTransparent) {
  // Set any existing frames to be transparent.
  Frame* frame = m_page->mainFrame();
  while (frame) {
    if (frame->isLocalFrame())
      toLocalFrame(frame)->view()->setTransparent(isTransparent);
    frame = frame->tree().traverseNext();
  }

  // Future frames check this to know whether to be transparent.
  m_isTransparent = isTransparent;

  if (m_layerTreeView)
    m_layerTreeView->setHasTransparentBackground(this->isTransparent());
}

bool WebViewImpl::isTransparent() const {
  return m_isTransparent;
}

WebInputMethodControllerImpl* WebViewImpl::getActiveWebInputMethodController()
    const {
  return WebInputMethodControllerImpl::fromFrame(
      focusedLocalFrameAvailableForIme());
}

void WebViewImpl::setBaseBackgroundColor(WebColor color) {
  if (m_baseBackgroundColor == color)
    return;

  m_baseBackgroundColor = color;

  if (m_page->mainFrame() && m_page->mainFrame()->isLocalFrame())
    m_page->deprecatedLocalMainFrame()->view()->setBaseBackgroundColor(color);
}

void WebViewImpl::setIsActive(bool active) {
  if (page())
    page()->focusController().setActive(active);
}

bool WebViewImpl::isActive() const {
  return page() ? page()->focusController().isActive() : false;
}

void WebViewImpl::setDomainRelaxationForbidden(bool forbidden,
                                               const WebString& scheme) {
  SchemeRegistry::setDomainRelaxationForbiddenForURLScheme(forbidden,
                                                           String(scheme));
}

void WebViewImpl::setWindowFeatures(const WebWindowFeatures& features) {
  m_page->chromeClient().setWindowFeatures(features);
}

void WebViewImpl::setOpenedByDOM() {
  m_page->setOpenedByDOM();
}

void WebViewImpl::setSelectionColors(unsigned activeBackgroundColor,
                                     unsigned activeForegroundColor,
                                     unsigned inactiveBackgroundColor,
                                     unsigned inactiveForegroundColor) {
#if USE(DEFAULT_RENDER_THEME)
  LayoutThemeDefault::setSelectionColors(
      activeBackgroundColor, activeForegroundColor, inactiveBackgroundColor,
      inactiveForegroundColor);
  LayoutTheme::theme().platformColorsDidChange();
#endif
}

void WebViewImpl::didCommitLoad(bool isNewNavigation,
                                bool isNavigationWithinPage) {
  if (!isNavigationWithinPage) {
    m_shouldDispatchFirstVisuallyNonEmptyLayout = true;
    m_shouldDispatchFirstLayoutAfterFinishedParsing = true;
    m_shouldDispatchFirstLayoutAfterFinishedLoading = true;

    if (isNewNavigation) {
      pageScaleConstraintsSet().setNeedsReset(true);
      m_pageImportanceSignals.onCommitLoad();
    }
  }

  // Give the visual viewport's scroll layer its initial size.
  page()->frameHost().visualViewport().mainFrameDidChangeSize();

  // Make sure link highlight from previous page is cleared.
  m_linkHighlights.clear();
  endActiveFlingAnimation();
  m_userGestureObserved = false;
}

void WebViewImpl::postLayoutResize(WebLocalFrameImpl* webframe) {
  FrameView* view = webframe->frame()->view();
  if (webframe == mainFrame())
    m_resizeViewportAnchor->resizeFrameView(mainFrameSize());
  else
    view->resize(webframe->frameView()->size());
}

void WebViewImpl::layoutUpdated(WebLocalFrameImpl* webframe) {
  LocalFrame* frame = webframe->frame();
  if (!m_client || !frame->isLocalRoot())
    return;

  if (m_shouldAutoResize) {
    WebSize frameSize = frame->view()->frameRect().size();
    if (frameSize != m_size) {
      m_size = frameSize;

      page()->frameHost().visualViewport().setSize(m_size);
      pageScaleConstraintsSet().didChangeInitialContainingBlockSize(m_size);
      frame->view()->setInitialViewportSize(m_size);

      m_client->didAutoResize(m_size);
      sendResizeEventAndRepaint();
    }
  }

  if (pageScaleConstraintsSet().constraintsDirty())
    refreshPageScaleFactorAfterLayout();

  FrameView* view = webframe->frame()->view();

  postLayoutResize(webframe);

  // Relayout immediately to avoid violating the rule that needsLayout()
  // isn't set at the end of a layout.
  if (view->needsLayout())
    view->layout();

  updatePageOverlays();

  m_fullscreenController->didUpdateLayout();
  m_client->didUpdateLayout();
}

void WebViewImpl::didChangeContentsSize() {
  pageScaleConstraintsSet().didChangeContentsSize(contentsSize(),
                                                  pageScaleFactor());
}

void WebViewImpl::pageScaleFactorChanged() {
  pageScaleConstraintsSet().setNeedsReset(false);
  updateLayerTreeViewport();
  m_client->pageScaleFactorChanged();
  m_devToolsEmulator->mainFrameScrollOrScaleChanged();
}

void WebViewImpl::mainFrameScrollOffsetChanged() {
  m_devToolsEmulator->mainFrameScrollOrScaleChanged();
}

bool WebViewImpl::useExternalPopupMenus() {
  return shouldUseExternalPopupMenus;
}

void WebViewImpl::setIgnoreInputEvents(bool newValue) {
  DCHECK_NE(m_ignoreInputEvents, newValue);
  m_ignoreInputEvents = newValue;
}

void WebViewImpl::setBackgroundColorOverride(WebColor color) {
  m_backgroundColorOverride = color;
  updateLayerTreeBackgroundColor();
}

void WebViewImpl::setZoomFactorOverride(float zoomFactor) {
  m_zoomFactorOverride = zoomFactor;
  setZoomLevel(zoomLevel());
}

void WebViewImpl::setPageOverlayColor(WebColor color) {
  if (m_pageColorOverlay)
    m_pageColorOverlay.reset();

  if (color == Color::transparent)
    return;

  m_pageColorOverlay =
      PageOverlay::create(mainFrameImpl(), makeUnique<ColorOverlay>(color));
  m_pageColorOverlay->update();
}

WebPageImportanceSignals* WebViewImpl::pageImportanceSignals() {
  return &m_pageImportanceSignals;
}

Element* WebViewImpl::focusedElement() const {
  LocalFrame* frame = m_page->focusController().focusedFrame();
  if (!frame)
    return nullptr;

  Document* document = frame->document();
  if (!document)
    return nullptr;

  return document->focusedElement();
}

HitTestResult WebViewImpl::hitTestResultForViewportPos(
    const IntPoint& posInViewport) {
  IntPoint rootFramePoint(
      m_page->frameHost().visualViewport().viewportToRootFrame(posInViewport));
  return hitTestResultForRootFramePos(rootFramePoint);
}

HitTestResult WebViewImpl::hitTestResultForRootFramePos(
    const IntPoint& posInRootFrame) {
  if (!m_page->mainFrame()->isLocalFrame())
    return HitTestResult();
  IntPoint docPoint(
      m_page->deprecatedLocalMainFrame()->view()->rootFrameToContents(
          posInRootFrame));
  HitTestResult result =
      m_page->deprecatedLocalMainFrame()->eventHandler().hitTestResultAtPoint(
          docPoint, HitTestRequest::ReadOnly | HitTestRequest::Active);
  result.setToShadowHostIfInUserAgentShadowRoot();
  return result;
}

WebHitTestResult WebViewImpl::hitTestResultForTap(
    const WebPoint& tapPointWindowPos,
    const WebSize& tapArea) {
  if (!m_page->mainFrame()->isLocalFrame())
    return HitTestResult();

  WebGestureEvent tapEvent;
  tapEvent.x = tapPointWindowPos.x;
  tapEvent.y = tapPointWindowPos.y;
  tapEvent.type = WebInputEvent::GestureTap;
  // GestureTap is only ever from a touchscreen.
  tapEvent.sourceDevice = WebGestureDeviceTouchscreen;
  tapEvent.data.tap.tapCount = 1;
  tapEvent.data.tap.width = tapArea.width;
  tapEvent.data.tap.height = tapArea.height;

  PlatformGestureEventBuilder platformEvent(mainFrameImpl()->frameView(),
                                            tapEvent);

  HitTestResult result =
      m_page->deprecatedLocalMainFrame()
          ->eventHandler()
          .hitTestResultForGestureEvent(
              platformEvent, HitTestRequest::ReadOnly | HitTestRequest::Active)
          .hitTestResult();

  result.setToShadowHostIfInUserAgentShadowRoot();
  return result;
}

void WebViewImpl::setTabsToLinks(bool enable) {
  m_tabsToLinks = enable;
}

bool WebViewImpl::tabsToLinks() const {
  return m_tabsToLinks;
}

void WebViewImpl::registerViewportLayersWithCompositor() {
  DCHECK(m_layerTreeView);

  if (!page()->mainFrame() || !page()->mainFrame()->isLocalFrame())
    return;

  Document* document = page()->deprecatedLocalMainFrame()->document();

  DCHECK(document);

  // Get the outer viewport scroll layer.
  GraphicsLayer* layoutViewportScrollLayer =
      page()->frameHost().globalRootScrollerController().rootScrollerLayer();
  WebLayer* layoutViewportWebLayer =
      layoutViewportScrollLayer ? layoutViewportScrollLayer->platformLayer()
                                : nullptr;

  VisualViewport& visualViewport = page()->frameHost().visualViewport();

  // TODO(bokan): This was moved here from when registerViewportLayers was a
  // part of VisualViewport and maybe doesn't belong here. See comment inside
  // the mehtod.
  visualViewport.setScrollLayerOnScrollbars(layoutViewportWebLayer);

  m_layerTreeView->registerViewportLayers(
      visualViewport.overscrollElasticityLayer()->platformLayer(),
      visualViewport.pageScaleLayer()->platformLayer(),
      visualViewport.scrollLayer()->platformLayer(), layoutViewportWebLayer);
}

void WebViewImpl::setRootGraphicsLayer(GraphicsLayer* graphicsLayer) {
  if (!m_layerTreeView)
    return;

  // In SPv2, setRootLayer is used instead.
  DCHECK(!RuntimeEnabledFeatures::slimmingPaintV2Enabled());

  VisualViewport& visualViewport = page()->frameHost().visualViewport();
  visualViewport.attachToLayerTree(graphicsLayer);
  if (graphicsLayer) {
    m_rootGraphicsLayer = visualViewport.rootGraphicsLayer();
    m_visualViewportContainerLayer = visualViewport.containerLayer();
    m_rootLayer = m_rootGraphicsLayer->platformLayer();
    updateDeviceEmulationTransform();
    m_layerTreeView->setRootLayer(*m_rootLayer);
    // We register viewport layers here since there may not be a layer
    // tree view prior to this point.
    registerViewportLayersWithCompositor();

    // TODO(enne): Work around page visibility changes not being
    // propagated to the WebView in some circumstances.  This needs to
    // be refreshed here when setting a new root layer to avoid being
    // stuck in a presumed incorrectly invisible state.
    m_layerTreeView->setVisible(page()->isPageVisible());
  } else {
    m_rootGraphicsLayer = nullptr;
    m_visualViewportContainerLayer = nullptr;
    m_rootLayer = nullptr;
    // This means that we're transitioning to a new page. Suppress
    // commits until Blink generates invalidations so we don't
    // attempt to paint too early in the next page load.
    m_layerTreeView->setDeferCommits(true);
    m_layerTreeView->clearRootLayer();
    m_layerTreeView->clearViewportLayers();
    if (WebDevToolsAgentImpl* devTools = mainFrameDevToolsAgentImpl())
      devTools->rootLayerCleared();
  }
}

void WebViewImpl::setRootLayer(WebLayer* layer) {
  if (!m_layerTreeView)
    return;

  if (layer) {
    m_rootLayer = layer;
    m_layerTreeView->setRootLayer(*m_rootLayer);
    m_layerTreeView->setVisible(page()->isPageVisible());
  } else {
    m_rootLayer = nullptr;
    // This means that we're transitioning to a new page. Suppress
    // commits until Blink generates invalidations so we don't
    // attempt to paint too early in the next page load.
    m_layerTreeView->setDeferCommits(true);
    m_layerTreeView->clearRootLayer();
    m_layerTreeView->clearViewportLayers();
    if (WebDevToolsAgentImpl* devTools = mainFrameDevToolsAgentImpl())
      devTools->rootLayerCleared();
  }
}

void WebViewImpl::invalidateRect(const IntRect& rect) {
  if (m_layerTreeView) {
    updateLayerTreeViewport();
  } else if (m_client) {
    // This is only for WebViewPlugin.
    m_client->widgetClient()->didInvalidateRect(rect);
  }
}

PaintLayerCompositor* WebViewImpl::compositor() const {
  WebLocalFrameImpl* frame = mainFrameImpl();
  if (!frame)
    return nullptr;

  Document* document = frame->frame()->document();
  if (!document || document->layoutViewItem().isNull())
    return nullptr;

  return document->layoutViewItem().compositor();
}

GraphicsLayer* WebViewImpl::rootGraphicsLayer() {
  return m_rootGraphicsLayer;
}

void WebViewImpl::scheduleAnimationForWidget() {
  if (m_layerTreeView) {
    m_layerTreeView->setNeedsBeginFrame();
    return;
  }
  if (m_client)
    m_client->widgetClient()->scheduleAnimation();
}

void WebViewImpl::attachCompositorAnimationTimeline(
    CompositorAnimationTimeline* timeline) {
  if (m_layerTreeView)
    m_layerTreeView->attachCompositorAnimationTimeline(
        timeline->animationTimeline());
}

void WebViewImpl::detachCompositorAnimationTimeline(
    CompositorAnimationTimeline* timeline) {
  if (m_layerTreeView)
    m_layerTreeView->detachCompositorAnimationTimeline(
        timeline->animationTimeline());
}

void WebViewImpl::initializeLayerTreeView() {
  if (m_client) {
    m_client->initializeLayerTreeView();
    m_layerTreeView = m_client->widgetClient()->layerTreeView();
  }

  if (WebDevToolsAgentImpl* devTools = mainFrameDevToolsAgentImpl())
    devTools->layerTreeViewChanged(m_layerTreeView);

  m_page->settings().setAcceleratedCompositingEnabled(m_layerTreeView);
  if (m_layerTreeView)
    m_page->layerTreeViewInitialized(*m_layerTreeView);

  // FIXME: only unittests, click to play, Android printing, and printing (for
  // headers and footers) make this assert necessary. We should make them not
  // hit this code and then delete allowsBrokenNullLayerTreeView.
  DCHECK(m_layerTreeView || !m_client ||
         m_client->widgetClient()->allowsBrokenNullLayerTreeView());

  if (Platform::current()->isThreadedAnimationEnabled() && m_layerTreeView) {
    m_linkHighlightsTimeline = CompositorAnimationTimeline::create();
    attachCompositorAnimationTimeline(m_linkHighlightsTimeline.get());
  }
}

void WebViewImpl::applyViewportDeltas(
    const WebFloatSize& visualViewportDelta,
    // TODO(bokan): This parameter is to be removed but requires adjusting many
    // callsites.
    const WebFloatSize&,
    const WebFloatSize& elasticOverscrollDelta,
    float pageScaleDelta,
    float browserControlsShownRatioDelta) {
  VisualViewport& visualViewport = page()->frameHost().visualViewport();

  // Store the desired offsets the visual viewport before setting the top
  // controls ratio since doing so will change the bounds and move the
  // viewports to keep the offsets valid. The compositor may have already
  // done that so we don't want to double apply the deltas here.
  FloatPoint visualViewportOffset = visualViewport.visibleRect().location();
  visualViewportOffset.move(visualViewportDelta.width,
                            visualViewportDelta.height);

  browserControls().setShownRatio(browserControls().shownRatio() +
                                  browserControlsShownRatioDelta);

  setPageScaleFactorAndLocation(pageScaleFactor() * pageScaleDelta,
                                visualViewportOffset);

  if (pageScaleDelta != 1) {
    m_doubleTapZoomPending = false;
    visualViewport.userDidChangeScale();
  }

  m_elasticOverscroll += elasticOverscrollDelta;

  if (mainFrameImpl() && mainFrameImpl()->frameView())
    mainFrameImpl()->frameView()->didUpdateElasticOverscroll();
}

void WebViewImpl::updateLayerTreeViewport() {
  if (!page() || !m_layerTreeView)
    return;

  m_layerTreeView->setPageScaleFactorAndLimits(
      pageScaleFactor(), minimumPageScaleFactor(), maximumPageScaleFactor());
}

void WebViewImpl::updateLayerTreeBackgroundColor() {
  if (!m_layerTreeView)
    return;

  m_layerTreeView->setBackgroundColor(alphaChannel(m_backgroundColorOverride)
                                          ? m_backgroundColorOverride
                                          : backgroundColor());
}

void WebViewImpl::updateLayerTreeDeviceScaleFactor() {
  DCHECK(page());
  DCHECK(m_layerTreeView);

  float deviceScaleFactor = m_compositorDeviceScaleFactorOverride
                                ? m_compositorDeviceScaleFactorOverride
                                : page()->deviceScaleFactor();
  m_layerTreeView->setDeviceScaleFactor(deviceScaleFactor);
}

void WebViewImpl::updateDeviceEmulationTransform() {
  if (!m_visualViewportContainerLayer)
    return;

  // When the device emulation transform is updated, to avoid incorrect
  // scales and fuzzy raster from the compositor, force all content to
  // pick ideal raster scales.
  m_visualViewportContainerLayer->setTransform(m_deviceEmulationTransform);
  m_layerTreeView->forceRecalculateRasterScales();
}

bool WebViewImpl::detectContentOnTouch(
    const GestureEventWithHitTestResults& targetedEvent) {
  if (!m_page->mainFrame()->isLocalFrame())
    return false;

  // Need a local copy of the hit test as
  // setToShadowHostIfInUserAgentShadowRoot() will modify it.
  HitTestResult touchHit = targetedEvent.hitTestResult();
  touchHit.setToShadowHostIfInUserAgentShadowRoot();

  if (touchHit.isContentEditable())
    return false;

  Node* node = touchHit.innerNode();
  if (!node || !node->isTextNode())
    return false;

  // Ignore when tapping on links or nodes listening to click events, unless
  // the click event is on the body element, in which case it's unlikely that
  // the original node itself was intended to be clickable.
  for (; node && !isHTMLBodyElement(*node);
       node = LayoutTreeBuilderTraversal::parent(*node)) {
    if (node->isLink() || node->willRespondToTouchEvents() ||
        node->willRespondToMouseClickEvents())
      return false;
  }

  WebURL intent = m_client->detectContentIntentAt(touchHit);
  if (!intent.isValid())
    return false;

  // This code is called directly after hit test code, with no user code
  // running in between, thus it is assumed that the frame pointer is non-null.
  bool isMainFrame = node ? node->document().frame()->isMainFrame() : true;
  m_client->scheduleContentIntent(intent, isMainFrame);
  return true;
}

WebViewScheduler* WebViewImpl::scheduler() const {
  return m_scheduler.get();
}

void WebViewImpl::setVisibilityState(WebPageVisibilityState visibilityState,
                                     bool isInitialState) {
  DCHECK(visibilityState == WebPageVisibilityStateVisible ||
         visibilityState == WebPageVisibilityStateHidden ||
         visibilityState == WebPageVisibilityStatePrerender);

  if (page())
    m_page->setVisibilityState(
        static_cast<PageVisibilityState>(static_cast<int>(visibilityState)),
        isInitialState);

  bool visible = visibilityState == WebPageVisibilityStateVisible;
  if (m_layerTreeView && !m_overrideCompositorVisibility)
    m_layerTreeView->setVisible(visible);
  m_scheduler->setPageVisible(visible);
}

void WebViewImpl::setCompositorVisibility(bool isVisible) {
  if (!isVisible)
    m_overrideCompositorVisibility = true;
  else
    m_overrideCompositorVisibility = false;
  if (m_layerTreeView)
    m_layerTreeView->setVisible(isVisible);
}

void WebViewImpl::pointerLockMouseEvent(const WebInputEvent& event) {
  std::unique_ptr<UserGestureIndicator> gestureIndicator;
  AtomicString eventType;
  switch (event.type) {
    case WebInputEvent::MouseDown:
      eventType = EventTypeNames::mousedown;
      if (!page() || !page()->pointerLockController().element())
        break;
      gestureIndicator =
          wrapUnique(new UserGestureIndicator(DocumentUserGestureToken::create(
              &page()->pointerLockController().element()->document(),
              UserGestureToken::NewGesture)));
      m_pointerLockGestureToken = gestureIndicator->currentToken();
      break;
    case WebInputEvent::MouseUp:
      eventType = EventTypeNames::mouseup;
      gestureIndicator = wrapUnique(
          new UserGestureIndicator(m_pointerLockGestureToken.release()));
      break;
    case WebInputEvent::MouseMove:
      eventType = EventTypeNames::mousemove;
      break;
    default:
      NOTREACHED();
  }

  const WebMouseEvent& mouseEvent = static_cast<const WebMouseEvent&>(event);

  if (page())
    page()->pointerLockController().dispatchLockedMouseEvent(
        PlatformMouseEventBuilder(mainFrameImpl()->frameView(), mouseEvent),
        eventType);
}

void WebViewImpl::forceNextWebGLContextCreationToFail() {
  WebGLRenderingContext::forceNextWebGLContextCreationToFail();
}

void WebViewImpl::forceNextDrawingBufferCreationToFail() {
  DrawingBuffer::forceNextDrawingBufferCreationToFail();
}

CompositorProxyClient* WebViewImpl::createCompositorProxyClient() {
  if (!m_mutator) {
    std::unique_ptr<CompositorMutatorClient> mutatorClient =
        CompositorMutatorImpl::createClient();
    m_mutator = static_cast<CompositorMutatorImpl*>(mutatorClient->mutator());
    m_layerTreeView->setMutatorClient(std::move(mutatorClient));
  }
  return new CompositorProxyClientImpl(m_mutator);
}

void WebViewImpl::updatePageOverlays() {
  if (m_pageColorOverlay)
    m_pageColorOverlay->update();
  if (InspectorOverlay* overlay = inspectorOverlay()) {
    PageOverlay* inspectorPageOverlay = overlay->pageOverlay();
    if (inspectorPageOverlay)
      inspectorPageOverlay->update();
  }
}

float WebViewImpl::deviceScaleFactor() const {
  // TODO(oshima): Investigate if this should return the ScreenInfo's scale
  // factor rather than page's scale factor, which can be 1 in use-zoom-for-dsf
  // mode.
  if (!page())
    return 1;

  return page()->deviceScaleFactor();
}

LocalFrame* WebViewImpl::focusedLocalFrameInWidget() const {
  if (!mainFrameImpl())
    return nullptr;

  LocalFrame* focusedFrame = toLocalFrame(focusedCoreFrame());
  if (focusedFrame->localFrameRoot() != mainFrameImpl()->frame())
    return nullptr;
  return focusedFrame;
}

LocalFrame* WebViewImpl::focusedLocalFrameAvailableForIme() const {
  return m_imeAcceptEvents ? focusedLocalFrameInWidget() : nullptr;
}

}  // namespace blink
