mobile/android/base/gfx/GeckoLayerClient.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko.gfx;
     8 import org.mozilla.gecko.GeckoAppShell;
     9 import org.mozilla.gecko.GeckoEvent;
    10 import org.mozilla.gecko.gfx.LayerView.DrawListener;
    11 import org.mozilla.gecko.Tab;
    12 import org.mozilla.gecko.Tabs;
    13 import org.mozilla.gecko.ZoomConstraints;
    14 import org.mozilla.gecko.mozglue.RobocopTarget;
    15 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
    16 import org.mozilla.gecko.EventDispatcher;
    17 import org.mozilla.gecko.util.FloatUtils;
    19 import android.content.Context;
    20 import android.graphics.PointF;
    21 import android.graphics.RectF;
    22 import android.os.SystemClock;
    23 import android.util.DisplayMetrics;
    24 import android.util.Log;
    26 import java.util.ArrayList;
    27 import java.util.List;
    29 class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
    30 {
    31     private static final String LOGTAG = "GeckoLayerClient";
    33     private LayerRenderer mLayerRenderer;
    34     private boolean mLayerRendererInitialized;
    36     private Context mContext;
    37     private IntSize mScreenSize;
    38     private IntSize mWindowSize;
    39     private DisplayPortMetrics mDisplayPort;
    41     private boolean mRecordDrawTimes;
    42     private final DrawTimingQueue mDrawTimingQueue;
    44     private VirtualLayer mRootLayer;
    46     /* The Gecko viewport as per the UI thread. Must be touched only on the UI thread.
    47      * If any events being sent to Gecko that are relative to the Gecko viewport position,
    48      * they must (a) be relative to this viewport, and (b) be sent on the UI thread to
    49      * avoid races. As long as these two conditions are satisfied, and the events being
    50      * sent to Gecko are processed in FIFO order, the events will properly be relative
    51      * to the Gecko viewport position. Note that if Gecko updates its viewport independently,
    52      * we get notified synchronously and also update this on the UI thread.
    53      */
    54     private ImmutableViewportMetrics mGeckoViewport;
    56     /*
    57      * The viewport metrics being used to draw the current frame. This is only
    58      * accessed by the compositor thread, and so needs no synchronisation.
    59      */
    60     private ImmutableViewportMetrics mFrameMetrics;
    62     private List<DrawListener> mDrawListeners;
    64     /* Used as temporaries by syncViewportInfo */
    65     private final ViewTransform mCurrentViewTransform;
    66     private final RectF mCurrentViewTransformMargins;
    68     /* Used as the return value of progressiveUpdateCallback */
    69     private final ProgressiveUpdateData mProgressiveUpdateData;
    70     private DisplayPortMetrics mProgressiveUpdateDisplayPort;
    71     private boolean mLastProgressiveUpdateWasLowPrecision;
    72     private boolean mProgressiveUpdateWasInDanger;
    74     private boolean mForceRedraw;
    76     /* The current viewport metrics.
    77      * This is volatile so that we can read and write to it from different threads.
    78      * We avoid synchronization to make getting the viewport metrics from
    79      * the compositor as cheap as possible. The viewport is immutable so
    80      * we don't need to worry about anyone mutating it while we're reading from it.
    81      * Specifically:
    82      * 1) reading mViewportMetrics from any thread is fine without synchronization
    83      * 2) writing to mViewportMetrics requires synchronizing on the layer controller object
    84      * 3) whenver reading multiple fields from mViewportMetrics without synchronization (i.e. in
    85      *    case 1 above) you should always frist grab a local copy of the reference, and then use
    86      *    that because mViewportMetrics might get reassigned in between reading the different
    87      *    fields. */
    88     private volatile ImmutableViewportMetrics mViewportMetrics;
    89     private LayerView.OnMetricsChangedListener mViewportChangeListener;
    91     private ZoomConstraints mZoomConstraints;
    93     private boolean mGeckoIsReady;
    95     private final PanZoomController mPanZoomController;
    96     private final LayerMarginsAnimator mMarginsAnimator;
    97     private LayerView mView;
    99     /* This flag is true from the time that browser.js detects a first-paint is about to start,
   100      * to the time that we receive the first-paint composite notification from the compositor.
   101      * Note that there is a small race condition with this; if there are two paints that both
   102      * have the first-paint flag set, and the second paint happens concurrently with the
   103      * composite for the first paint, then this flag may be set to true prematurely. Fixing this
   104      * is possible but risky; see https://bugzilla.mozilla.org/show_bug.cgi?id=797615#c751
   105      */
   106     private volatile boolean mContentDocumentIsDisplayed;
   108     public GeckoLayerClient(Context context, LayerView view, EventDispatcher eventDispatcher) {
   109         // we can fill these in with dummy values because they are always written
   110         // to before being read
   111         mContext = context;
   112         mScreenSize = new IntSize(0, 0);
   113         mWindowSize = new IntSize(0, 0);
   114         mDisplayPort = new DisplayPortMetrics();
   115         mRecordDrawTimes = true;
   116         mDrawTimingQueue = new DrawTimingQueue();
   117         mCurrentViewTransform = new ViewTransform(0, 0, 1);
   118         mCurrentViewTransformMargins = new RectF();
   119         mProgressiveUpdateData = new ProgressiveUpdateData();
   120         mProgressiveUpdateDisplayPort = new DisplayPortMetrics();
   121         mLastProgressiveUpdateWasLowPrecision = false;
   122         mProgressiveUpdateWasInDanger = false;
   124         mForceRedraw = true;
   125         DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
   126         mViewportMetrics = new ImmutableViewportMetrics(displayMetrics)
   127                            .setViewportSize(view.getWidth(), view.getHeight());
   128         mZoomConstraints = new ZoomConstraints(false);
   130         Tab tab = Tabs.getInstance().getSelectedTab();
   131         if (tab != null) {
   132             mZoomConstraints = tab.getZoomConstraints();
   133             mViewportMetrics = mViewportMetrics.setIsRTL(tab.getIsRTL());
   134         }
   136         mFrameMetrics = mViewportMetrics;
   138         mDrawListeners = new ArrayList<DrawListener>();
   139         mPanZoomController = PanZoomController.Factory.create(this, view, eventDispatcher);
   140         mMarginsAnimator = new LayerMarginsAnimator(this, view);
   141         mView = view;
   142         mView.setListener(this);
   143         mContentDocumentIsDisplayed = true;
   144     }
   146     public void setOverscrollHandler(final Overscroll listener) {
   147         mPanZoomController.setOverscrollHandler(listener);
   148     }
   150     /** Attaches to root layer so that Gecko appears. */
   151     public void notifyGeckoReady() {
   152         mGeckoIsReady = true;
   154         mRootLayer = new VirtualLayer(new IntSize(mView.getWidth(), mView.getHeight()));
   155         mLayerRenderer = mView.getRenderer();
   157         sendResizeEventIfNecessary(true);
   159         DisplayPortCalculator.initPrefs();
   161         // Gecko being ready is one of the two conditions (along with having an available
   162         // surface) that cause us to create the compositor. So here, now that we know gecko
   163         // is ready, call updateCompositor() to see if we can actually do the creation.
   164         // This needs to run on the UI thread so that the surface validity can't change on
   165         // us while we're in the middle of creating the compositor.
   166         mView.post(new Runnable() {
   167             @Override
   168             public void run() {
   169                 mView.getGLController().updateCompositor();
   170             }
   171         });
   172     }
   174     public void destroy() {
   175         mPanZoomController.destroy();
   176         mMarginsAnimator.destroy();
   177         mDrawListeners.clear();
   178     }
   180     /**
   181      * Returns true if this client is fine with performing a redraw operation or false if it
   182      * would prefer that the action didn't take place.
   183      */
   184     private boolean getRedrawHint() {
   185         if (mForceRedraw) {
   186             mForceRedraw = false;
   187             return true;
   188         }
   190         if (!mPanZoomController.getRedrawHint()) {
   191             return false;
   192         }
   194         return DisplayPortCalculator.aboutToCheckerboard(mViewportMetrics,
   195                 mPanZoomController.getVelocityVector(), mDisplayPort);
   196     }
   198     Layer getRoot() {
   199         return mGeckoIsReady ? mRootLayer : null;
   200     }
   202     public LayerView getView() {
   203         return mView;
   204     }
   206     public FloatSize getViewportSize() {
   207         return mViewportMetrics.getSize();
   208     }
   210     /**
   211      * The view calls this function to indicate that the viewport changed size. It must hold the
   212      * monitor while calling it.
   213      *
   214      * TODO: Refactor this to use an interface. Expose that interface only to the view and not
   215      * to the layer client. That way, the layer client won't be tempted to call this, which might
   216      * result in an infinite loop.
   217      */
   218     void setViewportSize(int width, int height) {
   219         mViewportMetrics = mViewportMetrics.setViewportSize(width, height);
   221         if (mGeckoIsReady) {
   222             // here we send gecko a resize message. The code in browser.js is responsible for
   223             // picking up on that resize event, modifying the viewport as necessary, and informing
   224             // us of the new viewport.
   225             sendResizeEventIfNecessary(true);
   226             // the following call also sends gecko a message, which will be processed after the resize
   227             // message above has updated the viewport. this message ensures that if we have just put
   228             // focus in a text field, we scroll the content so that the text field is in view.
   229             GeckoAppShell.viewSizeChanged();
   230         }
   231     }
   233     PanZoomController getPanZoomController() {
   234         return mPanZoomController;
   235     }
   237     LayerMarginsAnimator getLayerMarginsAnimator() {
   238         return mMarginsAnimator;
   239     }
   241     /* Informs Gecko that the screen size has changed. */
   242     private void sendResizeEventIfNecessary(boolean force) {
   243         DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
   245         IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
   246         IntSize newWindowSize = new IntSize(mView.getWidth(), mView.getHeight());
   248         boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
   249         boolean windowSizeChanged = !mWindowSize.equals(newWindowSize);
   251         if (!force && !screenSizeChanged && !windowSizeChanged) {
   252             return;
   253         }
   255         mScreenSize = newScreenSize;
   256         mWindowSize = newWindowSize;
   258         if (screenSizeChanged) {
   259             Log.d(LOGTAG, "Screen-size changed to " + mScreenSize);
   260         }
   262         if (windowSizeChanged) {
   263             Log.d(LOGTAG, "Window-size changed to " + mWindowSize);
   264         }
   266         GeckoEvent event = GeckoEvent.createSizeChangedEvent(mWindowSize.width, mWindowSize.height,
   267                                                              mScreenSize.width, mScreenSize.height);
   268         GeckoAppShell.sendEventToGecko(event);
   269         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Window:Resize", ""));
   270     }
   272     /** Sets the current page rect. You must hold the monitor while calling this. */
   273     private void setPageRect(RectF rect, RectF cssRect) {
   274         // Since the "rect" is always just a multiple of "cssRect" we don't need to
   275         // check both; this function assumes that both "rect" and "cssRect" are relative
   276         // the zoom factor in mViewportMetrics.
   277         if (mViewportMetrics.getCssPageRect().equals(cssRect))
   278             return;
   280         mViewportMetrics = mViewportMetrics.setPageRect(rect, cssRect);
   282         // Page size is owned by the layer client, so no need to notify it of
   283         // this change.
   285         post(new Runnable() {
   286             @Override
   287             public void run() {
   288                 mPanZoomController.pageRectUpdated();
   289                 mView.requestRender();
   290             }
   291         });
   292     }
   294     /**
   295      * Derives content document fixed position margins/fixed layer margins from
   296      * the view margins in the given metrics object.
   297      */
   298     private void getFixedMargins(ImmutableViewportMetrics metrics, RectF fixedMargins) {
   299         fixedMargins.left = 0;
   300         fixedMargins.top = 0;
   301         fixedMargins.right = 0;
   302         fixedMargins.bottom = 0;
   304         // The maximum margins are determined by the scrollable area of the page.
   305         float maxMarginWidth = Math.max(0, metrics.getPageWidth() - metrics.getWidthWithoutMargins());
   306         float maxMarginHeight = Math.max(0, metrics.getPageHeight() - metrics.getHeightWithoutMargins());
   308         // If the margins can't fully hide, they're pinned on - in which case,
   309         // fixed margins should always be zero.
   310         if (maxMarginWidth < metrics.marginLeft + metrics.marginRight) {
   311           maxMarginWidth = 0;
   312         }
   313         if (maxMarginHeight < metrics.marginTop + metrics.marginBottom) {
   314           maxMarginHeight = 0;
   315         }
   317         PointF offset = metrics.getMarginOffset();
   318         RectF overscroll = metrics.getOverscroll();
   319         if (offset.x >= 0) {
   320             fixedMargins.right = Math.max(0, Math.min(offset.x - overscroll.right, maxMarginWidth));
   321         } else {
   322             fixedMargins.left = Math.max(0, Math.min(-offset.x - overscroll.left, maxMarginWidth));
   323         }
   324         if (offset.y >= 0) {
   325             fixedMargins.bottom = Math.max(0, Math.min(offset.y - overscroll.bottom, maxMarginHeight));
   326         } else {
   327             fixedMargins.top = Math.max(0, Math.min(-offset.y - overscroll.top, maxMarginHeight));
   328         }
   330         // Adjust for overscroll. If we're overscrolled on one side, add that
   331         // distance to the margins of the other side (limiting to the maximum
   332         // margin size calculated above).
   333         if (overscroll.left > 0) {
   334             fixedMargins.right = Math.min(maxMarginWidth - fixedMargins.left,
   335                                           fixedMargins.right + overscroll.left);
   336         } else if (overscroll.right > 0) {
   337             fixedMargins.left = Math.min(maxMarginWidth - fixedMargins.right,
   338                                          fixedMargins.left + overscroll.right);
   339         }
   340         if (overscroll.top > 0) {
   341             fixedMargins.bottom = Math.min(maxMarginHeight - fixedMargins.top,
   342                                            fixedMargins.bottom + overscroll.top);
   343         } else if (overscroll.bottom > 0) {
   344             fixedMargins.top = Math.min(maxMarginHeight - fixedMargins.bottom,
   345                                         fixedMargins.top + overscroll.bottom);
   346         }
   347     }
   349     private void adjustViewport(DisplayPortMetrics displayPort) {
   350         ImmutableViewportMetrics metrics = getViewportMetrics();
   351         ImmutableViewportMetrics clampedMetrics = metrics.clamp();
   353         RectF margins = new RectF();
   354         getFixedMargins(metrics, margins);
   355         clampedMetrics = clampedMetrics.setMargins(
   356             margins.left, margins.top, margins.right, margins.bottom);
   358         if (displayPort == null) {
   359             displayPort = DisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector());
   360         }
   362         mDisplayPort = displayPort;
   363         mGeckoViewport = clampedMetrics;
   365         if (mRecordDrawTimes) {
   366             mDrawTimingQueue.add(displayPort);
   367         }
   369         GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(clampedMetrics, displayPort));
   370     }
   372     /** Aborts any pan/zoom animation that is currently in progress. */
   373     private void abortPanZoomAnimation() {
   374         if (mPanZoomController != null) {
   375             post(new Runnable() {
   376                 @Override
   377                 public void run() {
   378                     mPanZoomController.abortAnimation();
   379                 }
   380             });
   381         }
   382     }
   384     /**
   385      * The different types of Viewport messages handled. All viewport events
   386      * expect a display-port to be returned, but can handle one not being
   387      * returned.
   388      */
   389     private enum ViewportMessageType {
   390         UPDATE,       // The viewport has changed and should be entirely updated
   391         PAGE_SIZE     // The viewport's page-size has changed
   392     }
   394     /** Viewport message handler. */
   395     private DisplayPortMetrics handleViewportMessage(ImmutableViewportMetrics messageMetrics, ViewportMessageType type) {
   396         synchronized (getLock()) {
   397             ImmutableViewportMetrics newMetrics;
   398             ImmutableViewportMetrics oldMetrics = getViewportMetrics();
   400             switch (type) {
   401             default:
   402             case UPDATE:
   403                 // Keep the old viewport size
   404                 newMetrics = messageMetrics.setViewportSize(oldMetrics.getWidth(), oldMetrics.getHeight());
   405                 if (!oldMetrics.fuzzyEquals(newMetrics)) {
   406                     abortPanZoomAnimation();
   407                 }
   408                 break;
   409             case PAGE_SIZE:
   410                 // adjust the page dimensions to account for differences in zoom
   411                 // between the rendered content (which is what Gecko tells us)
   412                 // and our zoom level (which may have diverged).
   413                 float scaleFactor = oldMetrics.zoomFactor / messageMetrics.zoomFactor;
   414                 newMetrics = oldMetrics.setPageRect(RectUtils.scale(messageMetrics.getPageRect(), scaleFactor), messageMetrics.getCssPageRect());
   415                 break;
   416             }
   418             // Update the Gecko-side viewport metrics. Make sure to do this
   419             // before modifying the metrics below.
   420             final ImmutableViewportMetrics geckoMetrics = newMetrics.clamp();
   421             post(new Runnable() {
   422                 @Override
   423                 public void run() {
   424                     mGeckoViewport = geckoMetrics;
   425                 }
   426             });
   428             setViewportMetrics(newMetrics, type == ViewportMessageType.UPDATE);
   429             mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null);
   430         }
   431         return mDisplayPort;
   432     }
   434     @WrapElementForJNI
   435     DisplayPortMetrics getDisplayPort(boolean pageSizeUpdate, boolean isBrowserContentDisplayed, int tabId, ImmutableViewportMetrics metrics) {
   436         Tabs tabs = Tabs.getInstance();
   437         if (isBrowserContentDisplayed && tabs.isSelectedTabId(tabId)) {
   438             // for foreground tabs, send the viewport update unless the document
   439             // displayed is different from the content document. In that case, just
   440             // calculate the display port.
   441             return handleViewportMessage(metrics, pageSizeUpdate ? ViewportMessageType.PAGE_SIZE : ViewportMessageType.UPDATE);
   442         } else {
   443             // for background tabs, request a new display port calculation, so that
   444             // when we do switch to that tab, we have the correct display port and
   445             // don't need to draw twice (once to allow the first-paint viewport to
   446             // get to java, and again once java figures out the display port).
   447             return DisplayPortCalculator.calculate(metrics, null);
   448         }
   449     }
   451     @WrapElementForJNI
   452     void contentDocumentChanged() {
   453         mContentDocumentIsDisplayed = false;
   454     }
   456     @WrapElementForJNI
   457     boolean isContentDocumentDisplayed() {
   458         return mContentDocumentIsDisplayed;
   459     }
   461     // This is called on the Gecko thread to determine if we're still interested
   462     // in the update of this display-port to continue. We can return true here
   463     // to abort the current update and continue with any subsequent ones. This
   464     // is useful for slow-to-render pages when the display-port starts lagging
   465     // behind enough that continuing to draw it is wasted effort.
   466     @WrapElementForJNI(allowMultithread = true)
   467     public ProgressiveUpdateData progressiveUpdateCallback(boolean aHasPendingNewThebesContent,
   468                                                            float x, float y, float width, float height,
   469                                                            float resolution, boolean lowPrecision) {
   470         // Reset the checkerboard risk flag when switching to low precision
   471         // rendering.
   472         if (lowPrecision && !mLastProgressiveUpdateWasLowPrecision) {
   473             // Skip low precision rendering until we're at risk of checkerboarding.
   474             if (!mProgressiveUpdateWasInDanger) {
   475                 mProgressiveUpdateData.abort = true;
   476                 return mProgressiveUpdateData;
   477             }
   478             mProgressiveUpdateWasInDanger = false;
   479         }
   480         mLastProgressiveUpdateWasLowPrecision = lowPrecision;
   482         // Grab a local copy of the last display-port sent to Gecko and the
   483         // current viewport metrics to avoid races when accessing them.
   484         DisplayPortMetrics displayPort = mDisplayPort;
   485         ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
   486         mProgressiveUpdateData.setViewport(viewportMetrics);
   487         mProgressiveUpdateData.abort = false;
   489         // Always abort updates if the resolution has changed. There's no use
   490         // in drawing at the incorrect resolution.
   491         if (!FloatUtils.fuzzyEquals(resolution, viewportMetrics.zoomFactor)) {
   492             Log.d(LOGTAG, "Aborting draw due to resolution change: " + resolution + " != " + viewportMetrics.zoomFactor);
   493             mProgressiveUpdateData.abort = true;
   494             return mProgressiveUpdateData;
   495         }
   497         // Store the high precision displayport for comparison when doing low
   498         // precision updates.
   499         if (!lowPrecision) {
   500             if (!FloatUtils.fuzzyEquals(resolution, mProgressiveUpdateDisplayPort.resolution) ||
   501                 !FloatUtils.fuzzyEquals(x, mProgressiveUpdateDisplayPort.getLeft()) ||
   502                 !FloatUtils.fuzzyEquals(y, mProgressiveUpdateDisplayPort.getTop()) ||
   503                 !FloatUtils.fuzzyEquals(x + width, mProgressiveUpdateDisplayPort.getRight()) ||
   504                 !FloatUtils.fuzzyEquals(y + height, mProgressiveUpdateDisplayPort.getBottom())) {
   505                 mProgressiveUpdateDisplayPort =
   506                     new DisplayPortMetrics(x, y, x+width, y+height, resolution);
   507             }
   508         }
   510         // If we're not doing low precision draws and we're about to
   511         // checkerboard, enable low precision drawing.
   512         if (!lowPrecision && !mProgressiveUpdateWasInDanger) {
   513             if (DisplayPortCalculator.aboutToCheckerboard(viewportMetrics,
   514                   mPanZoomController.getVelocityVector(), mProgressiveUpdateDisplayPort)) {
   515                 mProgressiveUpdateWasInDanger = true;
   516             }
   517         }
   519         // XXX All sorts of rounding happens inside Gecko that becomes hard to
   520         //     account exactly for. Given we align the display-port to tile
   521         //     boundaries (and so they rarely vary by sub-pixel amounts), just
   522         //     check that values are within a couple of pixels of the
   523         //     display-port bounds.
   525         // Never abort drawing if we can't be sure we've sent a more recent
   526         // display-port. If we abort updating when we shouldn't, we can end up
   527         // with blank regions on the screen and we open up the risk of entering
   528         // an endless updating cycle.
   529         if (Math.abs(displayPort.getLeft() - mProgressiveUpdateDisplayPort.getLeft()) <= 2 &&
   530             Math.abs(displayPort.getTop() - mProgressiveUpdateDisplayPort.getTop()) <= 2 &&
   531             Math.abs(displayPort.getBottom() - mProgressiveUpdateDisplayPort.getBottom()) <= 2 &&
   532             Math.abs(displayPort.getRight() - mProgressiveUpdateDisplayPort.getRight()) <= 2) {
   533             return mProgressiveUpdateData;
   534         }
   536         // Abort updates when the display-port no longer contains the visible
   537         // area of the page (that is, the viewport cropped by the page
   538         // boundaries).
   539         // XXX This makes the assumption that we never let the visible area of
   540         //     the page fall outside of the display-port.
   541         if (Math.max(viewportMetrics.viewportRectLeft, viewportMetrics.pageRectLeft) + 1 < x ||
   542             Math.max(viewportMetrics.viewportRectTop, viewportMetrics.pageRectTop) + 1 < y ||
   543             Math.min(viewportMetrics.viewportRectRight, viewportMetrics.pageRectRight) - 1 > x + width ||
   544             Math.min(viewportMetrics.viewportRectBottom, viewportMetrics.pageRectBottom) - 1 > y + height) {
   545             Log.d(LOGTAG, "Aborting update due to viewport not in display-port");
   546             mProgressiveUpdateData.abort = true;
   548             // Enable low-precision drawing, as we're likely to be in danger if
   549             // this situation has been encountered.
   550             mProgressiveUpdateWasInDanger = true;
   552             return mProgressiveUpdateData;
   553         }
   555         // Abort drawing stale low-precision content if there's a more recent
   556         // display-port in the pipeline.
   557         if (lowPrecision && !aHasPendingNewThebesContent) {
   558           mProgressiveUpdateData.abort = true;
   559         }
   560         return mProgressiveUpdateData;
   561     }
   563     void setZoomConstraints(ZoomConstraints constraints) {
   564         mZoomConstraints = constraints;
   565     }
   567     void setIsRTL(boolean aIsRTL) {
   568         synchronized (getLock()) {
   569             ImmutableViewportMetrics newMetrics = getViewportMetrics().setIsRTL(aIsRTL);
   570             setViewportMetrics(newMetrics, false);
   571         }
   572     }
   574     /** The compositor invokes this function just before compositing a frame where the document
   575       * is different from the document composited on the last frame. In these cases, the viewport
   576       * information we have in Java is no longer valid and needs to be replaced with the new
   577       * viewport information provided. setPageRect will never be invoked on the same frame that
   578       * this function is invoked on; and this function will always be called prior to syncViewportInfo.
   579       */
   580     @WrapElementForJNI(allowMultithread = true)
   581     public void setFirstPaintViewport(float offsetX, float offsetY, float zoom,
   582             float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
   583         synchronized (getLock()) {
   584             ImmutableViewportMetrics currentMetrics = getViewportMetrics();
   586             Tab tab = Tabs.getInstance().getSelectedTab();
   588             RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
   589             RectF pageRect = RectUtils.scaleAndRound(cssPageRect, zoom);
   591             final ImmutableViewportMetrics newMetrics = currentMetrics
   592                 .setViewportOrigin(offsetX, offsetY)
   593                 .setZoomFactor(zoom)
   594                 .setPageRect(pageRect, cssPageRect)
   595                 .setIsRTL(tab.getIsRTL());
   596             // Since we have switched to displaying a different document, we need to update any
   597             // viewport-related state we have lying around. This includes mGeckoViewport and
   598             // mViewportMetrics. Usually this information is updated via handleViewportMessage
   599             // while we remain on the same document.
   600             post(new Runnable() {
   601                 @Override
   602                 public void run() {
   603                     mGeckoViewport = newMetrics;
   604                 }
   605             });
   607             setViewportMetrics(newMetrics);
   609             mView.setBackgroundColor(tab.getBackgroundColor());
   610             setZoomConstraints(tab.getZoomConstraints());
   612             // At this point, we have just switched to displaying a different document than we
   613             // we previously displaying. This means we need to abort any panning/zooming animations
   614             // that are in progress and send an updated display port request to browser.js as soon
   615             // as possible. The call to PanZoomController.abortAnimation accomplishes this by calling the
   616             // forceRedraw function, which sends the viewport to gecko. The display port request is
   617             // actually a full viewport update, which is fine because if browser.js has somehow moved to
   618             // be out of sync with this first-paint viewport, then we force them back in sync.
   619             abortPanZoomAnimation();
   621             // Indicate that the document is about to be composited so the
   622             // LayerView background can be removed.
   623             if (mView.getPaintState() == LayerView.PAINT_START) {
   624                 mView.setPaintState(LayerView.PAINT_BEFORE_FIRST);
   625             }
   626         }
   627         DisplayPortCalculator.resetPageState();
   628         mDrawTimingQueue.reset();
   630         mContentDocumentIsDisplayed = true;
   631     }
   633     /** The compositor invokes this function whenever it determines that the page rect
   634       * has changed (based on the information it gets from layout). If setFirstPaintViewport
   635       * is invoked on a frame, then this function will not be. For any given frame, this
   636       * function will be invoked before syncViewportInfo.
   637       */
   638     @WrapElementForJNI(allowMultithread = true)
   639     public void setPageRect(float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
   640         synchronized (getLock()) {
   641             RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
   642             float ourZoom = getViewportMetrics().zoomFactor;
   643             setPageRect(RectUtils.scale(cssPageRect, ourZoom), cssPageRect);
   644             // Here the page size of the document has changed, but the document being displayed
   645             // is still the same. Therefore, we don't need to send anything to browser.js; any
   646             // changes we need to make to the display port will get sent the next time we call
   647             // adjustViewport().
   648         }
   649     }
   651     /** The compositor invokes this function on every frame to figure out what part of the
   652       * page to display, and to inform Java of the current display port. Since it is called
   653       * on every frame, it needs to be ultra-fast.
   654       * It avoids taking any locks or allocating any objects. We keep around a
   655       * mCurrentViewTransform so we don't need to allocate a new ViewTransform
   656       * everytime we're called. NOTE: we might be able to return a ImmutableViewportMetrics
   657       * which would avoid the copy into mCurrentViewTransform.
   658       */
   659     @WrapElementForJNI(allowMultithread = true)
   660     public ViewTransform syncViewportInfo(int x, int y, int width, int height, float resolution, boolean layersUpdated) {
   661         // getViewportMetrics is thread safe so we don't need to synchronize.
   662         // We save the viewport metrics here, so we later use it later in
   663         // createFrame (which will be called by nsWindow::DrawWindowUnderlay on
   664         // the native side, by the compositor). The viewport
   665         // metrics can change between here and there, as it's accessed outside
   666         // of the compositor thread.
   667         mFrameMetrics = getViewportMetrics();
   669         mCurrentViewTransform.x = mFrameMetrics.viewportRectLeft;
   670         mCurrentViewTransform.y = mFrameMetrics.viewportRectTop;
   671         mCurrentViewTransform.scale = mFrameMetrics.zoomFactor;
   673         // Adjust the fixed layer margins so that overscroll subtracts from them.
   674         getFixedMargins(mFrameMetrics, mCurrentViewTransformMargins);
   675         mCurrentViewTransform.fixedLayerMarginLeft = mCurrentViewTransformMargins.left;
   676         mCurrentViewTransform.fixedLayerMarginTop = mCurrentViewTransformMargins.top;
   677         mCurrentViewTransform.fixedLayerMarginRight = mCurrentViewTransformMargins.right;
   678         mCurrentViewTransform.fixedLayerMarginBottom = mCurrentViewTransformMargins.bottom;
   680         // Offset the view transform so that it renders in the correct place.
   681         PointF offset = mFrameMetrics.getMarginOffset();
   682         mCurrentViewTransform.offsetX = offset.x;
   683         mCurrentViewTransform.offsetY = offset.y;
   685         mRootLayer.setPositionAndResolution(
   686             Math.round(x + mCurrentViewTransform.offsetX),
   687             Math.round(y + mCurrentViewTransform.offsetY),
   688             Math.round(x + width + mCurrentViewTransform.offsetX),
   689             Math.round(y + height + mCurrentViewTransform.offsetY),
   690             resolution);
   692         if (layersUpdated && mRecordDrawTimes) {
   693             // If we got a layers update, that means a draw finished. Check to see if the area drawn matches
   694             // one of our requested displayports; if it does calculate the draw time and notify the
   695             // DisplayPortCalculator
   696             DisplayPortMetrics drawn = new DisplayPortMetrics(x, y, x + width, y + height, resolution);
   697             long time = mDrawTimingQueue.findTimeFor(drawn);
   698             if (time >= 0) {
   699                 long now = SystemClock.uptimeMillis();
   700                 time = now - time;
   701                 mRecordDrawTimes = DisplayPortCalculator.drawTimeUpdate(time, width * height);
   702             }
   703         }
   705         if (layersUpdated) {
   706             for (DrawListener listener : mDrawListeners) {
   707                 listener.drawFinished();
   708             }
   709         }
   711         return mCurrentViewTransform;
   712     }
   714     @WrapElementForJNI(allowMultithread = true)
   715     public ViewTransform syncFrameMetrics(float offsetX, float offsetY, float zoom,
   716                 float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom,
   717                 boolean layersUpdated, int x, int y, int width, int height, float resolution,
   718                 boolean isFirstPaint)
   719     {
   720         if (isFirstPaint) {
   721             setFirstPaintViewport(offsetX, offsetY, zoom,
   722                                   cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
   723         }
   725         return syncViewportInfo(x, y, width, height, resolution, layersUpdated);
   726     }
   728     @WrapElementForJNI(allowMultithread = true)
   729     public LayerRenderer.Frame createFrame() {
   730         // Create the shaders and textures if necessary.
   731         if (!mLayerRendererInitialized) {
   732             mLayerRenderer.checkMonitoringEnabled();
   733             mLayerRenderer.createDefaultProgram();
   734             mLayerRendererInitialized = true;
   735         }
   737         try {
   738             return mLayerRenderer.createFrame(mFrameMetrics);
   739         } catch (Exception e) {
   740             Log.w(LOGTAG, e);
   741             return null;
   742         }
   743     }
   745     @WrapElementForJNI(allowMultithread = true)
   746     public void activateProgram() {
   747         mLayerRenderer.activateDefaultProgram();
   748     }
   750     @WrapElementForJNI(allowMultithread = true)
   751     public void deactivateProgram() {
   752         mLayerRenderer.deactivateDefaultProgram();
   753     }
   755     private void geometryChanged(DisplayPortMetrics displayPort) {
   756         /* Let Gecko know if the screensize has changed */
   757         sendResizeEventIfNecessary(false);
   758         if (getRedrawHint()) {
   759             adjustViewport(displayPort);
   760         }
   761     }
   763     /** Implementation of LayerView.Listener */
   764     @Override
   765     public void renderRequested() {
   766         try {
   767             GeckoAppShell.scheduleComposite();
   768         } catch (UnsupportedOperationException uoe) {
   769             // In some very rare cases this gets called before libxul is loaded,
   770             // so catch and ignore the exception that will throw. See bug 837821
   771             Log.d(LOGTAG, "Dropping renderRequested call before libxul load.");
   772         }
   773     }
   775     /** Implementation of LayerView.Listener */
   776     @Override
   777     public void sizeChanged(int width, int height) {
   778         // We need to make sure a draw happens synchronously at this point,
   779         // but resizing the surface before the SurfaceView has resized will
   780         // cause a visible jump.
   781         mView.getGLController().resumeCompositor(mWindowSize.width, mWindowSize.height);
   782     }
   784     /** Implementation of LayerView.Listener */
   785     @Override
   786     public void surfaceChanged(int width, int height) {
   787         setViewportSize(width, height);
   788     }
   790     /** Implementation of PanZoomTarget */
   791     @Override
   792     public ImmutableViewportMetrics getViewportMetrics() {
   793         return mViewportMetrics;
   794     }
   796     /** Implementation of PanZoomTarget */
   797     @Override
   798     public ZoomConstraints getZoomConstraints() {
   799         return mZoomConstraints;
   800     }
   802     /** Implementation of PanZoomTarget */
   803     @Override
   804     public boolean isFullScreen() {
   805         return mView.isFullScreen();
   806     }
   808     /** Implementation of PanZoomTarget */
   809     @Override
   810     public RectF getMaxMargins() {
   811         return mMarginsAnimator.getMaxMargins();
   812     }
   814     /** Implementation of PanZoomTarget */
   815     @Override
   816     public void setAnimationTarget(ImmutableViewportMetrics metrics) {
   817         if (mGeckoIsReady) {
   818             // We know what the final viewport of the animation is going to be, so
   819             // immediately request a draw of that area by setting the display port
   820             // accordingly. This way we should have the content pre-rendered by the
   821             // time the animation is done.
   822             DisplayPortMetrics displayPort = DisplayPortCalculator.calculate(metrics, null);
   823             adjustViewport(displayPort);
   824         }
   825     }
   827     /** Implementation of PanZoomTarget
   828      * You must hold the monitor while calling this.
   829      */
   830     @Override
   831     public void setViewportMetrics(ImmutableViewportMetrics metrics) {
   832         setViewportMetrics(metrics, true);
   833     }
   835     /*
   836      * You must hold the monitor while calling this.
   837      */
   838     private void setViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko) {
   839         // This class owns the viewport size and the fixed layer margins; don't let other pieces
   840         // of code clobber either of them. The only place the viewport size should ever be
   841         // updated is in GeckoLayerClient.setViewportSize, and the only place the margins should
   842         // ever be updated is in GeckoLayerClient.setFixedLayerMargins; both of these assign to
   843         // mViewportMetrics directly.
   844         metrics = metrics.setViewportSize(mViewportMetrics.getWidth(), mViewportMetrics.getHeight());
   845         metrics = metrics.setMarginsFrom(mViewportMetrics);
   846         mViewportMetrics = metrics;
   848         viewportMetricsChanged(notifyGecko);
   849     }
   851     /*
   852      * You must hold the monitor while calling this.
   853      */
   854     private void viewportMetricsChanged(boolean notifyGecko) {
   855         if (mViewportChangeListener != null) {
   856             mViewportChangeListener.onMetricsChanged(mViewportMetrics);
   857         }
   859         mView.requestRender();
   860         if (notifyGecko && mGeckoIsReady) {
   861             geometryChanged(null);
   862         }
   863     }
   865     /*
   866      * Updates the viewport metrics, overriding the viewport size and margins
   867      * which are normally retained when calling setViewportMetrics.
   868      * You must hold the monitor while calling this.
   869      */
   870     void forceViewportMetrics(ImmutableViewportMetrics metrics, boolean notifyGecko, boolean forceRedraw) {
   871         if (forceRedraw) {
   872             mForceRedraw = true;
   873         }
   874         mViewportMetrics = metrics;
   875         viewportMetricsChanged(notifyGecko);
   876     }
   878     /** Implementation of PanZoomTarget
   879      * Scroll the viewport by a certain amount. This will take viewport margins
   880      * and margin animation into account. If margins are currently animating,
   881      * this will just go ahead and modify the viewport origin, otherwise the
   882      * delta will be applied to the margins and the remainder will be applied to
   883      * the viewport origin.
   884      *
   885      * You must hold the monitor while calling this.
   886      */
   887     @Override
   888     public void scrollBy(float dx, float dy) {
   889         // Set mViewportMetrics manually so the margin changes take.
   890         mViewportMetrics = mMarginsAnimator.scrollBy(mViewportMetrics, dx, dy);
   891         viewportMetricsChanged(true);
   892     }
   894     /** Implementation of PanZoomTarget
   895      * Notification that a subdocument has been scrolled by a certain amount.
   896      * This is used here to make sure that the margins are still accessible
   897      * during subdocument scrolling.
   898      *
   899      * You must hold the monitor while calling this.
   900      */
   901     @Override
   902     public void scrollMarginsBy(float dx, float dy) {
   903         ImmutableViewportMetrics newMarginsMetrics =
   904             mMarginsAnimator.scrollBy(mViewportMetrics, dx, dy);
   905         mViewportMetrics = mViewportMetrics.setMarginsFrom(newMarginsMetrics);
   906         viewportMetricsChanged(true);
   907     }
   909     /** Implementation of PanZoomTarget */
   910     @Override
   911     public void panZoomStopped() {
   912         if (mViewportChangeListener != null) {
   913             mViewportChangeListener.onPanZoomStopped();
   914         }
   915     }
   917     /** Implementation of PanZoomTarget */
   918     @Override
   919     public void forceRedraw(DisplayPortMetrics displayPort) {
   920         mForceRedraw = true;
   921         if (mGeckoIsReady) {
   922             geometryChanged(displayPort);
   923         }
   924     }
   926     /** Implementation of PanZoomTarget */
   927     @Override
   928     public boolean post(Runnable action) {
   929         return mView.post(action);
   930     }
   932     /** Implementation of PanZoomTarget */
   933     @Override
   934     public void postRenderTask(RenderTask task) {
   935         mView.postRenderTask(task);
   936     }
   938     /** Implementation of PanZoomTarget */
   939     @Override
   940     public void removeRenderTask(RenderTask task) {
   941         mView.removeRenderTask(task);
   942     }
   945     /** Implementation of PanZoomTarget */
   946     @Override
   947     public boolean postDelayed(Runnable action, long delayMillis) {
   948         return mView.postDelayed(action, delayMillis);
   949     }
   951     /** Implementation of PanZoomTarget */
   952     @Override
   953     public Object getLock() {
   954         return this;
   955     }
   957     /** Implementation of PanZoomTarget
   958      * Converts a point from layer view coordinates to layer coordinates. In other words, given a
   959      * point measured in pixels from the top left corner of the layer view, returns the point in
   960      * pixels measured from the last scroll position we sent to Gecko, in CSS pixels. Assuming the
   961      * events being sent to Gecko are processed in FIFO order, this calculation should always be
   962      * correct.
   963      */
   964     @Override
   965     public PointF convertViewPointToLayerPoint(PointF viewPoint) {
   966         if (!mGeckoIsReady) {
   967             return null;
   968         }
   970         ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
   971         PointF origin = viewportMetrics.getOrigin();
   972         PointF offset = viewportMetrics.getMarginOffset();
   973         origin.offset(-offset.x, -offset.y);
   974         float zoom = viewportMetrics.zoomFactor;
   975         ImmutableViewportMetrics geckoViewport = mGeckoViewport;
   976         PointF geckoOrigin = geckoViewport.getOrigin();
   977         float geckoZoom = geckoViewport.zoomFactor;
   979         // viewPoint + origin - offset gives the coordinate in device pixels from the top-left corner of the page.
   980         // Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page.
   981         // geckoOrigin / geckoZoom is where Gecko thinks it is (scrollTo position) in CSS pixels from
   982         // the top-left corner of the page. Subtracting the two gives us the offset of the viewPoint from
   983         // the current Gecko coordinate in CSS pixels.
   984         PointF layerPoint = new PointF(
   985                 ((viewPoint.x + origin.x) / zoom) - (geckoOrigin.x / geckoZoom),
   986                 ((viewPoint.y + origin.y) / zoom) - (geckoOrigin.y / geckoZoom));
   988         return layerPoint;
   989     }
   991     void setOnMetricsChangedListener(LayerView.OnMetricsChangedListener listener) {
   992         mViewportChangeListener = listener;
   993     }
   995     public void addDrawListener(DrawListener listener) {
   996         mDrawListeners.add(listener);
   997     }
   999     public void removeDrawListener(DrawListener listener) {
  1000         mDrawListeners.remove(listener);

mercurial