blob: cccea94587708ebc12d88a5465241f4087c5c3ed [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/frame/VisualViewport.h"
#include "core/dom/DOMNodeIds.h"
#include "core/frame/FrameHost.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/PageScaleConstraints.h"
#include "core/frame/PageScaleConstraintsSet.h"
#include "core/frame/RootFrameViewport.h"
#include "core/frame/Settings.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/layout/TextAutosizer.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/loader/FrameLoaderClient.h"
#include "core/page/ChromeClient.h"
#include "core/page/Page.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "platform/Histogram.h"
#include "platform/geometry/DoubleRect.h"
#include "platform/geometry/FloatSize.h"
#include "platform/graphics/CompositorMutableProperties.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/scroll/Scrollbar.h"
#include "platform/scroll/ScrollbarThemeOverlay.h"
#include "platform/tracing/TraceEvent.h"
#include "public/platform/WebCompositorSupport.h"
#include "public/platform/WebScrollbar.h"
#include "public/platform/WebScrollbarLayer.h"
#include <memory>
namespace blink {
VisualViewport::VisualViewport(FrameHost& owner)
: m_frameHost(&owner),
m_scale(1),
m_topControlsAdjustment(0),
m_maxPageScale(-1),
m_trackPinchZoomStatsForPage(false) {
reset();
}
VisualViewport::~VisualViewport() {
sendUMAMetrics();
}
DEFINE_TRACE(VisualViewport) {
visitor->trace(m_frameHost);
ScrollableArea::trace(visitor);
}
void VisualViewport::updateStyleAndLayoutIgnorePendingStylesheets() {
if (!mainFrame())
return;
if (Document* document = mainFrame()->document())
document->updateStyleAndLayoutIgnorePendingStylesheets();
}
void VisualViewport::enqueueScrollEvent() {
if (!RuntimeEnabledFeatures::visualViewportAPIEnabled())
return;
if (Document* document = mainFrame()->document())
document->enqueueVisualViewportScrollEvent();
}
void VisualViewport::enqueueResizeEvent() {
if (!RuntimeEnabledFeatures::visualViewportAPIEnabled())
return;
if (Document* document = mainFrame()->document())
document->enqueueVisualViewportResizeEvent();
}
void VisualViewport::setSize(const IntSize& size) {
if (m_size == size)
return;
TRACE_EVENT2("blink", "VisualViewport::setSize", "width", size.width(),
"height", size.height());
bool widthDidChange = size.width() != m_size.width();
m_size = size;
if (m_innerViewportContainerLayer) {
m_innerViewportContainerLayer->setSize(FloatSize(m_size));
// Need to re-compute sizes for the overlay scrollbars.
initializeScrollbars();
}
if (!mainFrame())
return;
enqueueResizeEvent();
bool autosizerNeedsUpdating =
widthDidChange && mainFrame()->settings() &&
mainFrame()->settings()->textAutosizingEnabled();
if (autosizerNeedsUpdating) {
// This needs to happen after setting the m_size member since it'll be read
// in the update call.
if (TextAutosizer* textAutosizer = mainFrame()->document()->textAutosizer())
textAutosizer->updatePageInfoInAllFrames();
}
}
void VisualViewport::reset() {
setScaleAndLocation(1, FloatPoint());
}
void VisualViewport::mainFrameDidChangeSize() {
TRACE_EVENT0("blink", "VisualViewport::mainFrameDidChangeSize");
// In unit tests we may not have initialized the layer tree.
if (m_innerViewportScrollLayer)
m_innerViewportScrollLayer->setSize(FloatSize(contentsSize()));
clampToBoundaries();
}
FloatSize VisualViewport::visibleSize() const {
FloatSize scaledSize(m_size);
scaledSize.expand(0, m_topControlsAdjustment);
scaledSize.scale(1 / m_scale);
return scaledSize;
}
FloatRect VisualViewport::visibleRect() const {
return FloatRect(location(), visibleSize());
}
FloatRect VisualViewport::visibleRectInDocument() const {
if (!mainFrame() || !mainFrame()->view())
return FloatRect();
FloatPoint viewLocation = FloatPoint(
mainFrame()->view()->getScrollableArea()->scrollPositionDouble());
return FloatRect(viewLocation, visibleSize());
}
FloatRect VisualViewport::mainViewToViewportCSSPixels(
const FloatRect& rect) const {
// Note, this is in CSS Pixels so we don't apply scale.
FloatRect rectInViewport = rect;
rectInViewport.moveBy(-location());
return rectInViewport;
}
FloatPoint VisualViewport::viewportCSSPixelsToRootFrame(
const FloatPoint& point) const {
// Note, this is in CSS Pixels so we don't apply scale.
FloatPoint pointInRootFrame = point;
pointInRootFrame.moveBy(location());
return pointInRootFrame;
}
void VisualViewport::setLocation(const FloatPoint& newLocation) {
setScaleAndLocation(m_scale, newLocation);
}
void VisualViewport::move(const FloatPoint& delta) {
setLocation(m_offset + delta);
}
void VisualViewport::move(const FloatSize& delta) {
setLocation(m_offset + delta);
}
void VisualViewport::setScale(float scale) {
setScaleAndLocation(scale, m_offset);
}
double VisualViewport::scrollLeft() {
if (!mainFrame())
return 0;
updateStyleAndLayoutIgnorePendingStylesheets();
return adjustScrollForAbsoluteZoom(visibleRect().x(),
mainFrame()->pageZoomFactor());
}
double VisualViewport::scrollTop() {
if (!mainFrame())
return 0;
updateStyleAndLayoutIgnorePendingStylesheets();
return adjustScrollForAbsoluteZoom(visibleRect().y(),
mainFrame()->pageZoomFactor());
}
double VisualViewport::clientWidth() {
if (!mainFrame())
return 0;
updateStyleAndLayoutIgnorePendingStylesheets();
double width = adjustScrollForAbsoluteZoom(visibleSize().width(),
mainFrame()->pageZoomFactor());
return width - mainFrame()->view()->verticalScrollbarWidth() / m_scale;
}
double VisualViewport::clientHeight() {
if (!mainFrame())
return 0;
updateStyleAndLayoutIgnorePendingStylesheets();
double height = adjustScrollForAbsoluteZoom(visibleSize().height(),
mainFrame()->pageZoomFactor());
return height - mainFrame()->view()->horizontalScrollbarHeight() / m_scale;
}
double VisualViewport::pageScale() {
updateStyleAndLayoutIgnorePendingStylesheets();
return m_scale;
}
void VisualViewport::setScaleAndLocation(float scale,
const FloatPoint& location) {
if (didSetScaleOrLocation(scale, location))
notifyRootFrameViewport();
}
bool VisualViewport::didSetScaleOrLocation(float scale,
const FloatPoint& location) {
if (!mainFrame())
return false;
bool valuesChanged = false;
if (scale != m_scale) {
m_scale = scale;
valuesChanged = true;
frameHost().chromeClient().pageScaleFactorChanged();
enqueueResizeEvent();
}
FloatPoint clampedOffset(clampOffsetToBoundaries(location));
if (clampedOffset != m_offset) {
m_offset = clampedOffset;
scrollAnimator().setCurrentPosition(m_offset);
// SVG runs with accelerated compositing disabled so no
// ScrollingCoordinator.
if (ScrollingCoordinator* coordinator =
frameHost().page().scrollingCoordinator())
coordinator->scrollableAreaScrollLayerDidChange(this);
if (!frameHost().settings().inertVisualViewport()) {
if (Document* document = mainFrame()->document())
document->enqueueScrollEventForNode(document);
}
enqueueScrollEvent();
mainFrame()->view()->didChangeScrollOffset();
valuesChanged = true;
}
if (!valuesChanged)
return false;
InspectorInstrumentation::didUpdateLayout(mainFrame());
mainFrame()->loader().saveScrollState();
clampToBoundaries();
return true;
}
bool VisualViewport::magnifyScaleAroundAnchor(float magnifyDelta,
const FloatPoint& anchor) {
const float oldPageScale = scale();
const float newPageScale =
frameHost().chromeClient().clampPageScaleFactorToLimits(magnifyDelta *
oldPageScale);
if (newPageScale == oldPageScale)
return false;
if (!mainFrame() || !mainFrame()->view())
return false;
// Keep the center-of-pinch anchor in a stable position over the course
// of the magnify.
FloatPoint anchorAtOldScale = anchor.scaledBy(1.f / oldPageScale);
FloatPoint anchorAtNewScale = anchor.scaledBy(1.f / newPageScale);
FloatSize anchorDelta = anchorAtOldScale - anchorAtNewScale;
// First try to use the anchor's delta to scroll the FrameView.
FloatSize anchorDeltaUnusedByScroll = anchorDelta;
// Manually bubble any remaining anchor delta up to the visual viewport.
FloatPoint newLocation(location() + anchorDeltaUnusedByScroll);
setScaleAndLocation(newPageScale, newLocation);
return true;
}
// Modifies the top of the graphics layer tree to add layers needed to support
// the inner/outer viewport fixed-position model for pinch zoom. When finished,
// the tree will look like this (with * denoting added layers):
//
// *rootTransformLayer
// +- *innerViewportContainerLayer (fixed pos container)
// +- *overscrollElasticityLayer
// | +- *pageScaleLayer
// | +- *innerViewportScrollLayer
// | +-- overflowControlsHostLayer (root layer)
// | | [ owned by PaintLayerCompositor ]
// | +-- outerViewportContainerLayer (fixed pos container)
// | | [frame container layer in PaintLayerCompositor]
// | | +-- outerViewportScrollLayer
// | | | [frame scroll layer in PaintLayerCompositor]
// | | +-- content layers ...
// +- *PageOverlay for InspectorOverlay
// +- *PageOverlay for ColorOverlay
// +- horizontalScrollbarLayer [ owned by PaintLayerCompositor ]
// +- verticalScrollbarLayer [ owned by PaintLayerCompositor ]
// +- scroll corner (non-overlay only) [ owned by PaintLayerCompositor ]
//
void VisualViewport::attachToLayerTree(GraphicsLayer* currentLayerTreeRoot) {
TRACE_EVENT1("blink", "VisualViewport::attachToLayerTree",
"currentLayerTreeRoot", (bool)currentLayerTreeRoot);
if (!currentLayerTreeRoot) {
if (m_innerViewportScrollLayer)
m_innerViewportScrollLayer->removeAllChildren();
return;
}
if (currentLayerTreeRoot->parent() &&
currentLayerTreeRoot->parent() == m_innerViewportScrollLayer.get())
return;
if (!m_innerViewportScrollLayer) {
ASSERT(!m_overlayScrollbarHorizontal && !m_overlayScrollbarVertical &&
!m_overscrollElasticityLayer && !m_pageScaleLayer &&
!m_innerViewportContainerLayer);
// FIXME: The root transform layer should only be created on demand.
m_rootTransformLayer = GraphicsLayer::create(this);
m_innerViewportContainerLayer = GraphicsLayer::create(this);
m_overscrollElasticityLayer = GraphicsLayer::create(this);
m_pageScaleLayer = GraphicsLayer::create(this);
m_innerViewportScrollLayer = GraphicsLayer::create(this);
m_overlayScrollbarHorizontal = GraphicsLayer::create(this);
m_overlayScrollbarVertical = GraphicsLayer::create(this);
ScrollingCoordinator* coordinator =
frameHost().page().scrollingCoordinator();
ASSERT(coordinator);
coordinator->setLayerIsContainerForFixedPositionLayers(
m_innerViewportScrollLayer.get(), true);
// Set masks to bounds so the compositor doesn't clobber a manually
// set inner viewport container layer size.
m_innerViewportContainerLayer->setMasksToBounds(
frameHost().settings().mainFrameClipsContent());
m_innerViewportContainerLayer->setSize(FloatSize(m_size));
m_innerViewportScrollLayer->platformLayer()->setScrollClipLayer(
m_innerViewportContainerLayer->platformLayer());
m_innerViewportScrollLayer->platformLayer()->setUserScrollable(true, true);
if (mainFrame()) {
if (Document* document = mainFrame()->document())
m_innerViewportScrollLayer->setElementId(createCompositorElementId(
DOMNodeIds::idForNode(document), CompositorSubElementId::Scroll));
}
m_rootTransformLayer->addChild(m_innerViewportContainerLayer.get());
m_innerViewportContainerLayer->addChild(m_overscrollElasticityLayer.get());
m_overscrollElasticityLayer->addChild(m_pageScaleLayer.get());
m_pageScaleLayer->addChild(m_innerViewportScrollLayer.get());
// Ensure this class is set as the scroll layer's ScrollableArea.
coordinator->scrollableAreaScrollLayerDidChange(this);
initializeScrollbars();
}
m_innerViewportScrollLayer->removeAllChildren();
m_innerViewportScrollLayer->addChild(currentLayerTreeRoot);
}
void VisualViewport::initializeScrollbars() {
// Do nothing if not attached to layer tree yet - will initialize upon attach.
if (!m_innerViewportContainerLayer)
return;
if (visualViewportSuppliesScrollbars() &&
!frameHost().settings().hideScrollbars()) {
if (!m_overlayScrollbarHorizontal->parent())
m_innerViewportContainerLayer->addChild(
m_overlayScrollbarHorizontal.get());
if (!m_overlayScrollbarVertical->parent())
m_innerViewportContainerLayer->addChild(m_overlayScrollbarVertical.get());
} else {
m_overlayScrollbarHorizontal->removeFromParent();
m_overlayScrollbarVertical->removeFromParent();
}
setupScrollbar(WebScrollbar::Horizontal);
setupScrollbar(WebScrollbar::Vertical);
}
void VisualViewport::setupScrollbar(WebScrollbar::Orientation orientation) {
bool isHorizontal = orientation == WebScrollbar::Horizontal;
GraphicsLayer* scrollbarGraphicsLayer =
isHorizontal ? m_overlayScrollbarHorizontal.get()
: m_overlayScrollbarVertical.get();
std::unique_ptr<WebScrollbarLayer>& webScrollbarLayer =
isHorizontal ? m_webOverlayScrollbarHorizontal
: m_webOverlayScrollbarVertical;
ScrollbarThemeOverlay& theme = ScrollbarThemeOverlay::mobileTheme();
int thumbThickness = theme.thumbThickness();
int scrollbarThickness = theme.scrollbarThickness(RegularScrollbar);
int scrollbarMargin = theme.scrollbarMargin();
if (!webScrollbarLayer) {
ScrollingCoordinator* coordinator =
frameHost().page().scrollingCoordinator();
ASSERT(coordinator);
ScrollbarOrientation webcoreOrientation =
isHorizontal ? HorizontalScrollbar : VerticalScrollbar;
webScrollbarLayer = coordinator->createSolidColorScrollbarLayer(
webcoreOrientation, thumbThickness, scrollbarMargin, false);
// The compositor will control the scrollbar's visibility. Set to invisible
// by default so scrollbars don't show up in layout tests.
webScrollbarLayer->layer()->setOpacity(0);
scrollbarGraphicsLayer->setContentsToPlatformLayer(
webScrollbarLayer->layer());
scrollbarGraphicsLayer->setDrawsContent(false);
}
int xPosition = isHorizontal ? 0
: m_innerViewportContainerLayer->size().width() -
scrollbarThickness;
int yPosition =
isHorizontal
? m_innerViewportContainerLayer->size().height() - scrollbarThickness
: 0;
int width =
isHorizontal
? m_innerViewportContainerLayer->size().width() - scrollbarThickness
: scrollbarThickness;
int height = isHorizontal ? scrollbarThickness
: m_innerViewportContainerLayer->size().height() -
scrollbarThickness;
// Use the GraphicsLayer to position the scrollbars.
scrollbarGraphicsLayer->setPosition(IntPoint(xPosition, yPosition));
scrollbarGraphicsLayer->setSize(FloatSize(width, height));
scrollbarGraphicsLayer->setContentsRect(IntRect(0, 0, width, height));
}
void VisualViewport::setScrollLayerOnScrollbars(WebLayer* scrollLayer) const {
// TODO(bokan): This is currently done while registering viewport layers
// with the compositor but could it actually be done earlier, like in
// setupScrollbars? Then we wouldn't need this method.
m_webOverlayScrollbarHorizontal->setScrollLayer(scrollLayer);
m_webOverlayScrollbarVertical->setScrollLayer(scrollLayer);
}
bool VisualViewport::visualViewportSuppliesScrollbars() const {
return frameHost().settings().viewportEnabled();
}
bool VisualViewport::scrollAnimatorEnabled() const {
return frameHost().settings().scrollAnimatorEnabled();
}
HostWindow* VisualViewport::getHostWindow() const {
return &frameHost().chromeClient();
}
DoubleRect VisualViewport::visibleContentRectDouble(
IncludeScrollbarsInRect) const {
return visibleRect();
}
IntRect VisualViewport::visibleContentRect(
IncludeScrollbarsInRect scrollbarInclusion) const {
return enclosingIntRect(visibleContentRectDouble(scrollbarInclusion));
}
bool VisualViewport::shouldUseIntegerScrollOffset() const {
LocalFrame* frame = mainFrame();
if (frame && frame->settings() &&
!frame->settings()->preferCompositingToLCDTextEnabled())
return true;
return ScrollableArea::shouldUseIntegerScrollOffset();
}
void VisualViewport::setScrollPosition(const DoublePoint& scrollPoint,
ScrollType scrollType,
ScrollBehavior scrollBehavior) {
// We clamp the position here, because the ScrollAnimator may otherwise be
// set to a non-clamped position by ScrollableArea::setScrollPosition,
// which may lead to incorrect scrolling behavior in RootFrameViewport down
// the line.
// TODO(eseckler): Solve this instead by ensuring that ScrollableArea and
// ScrollAnimator are kept in sync. This requires that ScrollableArea always
// stores fractional offsets and that truncation happens elsewhere, see
// crbug.com/626315.
DoublePoint newScrollPosition = clampScrollPosition(scrollPoint);
ScrollableArea::setScrollPosition(newScrollPosition, scrollType,
scrollBehavior);
}
int VisualViewport::scrollSize(ScrollbarOrientation orientation) const {
IntSize scrollDimensions = maximumScrollPosition() - minimumScrollPosition();
return (orientation == HorizontalScrollbar) ? scrollDimensions.width()
: scrollDimensions.height();
}
IntPoint VisualViewport::minimumScrollPosition() const {
return IntPoint();
}
IntPoint VisualViewport::maximumScrollPosition() const {
return flooredIntPoint(maximumScrollPositionDouble());
}
DoublePoint VisualViewport::maximumScrollPositionDouble() const {
if (!mainFrame())
return IntPoint();
// TODO(bokan): We probably shouldn't be storing the bounds in a float.
// crbug.com/470718.
FloatSize frameViewSize(contentsSize());
if (m_topControlsAdjustment) {
float minScale =
frameHost().pageScaleConstraintsSet().finalConstraints().minimumScale;
frameViewSize.expand(0, m_topControlsAdjustment / minScale);
}
frameViewSize.scale(m_scale);
frameViewSize = FloatSize(flooredIntSize(frameViewSize));
FloatSize viewportSize(m_size);
viewportSize.expand(0, ceilf(m_topControlsAdjustment));
FloatSize maxPosition = frameViewSize - viewportSize;
maxPosition.scale(1 / m_scale);
return DoublePoint(maxPosition);
}
IntPoint VisualViewport::clampDocumentOffsetAtScale(const IntPoint& offset,
float scale) {
if (!mainFrame() || !mainFrame()->view())
return IntPoint();
FrameView* view = mainFrame()->view();
FloatSize scaledSize(m_size);
scaledSize.scale(1 / scale);
IntPoint visualViewportMax =
flooredIntPoint(FloatSize(contentsSize()) - scaledSize);
IntPoint max = view->maximumScrollPosition() + visualViewportMax;
IntPoint min =
view->minimumScrollPosition(); // VisualViewportMin should be (0, 0)
IntPoint clamped = offset;
clamped = clamped.shrunkTo(max);
clamped = clamped.expandedTo(min);
return clamped;
}
void VisualViewport::setTopControlsAdjustment(float adjustment) {
m_topControlsAdjustment = adjustment;
}
IntRect VisualViewport::scrollableAreaBoundingBox() const {
// This method should return the bounding box in the parent view's coordinate
// space; however, VisualViewport technically isn't a child of any Frames.
// Nonetheless, the VisualViewport always occupies the entire main frame so
// just return that.
LocalFrame* frame = mainFrame();
if (!frame || !frame->view())
return IntRect();
return frame->view()->frameRect();
}
IntSize VisualViewport::contentsSize() const {
LocalFrame* frame = mainFrame();
if (!frame || !frame->view())
return IntSize();
return frame->view()->visibleContentRect(IncludeScrollbars).size();
}
void VisualViewport::setScrollOffset(const DoublePoint& offset,
ScrollType scrollType) {
if (didSetScaleOrLocation(m_scale, toFloatPoint(offset)) &&
scrollType != AnchoringScroll)
notifyRootFrameViewport();
}
GraphicsLayer* VisualViewport::layerForContainer() const {
return m_innerViewportContainerLayer.get();
}
GraphicsLayer* VisualViewport::layerForScrolling() const {
return m_innerViewportScrollLayer.get();
}
GraphicsLayer* VisualViewport::layerForHorizontalScrollbar() const {
return m_overlayScrollbarHorizontal.get();
}
GraphicsLayer* VisualViewport::layerForVerticalScrollbar() const {
return m_overlayScrollbarVertical.get();
}
IntRect VisualViewport::computeInterestRect(const GraphicsLayer*,
const IntRect&) const {
return IntRect();
}
void VisualViewport::paintContents(const GraphicsLayer*,
GraphicsContext&,
GraphicsLayerPaintingPhase,
const IntRect&) const {}
LocalFrame* VisualViewport::mainFrame() const {
return frameHost().page().mainFrame() &&
frameHost().page().mainFrame()->isLocalFrame()
? frameHost().page().deprecatedLocalMainFrame()
: 0;
}
Widget* VisualViewport::getWidget() {
return mainFrame()->view();
}
FloatPoint VisualViewport::clampOffsetToBoundaries(const FloatPoint& offset) {
FloatPoint clampedOffset(offset);
clampedOffset =
clampedOffset.shrunkTo(FloatPoint(maximumScrollPositionDouble()));
clampedOffset =
clampedOffset.expandedTo(FloatPoint(minimumScrollPositionDouble()));
return clampedOffset;
}
void VisualViewport::clampToBoundaries() {
setLocation(m_offset);
}
FloatRect VisualViewport::viewportToRootFrame(
const FloatRect& rectInViewport) const {
FloatRect rectInRootFrame = rectInViewport;
rectInRootFrame.scale(1 / scale());
rectInRootFrame.moveBy(location());
return rectInRootFrame;
}
IntRect VisualViewport::viewportToRootFrame(
const IntRect& rectInViewport) const {
// FIXME: How to snap to pixels?
return enclosingIntRect(viewportToRootFrame(FloatRect(rectInViewport)));
}
FloatRect VisualViewport::rootFrameToViewport(
const FloatRect& rectInRootFrame) const {
FloatRect rectInViewport = rectInRootFrame;
rectInViewport.moveBy(-location());
rectInViewport.scale(scale());
return rectInViewport;
}
IntRect VisualViewport::rootFrameToViewport(
const IntRect& rectInRootFrame) const {
// FIXME: How to snap to pixels?
return enclosingIntRect(rootFrameToViewport(FloatRect(rectInRootFrame)));
}
FloatPoint VisualViewport::viewportToRootFrame(
const FloatPoint& pointInViewport) const {
FloatPoint pointInRootFrame = pointInViewport;
pointInRootFrame.scale(1 / scale(), 1 / scale());
pointInRootFrame.moveBy(location());
return pointInRootFrame;
}
FloatPoint VisualViewport::rootFrameToViewport(
const FloatPoint& pointInRootFrame) const {
FloatPoint pointInViewport = pointInRootFrame;
pointInViewport.moveBy(-location());
pointInViewport.scale(scale(), scale());
return pointInViewport;
}
IntPoint VisualViewport::viewportToRootFrame(
const IntPoint& pointInViewport) const {
// FIXME: How to snap to pixels?
return flooredIntPoint(
FloatPoint(viewportToRootFrame(FloatPoint(pointInViewport))));
}
IntPoint VisualViewport::rootFrameToViewport(
const IntPoint& pointInRootFrame) const {
// FIXME: How to snap to pixels?
return flooredIntPoint(
FloatPoint(rootFrameToViewport(FloatPoint(pointInRootFrame))));
}
void VisualViewport::startTrackingPinchStats() {
if (!mainFrame())
return;
Document* document = mainFrame()->document();
if (!document)
return;
if (!document->url().protocolIsInHTTPFamily())
return;
m_trackPinchZoomStatsForPage = !shouldDisableDesktopWorkarounds();
}
void VisualViewport::userDidChangeScale() {
if (!m_trackPinchZoomStatsForPage)
return;
m_maxPageScale = std::max(m_maxPageScale, m_scale);
}
void VisualViewport::sendUMAMetrics() {
if (m_trackPinchZoomStatsForPage) {
bool didScale = m_maxPageScale > 0;
DEFINE_STATIC_LOCAL(EnumerationHistogram, didScaleHistogram,
("Viewport.DidScalePage", 2));
didScaleHistogram.count(didScale ? 1 : 0);
if (didScale) {
int zoomPercentage = floor(m_maxPageScale * 100);
// See the PageScaleFactor enumeration in histograms.xml for the bucket
// ranges.
int bucket = floor(zoomPercentage / 25.f);
DEFINE_STATIC_LOCAL(EnumerationHistogram, maxScaleHistogram,
("Viewport.MaxPageScale", 21));
maxScaleHistogram.count(bucket);
}
}
m_maxPageScale = -1;
m_trackPinchZoomStatsForPage = false;
}
bool VisualViewport::shouldDisableDesktopWorkarounds() const {
if (!mainFrame() || !mainFrame()->view())
return false;
if (!mainFrame()->settings()->viewportEnabled())
return false;
// A document is considered adapted to small screen UAs if one of these holds:
// 1. The author specified viewport has a constrained width that is equal to
// the initial viewport width.
// 2. The author has disabled viewport zoom.
const PageScaleConstraints& constraints =
frameHost().pageScaleConstraintsSet().pageDefinedConstraints();
return mainFrame()->view()->layoutSize().width() == m_size.width() ||
(constraints.minimumScale == constraints.maximumScale &&
constraints.minimumScale != -1);
}
CompositorAnimationTimeline* VisualViewport::compositorAnimationTimeline()
const {
ScrollingCoordinator* c = frameHost().page().scrollingCoordinator();
return c ? c->compositorAnimationTimeline() : nullptr;
}
void VisualViewport::notifyRootFrameViewport() const {
if (!mainFrame() || !mainFrame()->view())
return;
RootFrameViewport* rootFrameViewport =
mainFrame()->view()->getRootFrameViewport();
if (!rootFrameViewport)
return;
rootFrameViewport->didUpdateVisualViewport();
}
String VisualViewport::debugName(const GraphicsLayer* graphicsLayer) const {
String name;
if (graphicsLayer == m_innerViewportContainerLayer.get()) {
name = "Inner Viewport Container Layer";
} else if (graphicsLayer == m_overscrollElasticityLayer.get()) {
name = "Overscroll Elasticity Layer";
} else if (graphicsLayer == m_pageScaleLayer.get()) {
name = "Page Scale Layer";
} else if (graphicsLayer == m_innerViewportScrollLayer.get()) {
name = "Inner Viewport Scroll Layer";
} else if (graphicsLayer == m_overlayScrollbarHorizontal.get()) {
name = "Overlay Scrollbar Horizontal Layer";
} else if (graphicsLayer == m_overlayScrollbarVertical.get()) {
name = "Overlay Scrollbar Vertical Layer";
} else if (graphicsLayer == m_rootTransformLayer.get()) {
name = "Root Transform Layer";
} else {
ASSERT_NOT_REACHED();
}
return name;
}
} // namespace blink