mobile/android/base/gfx/GeckoLayerClient.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/gfx/GeckoLayerClient.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1002 @@
     1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     1.5 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko.gfx;
    1.10 +
    1.11 +import org.mozilla.gecko.GeckoAppShell;
    1.12 +import org.mozilla.gecko.GeckoEvent;
    1.13 +import org.mozilla.gecko.gfx.LayerView.DrawListener;
    1.14 +import org.mozilla.gecko.Tab;
    1.15 +import org.mozilla.gecko.Tabs;
    1.16 +import org.mozilla.gecko.ZoomConstraints;
    1.17 +import org.mozilla.gecko.mozglue.RobocopTarget;
    1.18 +import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
    1.19 +import org.mozilla.gecko.EventDispatcher;
    1.20 +import org.mozilla.gecko.util.FloatUtils;
    1.21 +
    1.22 +import android.content.Context;
    1.23 +import android.graphics.PointF;
    1.24 +import android.graphics.RectF;
    1.25 +import android.os.SystemClock;
    1.26 +import android.util.DisplayMetrics;
    1.27 +import android.util.Log;
    1.28 +
    1.29 +import java.util.ArrayList;
    1.30 +import java.util.List;
    1.31 +
    1.32 +class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
    1.33 +{
    1.34 +    private static final String LOGTAG = "GeckoLayerClient";
    1.35 +
    1.36 +    private LayerRenderer mLayerRenderer;
    1.37 +    private boolean mLayerRendererInitialized;
    1.38 +
    1.39 +    private Context mContext;
    1.40 +    private IntSize mScreenSize;
    1.41 +    private IntSize mWindowSize;
    1.42 +    private DisplayPortMetrics mDisplayPort;
    1.43 +
    1.44 +    private boolean mRecordDrawTimes;
    1.45 +    private final DrawTimingQueue mDrawTimingQueue;
    1.46 +
    1.47 +    private VirtualLayer mRootLayer;
    1.48 +
    1.49 +    /* The Gecko viewport as per the UI thread. Must be touched only on the UI thread.
    1.50 +     * If any events being sent to Gecko that are relative to the Gecko viewport position,
    1.51 +     * they must (a) be relative to this viewport, and (b) be sent on the UI thread to
    1.52 +     * avoid races. As long as these two conditions are satisfied, and the events being
    1.53 +     * sent to Gecko are processed in FIFO order, the events will properly be relative
    1.54 +     * to the Gecko viewport position. Note that if Gecko updates its viewport independently,
    1.55 +     * we get notified synchronously and also update this on the UI thread.
    1.56 +     */
    1.57 +    private ImmutableViewportMetrics mGeckoViewport;
    1.58 +
    1.59 +    /*
    1.60 +     * The viewport metrics being used to draw the current frame. This is only
    1.61 +     * accessed by the compositor thread, and so needs no synchronisation.
    1.62 +     */
    1.63 +    private ImmutableViewportMetrics mFrameMetrics;
    1.64 +
    1.65 +    private List<DrawListener> mDrawListeners;
    1.66 +
    1.67 +    /* Used as temporaries by syncViewportInfo */
    1.68 +    private final ViewTransform mCurrentViewTransform;
    1.69 +    private final RectF mCurrentViewTransformMargins;
    1.70 +
    1.71 +    /* Used as the return value of progressiveUpdateCallback */
    1.72 +    private final ProgressiveUpdateData mProgressiveUpdateData;
    1.73 +    private DisplayPortMetrics mProgressiveUpdateDisplayPort;
    1.74 +    private boolean mLastProgressiveUpdateWasLowPrecision;
    1.75 +    private boolean mProgressiveUpdateWasInDanger;
    1.76 +
    1.77 +    private boolean mForceRedraw;
    1.78 +
    1.79 +    /* The current viewport metrics.
    1.80 +     * This is volatile so that we can read and write to it from different threads.
    1.81 +     * We avoid synchronization to make getting the viewport metrics from
    1.82 +     * the compositor as cheap as possible. The viewport is immutable so
    1.83 +     * we don't need to worry about anyone mutating it while we're reading from it.
    1.84 +     * Specifically:
    1.85 +     * 1) reading mViewportMetrics from any thread is fine without synchronization
    1.86 +     * 2) writing to mViewportMetrics requires synchronizing on the layer controller object
    1.87 +     * 3) whenver reading multiple fields from mViewportMetrics without synchronization (i.e. in
    1.88 +     *    case 1 above) you should always frist grab a local copy of the reference, and then use
    1.89 +     *    that because mViewportMetrics might get reassigned in between reading the different
    1.90 +     *    fields. */
    1.91 +    private volatile ImmutableViewportMetrics mViewportMetrics;
    1.92 +    private LayerView.OnMetricsChangedListener mViewportChangeListener;
    1.93 +
    1.94 +    private ZoomConstraints mZoomConstraints;
    1.95 +
    1.96 +    private boolean mGeckoIsReady;
    1.97 +
    1.98 +    private final PanZoomController mPanZoomController;
    1.99 +    private final LayerMarginsAnimator mMarginsAnimator;
   1.100 +    private LayerView mView;
   1.101 +
   1.102 +    /* This flag is true from the time that browser.js detects a first-paint is about to start,
   1.103 +     * to the time that we receive the first-paint composite notification from the compositor.
   1.104 +     * Note that there is a small race condition with this; if there are two paints that both
   1.105 +     * have the first-paint flag set, and the second paint happens concurrently with the
   1.106 +     * composite for the first paint, then this flag may be set to true prematurely. Fixing this
   1.107 +     * is possible but risky; see https://bugzilla.mozilla.org/show_bug.cgi?id=797615#c751
   1.108 +     */
   1.109 +    private volatile boolean mContentDocumentIsDisplayed;
   1.110 +
   1.111 +    public GeckoLayerClient(Context context, LayerView view, EventDispatcher eventDispatcher) {
   1.112 +        // we can fill these in with dummy values because they are always written
   1.113 +        // to before being read
   1.114 +        mContext = context;
   1.115 +        mScreenSize = new IntSize(0, 0);
   1.116 +        mWindowSize = new IntSize(0, 0);
   1.117 +        mDisplayPort = new DisplayPortMetrics();
   1.118 +        mRecordDrawTimes = true;
   1.119 +        mDrawTimingQueue = new DrawTimingQueue();
   1.120 +        mCurrentViewTransform = new ViewTransform(0, 0, 1);
   1.121 +        mCurrentViewTransformMargins = new RectF();
   1.122 +        mProgressiveUpdateData = new ProgressiveUpdateData();
   1.123 +        mProgressiveUpdateDisplayPort = new DisplayPortMetrics();
   1.124 +        mLastProgressiveUpdateWasLowPrecision = false;
   1.125 +        mProgressiveUpdateWasInDanger = false;
   1.126 +
   1.127 +        mForceRedraw = true;
   1.128 +        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
   1.129 +        mViewportMetrics = new ImmutableViewportMetrics(displayMetrics)
   1.130 +                           .setViewportSize(view.getWidth(), view.getHeight());
   1.131 +        mZoomConstraints = new ZoomConstraints(false);
   1.132 +
   1.133 +        Tab tab = Tabs.getInstance().getSelectedTab();
   1.134 +        if (tab != null) {
   1.135 +            mZoomConstraints = tab.getZoomConstraints();
   1.136 +            mViewportMetrics = mViewportMetrics.setIsRTL(tab.getIsRTL());
   1.137 +        }
   1.138 +
   1.139 +        mFrameMetrics = mViewportMetrics;
   1.140 +
   1.141 +        mDrawListeners = new ArrayList<DrawListener>();
   1.142 +        mPanZoomController = PanZoomController.Factory.create(this, view, eventDispatcher);
   1.143 +        mMarginsAnimator = new LayerMarginsAnimator(this, view);
   1.144 +        mView = view;
   1.145 +        mView.setListener(this);
   1.146 +        mContentDocumentIsDisplayed = true;
   1.147 +    }
   1.148 +
   1.149 +    public void setOverscrollHandler(final Overscroll listener) {
   1.150 +        mPanZoomController.setOverscrollHandler(listener);
   1.151 +    }
   1.152 +
   1.153 +    /** Attaches to root layer so that Gecko appears. */
   1.154 +    public void notifyGeckoReady() {
   1.155 +        mGeckoIsReady = true;
   1.156 +
   1.157 +        mRootLayer = new VirtualLayer(new IntSize(mView.getWidth(), mView.getHeight()));
   1.158 +        mLayerRenderer = mView.getRenderer();
   1.159 +
   1.160 +        sendResizeEventIfNecessary(true);
   1.161 +
   1.162 +        DisplayPortCalculator.initPrefs();
   1.163 +
   1.164 +        // Gecko being ready is one of the two conditions (along with having an available
   1.165 +        // surface) that cause us to create the compositor. So here, now that we know gecko
   1.166 +        // is ready, call updateCompositor() to see if we can actually do the creation.
   1.167 +        // This needs to run on the UI thread so that the surface validity can't change on
   1.168 +        // us while we're in the middle of creating the compositor.
   1.169 +        mView.post(new Runnable() {
   1.170 +            @Override
   1.171 +            public void run() {
   1.172 +                mView.getGLController().updateCompositor();
   1.173 +            }
   1.174 +        });
   1.175 +    }
   1.176 +
   1.177 +    public void destroy() {
   1.178 +        mPanZoomController.destroy();
   1.179 +        mMarginsAnimator.destroy();
   1.180 +        mDrawListeners.clear();
   1.181 +    }
   1.182 +
   1.183 +    /**
   1.184 +     * Returns true if this client is fine with performing a redraw operation or false if it
   1.185 +     * would prefer that the action didn't take place.
   1.186 +     */
   1.187 +    private boolean getRedrawHint() {
   1.188 +        if (mForceRedraw) {
   1.189 +            mForceRedraw = false;
   1.190 +            return true;
   1.191 +        }
   1.192 +
   1.193 +        if (!mPanZoomController.getRedrawHint()) {
   1.194 +            return false;
   1.195 +        }
   1.196 +
   1.197 +        return DisplayPortCalculator.aboutToCheckerboard(mViewportMetrics,
   1.198 +                mPanZoomController.getVelocityVector(), mDisplayPort);
   1.199 +    }
   1.200 +
   1.201 +    Layer getRoot() {
   1.202 +        return mGeckoIsReady ? mRootLayer : null;
   1.203 +    }
   1.204 +
   1.205 +    public LayerView getView() {
   1.206 +        return mView;
   1.207 +    }
   1.208 +
   1.209 +    public FloatSize getViewportSize() {
   1.210 +        return mViewportMetrics.getSize();
   1.211 +    }
   1.212 +
   1.213 +    /**
   1.214 +     * The view calls this function to indicate that the viewport changed size. It must hold the
   1.215 +     * monitor while calling it.
   1.216 +     *
   1.217 +     * TODO: Refactor this to use an interface. Expose that interface only to the view and not
   1.218 +     * to the layer client. That way, the layer client won't be tempted to call this, which might
   1.219 +     * result in an infinite loop.
   1.220 +     */
   1.221 +    void setViewportSize(int width, int height) {
   1.222 +        mViewportMetrics = mViewportMetrics.setViewportSize(width, height);
   1.223 +
   1.224 +        if (mGeckoIsReady) {
   1.225 +            // here we send gecko a resize message. The code in browser.js is responsible for
   1.226 +            // picking up on that resize event, modifying the viewport as necessary, and informing
   1.227 +            // us of the new viewport.
   1.228 +            sendResizeEventIfNecessary(true);
   1.229 +            // the following call also sends gecko a message, which will be processed after the resize
   1.230 +            // message above has updated the viewport. this message ensures that if we have just put
   1.231 +            // focus in a text field, we scroll the content so that the text field is in view.
   1.232 +            GeckoAppShell.viewSizeChanged();
   1.233 +        }
   1.234 +    }
   1.235 +
   1.236 +    PanZoomController getPanZoomController() {
   1.237 +        return mPanZoomController;
   1.238 +    }
   1.239 +
   1.240 +    LayerMarginsAnimator getLayerMarginsAnimator() {
   1.241 +        return mMarginsAnimator;
   1.242 +    }
   1.243 +
   1.244 +    /* Informs Gecko that the screen size has changed. */
   1.245 +    private void sendResizeEventIfNecessary(boolean force) {
   1.246 +        DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
   1.247 +
   1.248 +        IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
   1.249 +        IntSize newWindowSize = new IntSize(mView.getWidth(), mView.getHeight());
   1.250 +
   1.251 +        boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
   1.252 +        boolean windowSizeChanged = !mWindowSize.equals(newWindowSize);
   1.253 +
   1.254 +        if (!force && !screenSizeChanged && !windowSizeChanged) {
   1.255 +            return;
   1.256 +        }
   1.257 +
   1.258 +        mScreenSize = newScreenSize;
   1.259 +        mWindowSize = newWindowSize;
   1.260 +
   1.261 +        if (screenSizeChanged) {
   1.262 +            Log.d(LOGTAG, "Screen-size changed to " + mScreenSize);
   1.263 +        }
   1.264 +
   1.265 +        if (windowSizeChanged) {
   1.266 +            Log.d(LOGTAG, "Window-size changed to " + mWindowSize);
   1.267 +        }
   1.268 +
   1.269 +        GeckoEvent event = GeckoEvent.createSizeChangedEvent(mWindowSize.width, mWindowSize.height,
   1.270 +                                                             mScreenSize.width, mScreenSize.height);
   1.271 +        GeckoAppShell.sendEventToGecko(event);
   1.272 +        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Window:Resize", ""));
   1.273 +    }
   1.274 +
   1.275 +    /** Sets the current page rect. You must hold the monitor while calling this. */
   1.276 +    private void setPageRect(RectF rect, RectF cssRect) {
   1.277 +        // Since the "rect" is always just a multiple of "cssRect" we don't need to
   1.278 +        // check both; this function assumes that both "rect" and "cssRect" are relative
   1.279 +        // the zoom factor in mViewportMetrics.
   1.280 +        if (mViewportMetrics.getCssPageRect().equals(cssRect))
   1.281 +            return;
   1.282 +
   1.283 +        mViewportMetrics = mViewportMetrics.setPageRect(rect, cssRect);
   1.284 +
   1.285 +        // Page size is owned by the layer client, so no need to notify it of
   1.286 +        // this change.
   1.287 +
   1.288 +        post(new Runnable() {
   1.289 +            @Override
   1.290 +            public void run() {
   1.291 +                mPanZoomController.pageRectUpdated();
   1.292 +                mView.requestRender();
   1.293 +            }
   1.294 +        });
   1.295 +    }
   1.296 +
   1.297 +    /**
   1.298 +     * Derives content document fixed position margins/fixed layer margins from
   1.299 +     * the view margins in the given metrics object.
   1.300 +     */
   1.301 +    private void getFixedMargins(ImmutableViewportMetrics metrics, RectF fixedMargins) {
   1.302 +        fixedMargins.left = 0;
   1.303 +        fixedMargins.top = 0;
   1.304 +        fixedMargins.right = 0;
   1.305 +        fixedMargins.bottom = 0;
   1.306 +
   1.307 +        // The maximum margins are determined by the scrollable area of the page.
   1.308 +        float maxMarginWidth = Math.max(0, metrics.getPageWidth() - metrics.getWidthWithoutMargins());
   1.309 +        float maxMarginHeight = Math.max(0, metrics.getPageHeight() - metrics.getHeightWithoutMargins());
   1.310 +
   1.311 +        // If the margins can't fully hide, they're pinned on - in which case,
   1.312 +        // fixed margins should always be zero.
   1.313 +        if (maxMarginWidth < metrics.marginLeft + metrics.marginRight) {
   1.314 +          maxMarginWidth = 0;
   1.315 +        }
   1.316 +        if (maxMarginHeight < metrics.marginTop + metrics.marginBottom) {
   1.317 +          maxMarginHeight = 0;
   1.318 +        }
   1.319 +
   1.320 +        PointF offset = metrics.getMarginOffset();
   1.321 +        RectF overscroll = metrics.getOverscroll();
   1.322 +        if (offset.x >= 0) {
   1.323 +            fixedMargins.right = Math.max(0, Math.min(offset.x - overscroll.right, maxMarginWidth));
   1.324 +        } else {
   1.325 +            fixedMargins.left = Math.max(0, Math.min(-offset.x - overscroll.left, maxMarginWidth));
   1.326 +        }
   1.327 +        if (offset.y >= 0) {
   1.328 +            fixedMargins.bottom = Math.max(0, Math.min(offset.y - overscroll.bottom, maxMarginHeight));
   1.329 +        } else {
   1.330 +            fixedMargins.top = Math.max(0, Math.min(-offset.y - overscroll.top, maxMarginHeight));
   1.331 +        }
   1.332 +
   1.333 +        // Adjust for overscroll. If we're overscrolled on one side, add that
   1.334 +        // distance to the margins of the other side (limiting to the maximum
   1.335 +        // margin size calculated above).
   1.336 +        if (overscroll.left > 0) {
   1.337 +            fixedMargins.right = Math.min(maxMarginWidth - fixedMargins.left,
   1.338 +                                          fixedMargins.right + overscroll.left);
   1.339 +        } else if (overscroll.right > 0) {
   1.340 +            fixedMargins.left = Math.min(maxMarginWidth - fixedMargins.right,
   1.341 +                                         fixedMargins.left + overscroll.right);
   1.342 +        }
   1.343 +        if (overscroll.top > 0) {
   1.344 +            fixedMargins.bottom = Math.min(maxMarginHeight - fixedMargins.top,
   1.345 +                                           fixedMargins.bottom + overscroll.top);
   1.346 +        } else if (overscroll.bottom > 0) {
   1.347 +            fixedMargins.top = Math.min(maxMarginHeight - fixedMargins.bottom,
   1.348 +                                        fixedMargins.top + overscroll.bottom);
   1.349 +        }
   1.350 +    }
   1.351 +
   1.352 +    private void adjustViewport(DisplayPortMetrics displayPort) {
   1.353 +        ImmutableViewportMetrics metrics = getViewportMetrics();
   1.354 +        ImmutableViewportMetrics clampedMetrics = metrics.clamp();
   1.355 +
   1.356 +        RectF margins = new RectF();
   1.357 +        getFixedMargins(metrics, margins);
   1.358 +        clampedMetrics = clampedMetrics.setMargins(
   1.359 +            margins.left, margins.top, margins.right, margins.bottom);
   1.360 +
   1.361 +        if (displayPort == null) {
   1.362 +            displayPort = DisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector());
   1.363 +        }
   1.364 +
   1.365 +        mDisplayPort = displayPort;
   1.366 +        mGeckoViewport = clampedMetrics;
   1.367 +
   1.368 +        if (mRecordDrawTimes) {
   1.369 +            mDrawTimingQueue.add(displayPort);
   1.370 +        }
   1.371 +
   1.372 +        GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(clampedMetrics, displayPort));
   1.373 +    }
   1.374 +
   1.375 +    /** Aborts any pan/zoom animation that is currently in progress. */
   1.376 +    private void abortPanZoomAnimation() {
   1.377 +        if (mPanZoomController != null) {
   1.378 +            post(new Runnable() {
   1.379 +                @Override
   1.380 +                public void run() {
   1.381 +                    mPanZoomController.abortAnimation();
   1.382 +                }
   1.383 +            });
   1.384 +        }
   1.385 +    }
   1.386 +
   1.387 +    /**
   1.388 +     * The different types of Viewport messages handled. All viewport events
   1.389 +     * expect a display-port to be returned, but can handle one not being
   1.390 +     * returned.
   1.391 +     */
   1.392 +    private enum ViewportMessageType {
   1.393 +        UPDATE,       // The viewport has changed and should be entirely updated
   1.394 +        PAGE_SIZE     // The viewport's page-size has changed
   1.395 +    }
   1.396 +
   1.397 +    /** Viewport message handler. */
   1.398 +    private DisplayPortMetrics handleViewportMessage(ImmutableViewportMetrics messageMetrics, ViewportMessageType type) {
   1.399 +        synchronized (getLock()) {
   1.400 +            ImmutableViewportMetrics newMetrics;
   1.401 +            ImmutableViewportMetrics oldMetrics = getViewportMetrics();
   1.402 +
   1.403 +            switch (type) {
   1.404 +            default:
   1.405 +            case UPDATE:
   1.406 +                // Keep the old viewport size
   1.407 +                newMetrics = messageMetrics.setViewportSize(oldMetrics.getWidth(), oldMetrics.getHeight());
   1.408 +                if (!oldMetrics.fuzzyEquals(newMetrics)) {
   1.409 +                    abortPanZoomAnimation();
   1.410 +                }
   1.411 +                break;
   1.412 +            case PAGE_SIZE:
   1.413 +                // adjust the page dimensions to account for differences in zoom
   1.414 +                // between the rendered content (which is what Gecko tells us)
   1.415 +                // and our zoom level (which may have diverged).
   1.416 +                float scaleFactor = oldMetrics.zoomFactor / messageMetrics.zoomFactor;
   1.417 +                newMetrics = oldMetrics.setPageRect(RectUtils.scale(messageMetrics.getPageRect(), scaleFactor), messageMetrics.getCssPageRect());
   1.418 +                break;
   1.419 +            }
   1.420 +
   1.421 +            // Update the Gecko-side viewport metrics. Make sure to do this
   1.422 +            // before modifying the metrics below.
   1.423 +            final ImmutableViewportMetrics geckoMetrics = newMetrics.clamp();
   1.424 +            post(new Runnable() {
   1.425 +                @Override
   1.426 +                public void run() {
   1.427 +                    mGeckoViewport = geckoMetrics;
   1.428 +                }
   1.429 +            });
   1.430 +
   1.431 +            setViewportMetrics(newMetrics, type == ViewportMessageType.UPDATE);
   1.432 +            mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null);
   1.433 +        }
   1.434 +        return mDisplayPort;
   1.435 +    }
   1.436 +
   1.437 +    @WrapElementForJNI
   1.438 +    DisplayPortMetrics getDisplayPort(boolean pageSizeUpdate, boolean isBrowserContentDisplayed, int tabId, ImmutableViewportMetrics metrics) {
   1.439 +        Tabs tabs = Tabs.getInstance();
   1.440 +        if (isBrowserContentDisplayed && tabs.isSelectedTabId(tabId)) {
   1.441 +            // for foreground tabs, send the viewport update unless the document
   1.442 +            // displayed is different from the content document. In that case, just
   1.443 +            // calculate the display port.
   1.444 +            return handleViewportMessage(metrics, pageSizeUpdate ? ViewportMessageType.PAGE_SIZE : ViewportMessageType.UPDATE);
   1.445 +        } else {
   1.446 +            // for background tabs, request a new display port calculation, so that
   1.447 +            // when we do switch to that tab, we have the correct display port and
   1.448 +            // don't need to draw twice (once to allow the first-paint viewport to
   1.449 +            // get to java, and again once java figures out the display port).
   1.450 +            return DisplayPortCalculator.calculate(metrics, null);
   1.451 +        }
   1.452 +    }
   1.453 +
   1.454 +    @WrapElementForJNI
   1.455 +    void contentDocumentChanged() {
   1.456 +        mContentDocumentIsDisplayed = false;
   1.457 +    }
   1.458 +
   1.459 +    @WrapElementForJNI
   1.460 +    boolean isContentDocumentDisplayed() {
   1.461 +        return mContentDocumentIsDisplayed;
   1.462 +    }
   1.463 +
   1.464 +    // This is called on the Gecko thread to determine if we're still interested
   1.465 +    // in the update of this display-port to continue. We can return true here
   1.466 +    // to abort the current update and continue with any subsequent ones. This
   1.467 +    // is useful for slow-to-render pages when the display-port starts lagging
   1.468 +    // behind enough that continuing to draw it is wasted effort.
   1.469 +    @WrapElementForJNI(allowMultithread = true)
   1.470 +    public ProgressiveUpdateData progressiveUpdateCallback(boolean aHasPendingNewThebesContent,
   1.471 +                                                           float x, float y, float width, float height,
   1.472 +                                                           float resolution, boolean lowPrecision) {
   1.473 +        // Reset the checkerboard risk flag when switching to low precision
   1.474 +        // rendering.
   1.475 +        if (lowPrecision && !mLastProgressiveUpdateWasLowPrecision) {
   1.476 +            // Skip low precision rendering until we're at risk of checkerboarding.
   1.477 +            if (!mProgressiveUpdateWasInDanger) {
   1.478 +                mProgressiveUpdateData.abort = true;
   1.479 +                return mProgressiveUpdateData;
   1.480 +            }
   1.481 +            mProgressiveUpdateWasInDanger = false;
   1.482 +        }
   1.483 +        mLastProgressiveUpdateWasLowPrecision = lowPrecision;
   1.484 +
   1.485 +        // Grab a local copy of the last display-port sent to Gecko and the
   1.486 +        // current viewport metrics to avoid races when accessing them.
   1.487 +        DisplayPortMetrics displayPort = mDisplayPort;
   1.488 +        ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
   1.489 +        mProgressiveUpdateData.setViewport(viewportMetrics);
   1.490 +        mProgressiveUpdateData.abort = false;
   1.491 +
   1.492 +        // Always abort updates if the resolution has changed. There's no use
   1.493 +        // in drawing at the incorrect resolution.
   1.494 +        if (!FloatUtils.fuzzyEquals(resolution, viewportMetrics.zoomFactor)) {
   1.495 +            Log.d(LOGTAG, "Aborting draw due to resolution change: " + resolution + " != " + viewportMetrics.zoomFactor);
   1.496 +            mProgressiveUpdateData.abort = true;
   1.497 +            return mProgressiveUpdateData;
   1.498 +        }
   1.499 +
   1.500 +        // Store the high precision displayport for comparison when doing low
   1.501 +        // precision updates.
   1.502 +        if (!lowPrecision) {
   1.503 +            if (!FloatUtils.fuzzyEquals(resolution, mProgressiveUpdateDisplayPort.resolution) ||
   1.504 +                !FloatUtils.fuzzyEquals(x, mProgressiveUpdateDisplayPort.getLeft()) ||
   1.505 +                !FloatUtils.fuzzyEquals(y, mProgressiveUpdateDisplayPort.getTop()) ||
   1.506 +                !FloatUtils.fuzzyEquals(x + width, mProgressiveUpdateDisplayPort.getRight()) ||
   1.507 +                !FloatUtils.fuzzyEquals(y + height, mProgressiveUpdateDisplayPort.getBottom())) {
   1.508 +                mProgressiveUpdateDisplayPort =
   1.509 +                    new DisplayPortMetrics(x, y, x+width, y+height, resolution);
   1.510 +            }
   1.511 +        }
   1.512 +
   1.513 +        // If we're not doing low precision draws and we're about to
   1.514 +        // checkerboard, enable low precision drawing.
   1.515 +        if (!lowPrecision && !mProgressiveUpdateWasInDanger) {
   1.516 +            if (DisplayPortCalculator.aboutToCheckerboard(viewportMetrics,
   1.517 +                  mPanZoomController.getVelocityVector(), mProgressiveUpdateDisplayPort)) {
   1.518 +                mProgressiveUpdateWasInDanger = true;
   1.519 +            }
   1.520 +        }
   1.521 +
   1.522 +        // XXX All sorts of rounding happens inside Gecko that becomes hard to
   1.523 +        //     account exactly for. Given we align the display-port to tile
   1.524 +        //     boundaries (and so they rarely vary by sub-pixel amounts), just
   1.525 +        //     check that values are within a couple of pixels of the
   1.526 +        //     display-port bounds.
   1.527 +
   1.528 +        // Never abort drawing if we can't be sure we've sent a more recent
   1.529 +        // display-port. If we abort updating when we shouldn't, we can end up
   1.530 +        // with blank regions on the screen and we open up the risk of entering
   1.531 +        // an endless updating cycle.
   1.532 +        if (Math.abs(displayPort.getLeft() - mProgressiveUpdateDisplayPort.getLeft()) <= 2 &&
   1.533 +            Math.abs(displayPort.getTop() - mProgressiveUpdateDisplayPort.getTop()) <= 2 &&
   1.534 +            Math.abs(displayPort.getBottom() - mProgressiveUpdateDisplayPort.getBottom()) <= 2 &&
   1.535 +            Math.abs(displayPort.getRight() - mProgressiveUpdateDisplayPort.getRight()) <= 2) {
   1.536 +            return mProgressiveUpdateData;
   1.537 +        }
   1.538 +
   1.539 +        // Abort updates when the display-port no longer contains the visible
   1.540 +        // area of the page (that is, the viewport cropped by the page
   1.541 +        // boundaries).
   1.542 +        // XXX This makes the assumption that we never let the visible area of
   1.543 +        //     the page fall outside of the display-port.
   1.544 +        if (Math.max(viewportMetrics.viewportRectLeft, viewportMetrics.pageRectLeft) + 1 < x ||
   1.545 +            Math.max(viewportMetrics.viewportRectTop, viewportMetrics.pageRectTop) + 1 < y ||
   1.546 +            Math.min(viewportMetrics.viewportRectRight, viewportMetrics.pageRectRight) - 1 > x + width ||
   1.547 +            Math.min(viewportMetrics.viewportRectBottom, viewportMetrics.pageRectBottom) - 1 > y + height) {
   1.548 +            Log.d(LOGTAG, "Aborting update due to viewport not in display-port");
   1.549 +            mProgressiveUpdateData.abort = true;
   1.550 +
   1.551 +            // Enable low-precision drawing, as we're likely to be in danger if
   1.552 +            // this situation has been encountered.
   1.553 +            mProgressiveUpdateWasInDanger = true;
   1.554 +
   1.555 +            return mProgressiveUpdateData;
   1.556 +        }
   1.557 +
   1.558 +        // Abort drawing stale low-precision content if there's a more recent
   1.559 +        // display-port in the pipeline.
   1.560 +        if (lowPrecision && !aHasPendingNewThebesContent) {
   1.561 +          mProgressiveUpdateData.abort = true;
   1.562 +        }
   1.563 +        return mProgressiveUpdateData;
   1.564 +    }
   1.565 +
   1.566 +    void setZoomConstraints(ZoomConstraints constraints) {
   1.567 +        mZoomConstraints = constraints;
   1.568 +    }
   1.569 +
   1.570 +    void setIsRTL(boolean aIsRTL) {
   1.571 +        synchronized (getLock()) {
   1.572 +            ImmutableViewportMetrics newMetrics = getViewportMetrics().setIsRTL(aIsRTL);
   1.573 +            setViewportMetrics(newMetrics, false);
   1.574 +        }
   1.575 +    }
   1.576 +
   1.577 +    /** The compositor invokes this function just before compositing a frame where the document
   1.578 +      * is different from the document composited on the last frame. In these cases, the viewport
   1.579 +      * information we have in Java is no longer valid and needs to be replaced with the new
   1.580 +      * viewport information provided. setPageRect will never be invoked on the same frame that
   1.581 +      * this function is invoked on; and this function will always be called prior to syncViewportInfo.
   1.582 +      */
   1.583 +    @WrapElementForJNI(allowMultithread = true)
   1.584 +    public void setFirstPaintViewport(float offsetX, float offsetY, float zoom,
   1.585 +            float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
   1.586 +        synchronized (getLock()) {
   1.587 +            ImmutableViewportMetrics currentMetrics = getViewportMetrics();
   1.588 +
   1.589 +            Tab tab = Tabs.getInstance().getSelectedTab();
   1.590 +
   1.591 +            RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
   1.592 +            RectF pageRect = RectUtils.scaleAndRound(cssPageRect, zoom);
   1.593 +
   1.594 +            final ImmutableViewportMetrics newMetrics = currentMetrics
   1.595 +                .setViewportOrigin(offsetX, offsetY)
   1.596 +                .setZoomFactor(zoom)
   1.597 +                .setPageRect(pageRect, cssPageRect)
   1.598 +                .setIsRTL(tab.getIsRTL());
   1.599 +            // Since we have switched to displaying a different document, we need to update any
   1.600 +            // viewport-related state we have lying around. This includes mGeckoViewport and
   1.601 +            // mViewportMetrics. Usually this information is updated via handleViewportMessage
   1.602 +            // while we remain on the same document.
   1.603 +            post(new Runnable() {
   1.604 +                @Override
   1.605 +                public void run() {
   1.606 +                    mGeckoViewport = newMetrics;
   1.607 +                }
   1.608 +            });
   1.609 +
   1.610 +            setViewportMetrics(newMetrics);
   1.611 +
   1.612 +            mView.setBackgroundColor(tab.getBackgroundColor());
   1.613 +            setZoomConstraints(tab.getZoomConstraints());
   1.614 +
   1.615 +            // At this point, we have just switched to displaying a different document than we
   1.616 +            // we previously displaying. This means we need to abort any panning/zooming animations
   1.617 +            // that are in progress and send an updated display port request to browser.js as soon
   1.618 +            // as possible. The call to PanZoomController.abortAnimation accomplishes this by calling the
   1.619 +            // forceRedraw function, which sends the viewport to gecko. The display port request is
   1.620 +            // actually a full viewport update, which is fine because if browser.js has somehow moved to
   1.621 +            // be out of sync with this first-paint viewport, then we force them back in sync.
   1.622 +            abortPanZoomAnimation();
   1.623 +
   1.624 +            // Indicate that the document is about to be composited so the
   1.625 +            // LayerView background can be removed.
   1.626 +            if (mView.getPaintState() == LayerView.PAINT_START) {
   1.627 +                mView.setPaintState(LayerView.PAINT_BEFORE_FIRST);
   1.628 +            }
   1.629 +        }
   1.630 +        DisplayPortCalculator.resetPageState();
   1.631 +        mDrawTimingQueue.reset();
   1.632 +
   1.633 +        mContentDocumentIsDisplayed = true;
   1.634 +    }
   1.635 +
   1.636 +    /** The compositor invokes this function whenever it determines that the page rect
   1.637 +      * has changed (based on the information it gets from layout). If setFirstPaintViewport
   1.638 +      * is invoked on a frame, then this function will not be. For any given frame, this
   1.639 +      * function will be invoked before syncViewportInfo.
   1.640 +      */
   1.641 +    @WrapElementForJNI(allowMultithread = true)
   1.642 +    public void setPageRect(float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
   1.643 +        synchronized (getLock()) {
   1.644 +            RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
   1.645 +            float ourZoom = getViewportMetrics().zoomFactor;
   1.646 +            setPageRect(RectUtils.scale(cssPageRect, ourZoom), cssPageRect);
   1.647 +            // Here the page size of the document has changed, but the document being displayed
   1.648 +            // is still the same. Therefore, we don't need to send anything to browser.js; any
   1.649 +            // changes we need to make to the display port will get sent the next time we call
   1.650 +            // adjustViewport().
   1.651 +        }
   1.652 +    }
   1.653 +
   1.654 +    /** The compositor invokes this function on every frame to figure out what part of the
   1.655 +      * page to display, and to inform Java of the current display port. Since it is called
   1.656 +      * on every frame, it needs to be ultra-fast.
   1.657 +      * It avoids taking any locks or allocating any objects. We keep around a
   1.658 +      * mCurrentViewTransform so we don't need to allocate a new ViewTransform
   1.659 +      * everytime we're called. NOTE: we might be able to return a ImmutableViewportMetrics
   1.660 +      * which would avoid the copy into mCurrentViewTransform.
   1.661 +      */
   1.662 +    @WrapElementForJNI(allowMultithread = true)
   1.663 +    public ViewTransform syncViewportInfo(int x, int y, int width, int height, float resolution, boolean layersUpdated) {
   1.664 +        // getViewportMetrics is thread safe so we don't need to synchronize.
   1.665 +        // We save the viewport metrics here, so we later use it later in
   1.666 +        // createFrame (which will be called by nsWindow::DrawWindowUnderlay on
   1.667 +        // the native side, by the compositor). The viewport
   1.668 +        // metrics can change between here and there, as it's accessed outside
   1.669 +        // of the compositor thread.
   1.670 +        mFrameMetrics = getViewportMetrics();
   1.671 +
   1.672 +        mCurrentViewTransform.x = mFrameMetrics.viewportRectLeft;
   1.673 +        mCurrentViewTransform.y = mFrameMetrics.viewportRectTop;
   1.674 +        mCurrentViewTransform.scale = mFrameMetrics.zoomFactor;
   1.675 +
   1.676 +        // Adjust the fixed layer margins so that overscroll subtracts from them.
   1.677 +        getFixedMargins(mFrameMetrics, mCurrentViewTransformMargins);
   1.678 +        mCurrentViewTransform.fixedLayerMarginLeft = mCurrentViewTransformMargins.left;
   1.679 +        mCurrentViewTransform.fixedLayerMarginTop = mCurrentViewTransformMargins.top;
   1.680 +        mCurrentViewTransform.fixedLayerMarginRight = mCurrentViewTransformMargins.right;
   1.681 +        mCurrentViewTransform.fixedLayerMarginBottom = mCurrentViewTransformMargins.bottom;
   1.682 +
   1.683 +        // Offset the view transform so that it renders in the correct place.
   1.684 +        PointF offset = mFrameMetrics.getMarginOffset();
   1.685 +        mCurrentViewTransform.offsetX = offset.x;
   1.686 +        mCurrentViewTransform.offsetY = offset.y;
   1.687 +
   1.688 +        mRootLayer.setPositionAndResolution(
   1.689 +            Math.round(x + mCurrentViewTransform.offsetX),
   1.690 +            Math.round(y + mCurrentViewTransform.offsetY),
   1.691 +            Math.round(x + width + mCurrentViewTransform.offsetX),
   1.692 +            Math.round(y + height + mCurrentViewTransform.offsetY),
   1.693 +            resolution);
   1.694 +
   1.695 +        if (layersUpdated && mRecordDrawTimes) {
   1.696 +            // If we got a layers update, that means a draw finished. Check to see if the area drawn matches
   1.697 +            // one of our requested displayports; if it does calculate the draw time and notify the
   1.698 +            // DisplayPortCalculator
   1.699 +            DisplayPortMetrics drawn = new DisplayPortMetrics(x, y, x + width, y + height, resolution);
   1.700 +            long time = mDrawTimingQueue.findTimeFor(drawn);
   1.701 +            if (time >= 0) {
   1.702 +                long now = SystemClock.uptimeMillis();
   1.703 +                time = now - time;
   1.704 +                mRecordDrawTimes = DisplayPortCalculator.drawTimeUpdate(time, width * height);
   1.705 +            }
   1.706 +        }
   1.707 +
   1.708 +        if (layersUpdated) {
   1.709 +            for (DrawListener listener : mDrawListeners) {
   1.710 +                listener.drawFinished();
   1.711 +            }
   1.712 +        }
   1.713 +
   1.714 +        return mCurrentViewTransform;
   1.715 +    }
   1.716 +
   1.717 +    @WrapElementForJNI(allowMultithread = true)
   1.718 +    public ViewTransform syncFrameMetrics(float offsetX, float offsetY, float zoom,
   1.719 +                float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom,
   1.720 +                boolean layersUpdated, int x, int y, int width, int height, float resolution,
   1.721 +                boolean isFirstPaint)
   1.722 +    {
   1.723 +        if (isFirstPaint) {
   1.724 +            setFirstPaintViewport(offsetX, offsetY, zoom,
   1.725 +                                  cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
   1.726 +        }
   1.727 +
   1.728 +        return syncViewportInfo(x, y, width, height, resolution, layersUpdated);
   1.729 +    }
   1.730 +
   1.731 +    @WrapElementForJNI(allowMultithread = true)
   1.732 +    public LayerRenderer.Frame createFrame() {
   1.733 +        // Create the shaders and textures if necessary.
   1.734 +        if (!mLayerRendererInitialized) {
   1.735 +            mLayerRenderer.checkMonitoringEnabled();
   1.736 +            mLayerRenderer.createDefaultProgram();
   1.737 +            mLayerRendererInitialized = true;
   1.738 +        }
   1.739 +
   1.740 +        try {
   1.741 +            return mLayerRenderer.createFrame(mFrameMetrics);
   1.742 +        } catch (Exception e) {
   1.743 +            Log.w(LOGTAG, e);
   1.744 +            return null;
   1.745 +        }
   1.746 +    }
   1.747 +
   1.748 +    @WrapElementForJNI(allowMultithread = true)
   1.749 +    public void activateProgram() {
   1.750 +        mLayerRenderer.activateDefaultProgram();
   1.751 +    }
   1.752 +
   1.753 +    @WrapElementForJNI(allowMultithread = true)
   1.754 +    public void deactivateProgram() {
   1.755 +        mLayerRenderer.deactivateDefaultProgram();
   1.756 +    }
   1.757 +
   1.758 +    private void geometryChanged(DisplayPortMetrics displayPort) {
   1.759 +        /* Let Gecko know if the screensize has changed */
   1.760 +        sendResizeEventIfNecessary(false);
   1.761 +        if (getRedrawHint()) {
   1.762 +            adjustViewport(displayPort);
   1.763 +        }
   1.764 +    }
   1.765 +
   1.766 +    /** Implementation of LayerView.Listener */
   1.767 +    @Override
   1.768 +    public void renderRequested() {
   1.769 +        try {
   1.770 +            GeckoAppShell.scheduleComposite();
   1.771 +        } catch (UnsupportedOperationException uoe) {
   1.772 +            // In some very rare cases this gets called before libxul is loaded,
   1.773 +            // so catch and ignore the exception that will throw. See bug 837821
   1.774 +            Log.d(LOGTAG, "Dropping renderRequested call before libxul load.");
   1.775 +        }
   1.776 +    }
   1.777 +
   1.778 +    /** Implementation of LayerView.Listener */
   1.779 +    @Override
   1.780 +    public void sizeChanged(int width, int height) {
   1.781 +        // We need to make sure a draw happens synchronously at this point,
   1.782 +        // but resizing the surface before the SurfaceView has resized will
   1.783 +        // cause a visible jump.
   1.784 +        mView.getGLController().resumeCompositor(mWindowSize.width, mWindowSize.height);
   1.785 +    }
   1.786 +
   1.787 +    /** Implementation of LayerView.Listener */
   1.788 +    @Override
   1.789 +    public void surfaceChanged(int width, int height) {
   1.790 +        setViewportSize(width, height);
   1.791 +    }
   1.792 +
   1.793 +    /** Implementation of PanZoomTarget */
   1.794 +    @Override
   1.795 +    public ImmutableViewportMetrics getViewportMetrics() {
   1.796 +        return mViewportMetrics;
   1.797 +    }
   1.798 +
   1.799 +    /** Implementation of PanZoomTarget */
   1.800 +    @Override
   1.801 +    public ZoomConstraints getZoomConstraints() {
   1.802 +        return mZoomConstraints;
   1.803 +    }
   1.804 +
   1.805 +    /** Implementation of PanZoomTarget */
   1.806 +    @Override
   1.807 +    public boolean isFullScreen() {
   1.808 +        return mView.isFullScreen();
   1.809 +    }
   1.810 +
   1.811 +    /** Implementation of PanZoomTarget */
   1.812 +    @Override
   1.813 +    public RectF getMaxMargins() {
   1.814 +        return mMarginsAnimator.getMaxMargins();
   1.815 +    }
   1.816 +
   1.817 +    /** Implementation of PanZoomTarget */
   1.818 +    @Override
   1.819 +    public void setAnimationTarget(ImmutableViewportMetrics metrics) {
   1.820 +        if (mGeckoIsReady) {
   1.821 +            // We know what the final viewport of the animation is going to be, so
   1.822 +            // immediately request a draw of that area by setting the display port
   1.823 +            // accordingly. This way we should have the content pre-rendered by the
   1.824 +            // time the animation is done.
   1.825 +            DisplayPortMetrics displayPort = DisplayPortCalculator.calculate(metrics, null);
   1.826 +            adjustViewport(displayPort);
   1.827 +        }
   1.828 +    }
   1.829 +
   1.830 +    /** Implementation of PanZoomTarget
   1.831 +     * You must hold the monitor while calling this.
   1.832 +     */
   1.833 +    @Override
   1.834 +    public void setViewportMetrics(ImmutableViewportMetrics metrics) {
   1.835 +        setViewportMetrics(metrics, true);
   1.836 +    }
   1.837 +
   1.838 +    /*
   1.839 +     * You must hold the monitor while calling this.
   1.840 +     */
   1.841 +    private void setViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko) {
   1.842 +        // This class owns the viewport size and the fixed layer margins; don't let other pieces
   1.843 +        // of code clobber either of them. The only place the viewport size should ever be
   1.844 +        // updated is in GeckoLayerClient.setViewportSize, and the only place the margins should
   1.845 +        // ever be updated is in GeckoLayerClient.setFixedLayerMargins; both of these assign to
   1.846 +        // mViewportMetrics directly.
   1.847 +        metrics = metrics.setViewportSize(mViewportMetrics.getWidth(), mViewportMetrics.getHeight());
   1.848 +        metrics = metrics.setMarginsFrom(mViewportMetrics);
   1.849 +        mViewportMetrics = metrics;
   1.850 +
   1.851 +        viewportMetricsChanged(notifyGecko);
   1.852 +    }
   1.853 +
   1.854 +    /*
   1.855 +     * You must hold the monitor while calling this.
   1.856 +     */
   1.857 +    private void viewportMetricsChanged(boolean notifyGecko) {
   1.858 +        if (mViewportChangeListener != null) {
   1.859 +            mViewportChangeListener.onMetricsChanged(mViewportMetrics);
   1.860 +        }
   1.861 +
   1.862 +        mView.requestRender();
   1.863 +        if (notifyGecko && mGeckoIsReady) {
   1.864 +            geometryChanged(null);
   1.865 +        }
   1.866 +    }
   1.867 +
   1.868 +    /*
   1.869 +     * Updates the viewport metrics, overriding the viewport size and margins
   1.870 +     * which are normally retained when calling setViewportMetrics.
   1.871 +     * You must hold the monitor while calling this.
   1.872 +     */
   1.873 +    void forceViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko, boolean forceRedraw) {
   1.874 +        if (forceRedraw) {
   1.875 +            mForceRedraw = true;
   1.876 +        }
   1.877 +        mViewportMetrics = metrics;
   1.878 +        viewportMetricsChanged(notifyGecko);
   1.879 +    }
   1.880 +
   1.881 +    /** Implementation of PanZoomTarget
   1.882 +     * Scroll the viewport by a certain amount. This will take viewport margins
   1.883 +     * and margin animation into account. If margins are currently animating,
   1.884 +     * this will just go ahead and modify the viewport origin, otherwise the
   1.885 +     * delta will be applied to the margins and the remainder will be applied to
   1.886 +     * the viewport origin.
   1.887 +     *
   1.888 +     * You must hold the monitor while calling this.
   1.889 +     */
   1.890 +    @Override
   1.891 +    public void scrollBy(float dx, float dy) {
   1.892 +        // Set mViewportMetrics manually so the margin changes take.
   1.893 +        mViewportMetrics = mMarginsAnimator.scrollBy(mViewportMetrics, dx, dy);
   1.894 +        viewportMetricsChanged(true);
   1.895 +    }
   1.896 +
   1.897 +    /** Implementation of PanZoomTarget
   1.898 +     * Notification that a subdocument has been scrolled by a certain amount.
   1.899 +     * This is used here to make sure that the margins are still accessible
   1.900 +     * during subdocument scrolling.
   1.901 +     *
   1.902 +     * You must hold the monitor while calling this.
   1.903 +     */
   1.904 +    @Override
   1.905 +    public void scrollMarginsBy(float dx, float dy) {
   1.906 +        ImmutableViewportMetrics newMarginsMetrics =
   1.907 +            mMarginsAnimator.scrollBy(mViewportMetrics, dx, dy);
   1.908 +        mViewportMetrics = mViewportMetrics.setMarginsFrom(newMarginsMetrics);
   1.909 +        viewportMetricsChanged(true);
   1.910 +    }
   1.911 +
   1.912 +    /** Implementation of PanZoomTarget */
   1.913 +    @Override
   1.914 +    public void panZoomStopped() {
   1.915 +        if (mViewportChangeListener != null) {
   1.916 +            mViewportChangeListener.onPanZoomStopped();
   1.917 +        }
   1.918 +    }
   1.919 +
   1.920 +    /** Implementation of PanZoomTarget */
   1.921 +    @Override
   1.922 +    public void forceRedraw(DisplayPortMetrics displayPort) {
   1.923 +        mForceRedraw = true;
   1.924 +        if (mGeckoIsReady) {
   1.925 +            geometryChanged(displayPort);
   1.926 +        }
   1.927 +    }
   1.928 +
   1.929 +    /** Implementation of PanZoomTarget */
   1.930 +    @Override
   1.931 +    public boolean post(Runnable action) {
   1.932 +        return mView.post(action);
   1.933 +    }
   1.934 +
   1.935 +    /** Implementation of PanZoomTarget */
   1.936 +    @Override
   1.937 +    public void postRenderTask(RenderTask task) {
   1.938 +        mView.postRenderTask(task);
   1.939 +    }
   1.940 +
   1.941 +    /** Implementation of PanZoomTarget */
   1.942 +    @Override
   1.943 +    public void removeRenderTask(RenderTask task) {
   1.944 +        mView.removeRenderTask(task);
   1.945 +    }
   1.946 +
   1.947 +
   1.948 +    /** Implementation of PanZoomTarget */
   1.949 +    @Override
   1.950 +    public boolean postDelayed(Runnable action, long delayMillis) {
   1.951 +        return mView.postDelayed(action, delayMillis);
   1.952 +    }
   1.953 +
   1.954 +    /** Implementation of PanZoomTarget */
   1.955 +    @Override
   1.956 +    public Object getLock() {
   1.957 +        return this;
   1.958 +    }
   1.959 +
   1.960 +    /** Implementation of PanZoomTarget
   1.961 +     * Converts a point from layer view coordinates to layer coordinates. In other words, given a
   1.962 +     * point measured in pixels from the top left corner of the layer view, returns the point in
   1.963 +     * pixels measured from the last scroll position we sent to Gecko, in CSS pixels. Assuming the
   1.964 +     * events being sent to Gecko are processed in FIFO order, this calculation should always be
   1.965 +     * correct.
   1.966 +     */
   1.967 +    @Override
   1.968 +    public PointF convertViewPointToLayerPoint(PointF viewPoint) {
   1.969 +        if (!mGeckoIsReady) {
   1.970 +            return null;
   1.971 +        }
   1.972 +
   1.973 +        ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
   1.974 +        PointF origin = viewportMetrics.getOrigin();
   1.975 +        PointF offset = viewportMetrics.getMarginOffset();
   1.976 +        origin.offset(-offset.x, -offset.y);
   1.977 +        float zoom = viewportMetrics.zoomFactor;
   1.978 +        ImmutableViewportMetrics geckoViewport = mGeckoViewport;
   1.979 +        PointF geckoOrigin = geckoViewport.getOrigin();
   1.980 +        float geckoZoom = geckoViewport.zoomFactor;
   1.981 +
   1.982 +        // viewPoint + origin - offset gives the coordinate in device pixels from the top-left corner of the page.
   1.983 +        // Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page.
   1.984 +        // geckoOrigin / geckoZoom is where Gecko thinks it is (scrollTo position) in CSS pixels from
   1.985 +        // the top-left corner of the page. Subtracting the two gives us the offset of the viewPoint from
   1.986 +        // the current Gecko coordinate in CSS pixels.
   1.987 +        PointF layerPoint = new PointF(
   1.988 +                ((viewPoint.x + origin.x) / zoom) - (geckoOrigin.x / geckoZoom),
   1.989 +                ((viewPoint.y + origin.y) / zoom) - (geckoOrigin.y / geckoZoom));
   1.990 +
   1.991 +        return layerPoint;
   1.992 +    }
   1.993 +
   1.994 +    void setOnMetricsChangedListener(LayerView.OnMetricsChangedListener listener) {
   1.995 +        mViewportChangeListener = listener;
   1.996 +    }
   1.997 +
   1.998 +    public void addDrawListener(DrawListener listener) {
   1.999 +        mDrawListeners.add(listener);
  1.1000 +    }
  1.1001 +
  1.1002 +    public void removeDrawListener(DrawListener listener) {
  1.1003 +        mDrawListeners.remove(listener);
  1.1004 +    }
  1.1005 +}

mercurial