chromium/content/public/android/java/src/org/chromium/content/browser/RenderCoordinatesImpl.java

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.content.browser;

import org.chromium.base.ResettersForTesting;
import org.chromium.content.browser.webcontents.WebContentsImpl;
import org.chromium.content_public.browser.RenderCoordinates;
import org.chromium.content_public.browser.WebContents;

/**
 * Cached copy of all positions and scales (CSS-to-DIP-to-physical pixels)
 * reported from the renderer.
 * Provides wrappers and a utility class to help with coordinate transforms on the client side.
 * Provides the internally-visible set of update methods.
 *
 * Unless stated otherwise, all coordinates are in CSS (document) coordinate space.
 */
public class RenderCoordinatesImpl implements RenderCoordinates {
    private static RenderCoordinatesImpl sInstanceForTesting;

    // Scroll offset from the native in CSS.
    private float mScrollXCss;
    private float mScrollYCss;

    // Content size from native in CSS.
    private float mContentWidthCss;
    private float mContentHeightCss;

    // Last-frame render-reported viewport size in CSS.
    private float mLastFrameViewportWidthCss;
    private float mLastFrameViewportHeightCss;

    // Cached page scale factor from native.
    private float mPageScaleFactor = 1.0f;
    private float mMinPageScaleFactor = 1.0f;
    private float mMaxPageScaleFactor = 1.0f;

    // Cached device density.
    private float mDeviceScaleFactor = 1.0f;

    private float mTopContentOffsetYPix;

    public static RenderCoordinatesImpl fromWebContents(WebContents webContents) {
        if (sInstanceForTesting != null) return sInstanceForTesting;
        return ((WebContentsImpl) webContents).getRenderCoordinates();
    }

    // TODO(crbug.com/40850475): Mocking |#fromWebContents()| may be a better option, when
    // available.
    public static void setInstanceForTesting(RenderCoordinatesImpl instance) {
        sInstanceForTesting = instance;
        ResettersForTesting.register(() -> sInstanceForTesting = null);
    }

    // Internally-visible set of update methods (used by WebContentsImpl).
    public void reset() {
        mScrollXCss = mScrollYCss = 0;
        mPageScaleFactor = 1.0f;
    }

    @Override
    public int getScrollXPixInt() {
        return (int) Math.floor(getScrollXPix());
    }

    @Override
    public int getScrollYPixInt() {
        return (int) Math.floor(getScrollYPix());
    }

    @Override
    public int getContentOffsetYPixInt() {
        return (int) Math.floor(getContentOffsetYPix());
    }

    @Override
    public int getContentWidthPixInt() {
        return (int) Math.ceil(getContentWidthPix());
    }

    @Override
    public int getContentHeightPixInt() {
        return (int) Math.ceil(getContentHeightPix());
    }

    @Override
    public int getLastFrameViewportWidthPixInt() {
        return (int) Math.ceil(getLastFrameViewportWidthPix());
    }

    @Override
    public int getLastFrameViewportHeightPixInt() {
        return (int) Math.ceil(getLastFrameViewportHeightPix());
    }

    @Override
    public int getMaxVerticalScrollPixInt() {
        return (int) Math.floor(getMaxVerticalScrollPix());
    }

    @Override
    public int getMaxHorizontalScrollPixInt() {
        return (int) Math.floor(getMaxHorizontalScrollPix());
    }

    void updateContentSizeCss(float contentWidthCss, float contentHeightCss) {
        mContentWidthCss = contentWidthCss;
        mContentHeightCss = contentHeightCss;
    }

    public void setDeviceScaleFactor(float dipScale) {
        mDeviceScaleFactor = dipScale;
    }

    public void updateFrameInfo(
            float contentWidthCss,
            float contentHeightCss,
            float viewportWidthCss,
            float viewportHeightCss,
            float minPageScaleFactor,
            float maxPageScaleFactor,
            float contentOffsetYPix) {
        mMinPageScaleFactor = minPageScaleFactor;
        mMaxPageScaleFactor = maxPageScaleFactor;
        mTopContentOffsetYPix = contentOffsetYPix;

        updateContentSizeCss(contentWidthCss, contentHeightCss);
        mLastFrameViewportWidthCss = viewportWidthCss;
        mLastFrameViewportHeightCss = viewportHeightCss;
    }

    public void updateScrollInfo(float pageScaleFactor, float scrollXCss, float scrollYCss) {
        mPageScaleFactor = pageScaleFactor;
        mScrollXCss = scrollXCss;
        mScrollYCss = scrollYCss;
    }

    /**
     * @return Horizontal scroll offset in CSS pixels.
     */
    public float getScrollX() {
        return mScrollXCss;
    }

    /**
     * @return Vertical scroll offset in CSS pixels.
     */
    public float getScrollY() {
        return mScrollYCss;
    }

    /**
     * @return Horizontal scroll offset in physical pixels.
     */
    public float getScrollXPix() {
        return fromLocalCssToPix(mScrollXCss);
    }

    /**
     * @return Vertical scroll offset in physical pixels.
     */
    public float getScrollYPix() {
        return fromLocalCssToPix(mScrollYCss);
    }

    /**
     * @return Width of the content in CSS pixels.
     */
    public float getContentWidthCss() {
        return mContentWidthCss;
    }

    /**
     * @return Height of the content in CSS pixels.
     */
    public float getContentHeightCss() {
        return mContentHeightCss;
    }

    /**
     * @return The Physical on-screen Y offset amount below the browser controls.
     */
    public float getContentOffsetYPix() {
        return mTopContentOffsetYPix;
    }

    /**
     * @return Current page scale factor (maps CSS pixels to DIP pixels).
     */
    @Override
    public float getPageScaleFactor() {
        return mPageScaleFactor;
    }

    /**
     * @return Current page scale factor (approx, integer).
     */
    @Override
    public int getPageScaleFactorInt() {
        return (int) Math.floor(mPageScaleFactor);
    }

    /**
     * @return Minimum page scale factor to be used with the content.
     */
    @Override
    public float getMinPageScaleFactor() {
        return mMinPageScaleFactor;
    }

    /**
     * @return Maximum page scale factor to be used with the content.
     */
    public float getMaxPageScaleFactor() {
        return mMaxPageScaleFactor;
    }

    /**
     * @return Current device scale factor (maps DIP pixels to physical pixels).
     */
    public float getDeviceScaleFactor() {
        return mDeviceScaleFactor;
    }

    /**
     * @return Local CSS converted to physical coordinates.
     */
    public float fromLocalCssToPix(float css) {
        return css * mPageScaleFactor * mDeviceScaleFactor;
    }

    // Private methods

    // Approximate width of the content in physical pixels.
    private float getContentWidthPix() {
        return fromLocalCssToPix(mContentWidthCss);
    }

    // Approximate height of the content in physical pixels.
    private float getContentHeightPix() {
        return fromLocalCssToPix(mContentHeightCss);
    }

    // Render-reported width of the viewport in physical pixels (approximate).
    private float getLastFrameViewportWidthPix() {
        return fromLocalCssToPix(mLastFrameViewportWidthCss);
    }

    // Render-reported height of the viewport in physical pixels (approximate).
    private float getLastFrameViewportHeightPix() {
        return fromLocalCssToPix(mLastFrameViewportHeightCss);
    }

    // Maximum possible horizontal scroll in physical pixels.
    private float getMaxHorizontalScrollPix() {
        return getContentWidthPix() - getLastFrameViewportWidthPix();
    }

    // Maximum possible vertical scroll in physical pixels.
    private float getMaxVerticalScrollPix() {
        return getContentHeightPix() - getLastFrameViewportHeightPix();
    }

    /**
     * @return whether the first frame info was passed in and cached. Rendered content
     *     area dimension, page scale factor, etc. is available if true.
     */
    public boolean frameInfoUpdatedForTesting() {
        return mContentWidthCss != 0.f || mContentHeightCss != 0.f;
    }
}