mobile/android/base/gfx/GeckoLayerClient.java

branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
equal deleted inserted replaced
-1:000000000000 0:85dd70a6f46c
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/. */
5
6 package org.mozilla.gecko.gfx;
7
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;
18
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;
25
26 import java.util.ArrayList;
27 import java.util.List;
28
29 class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
30 {
31 private static final String LOGTAG = "GeckoLayerClient";
32
33 private LayerRenderer mLayerRenderer;
34 private boolean mLayerRendererInitialized;
35
36 private Context mContext;
37 private IntSize mScreenSize;
38 private IntSize mWindowSize;
39 private DisplayPortMetrics mDisplayPort;
40
41 private boolean mRecordDrawTimes;
42 private final DrawTimingQueue mDrawTimingQueue;
43
44 private VirtualLayer mRootLayer;
45
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;
55
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;
61
62 private List<DrawListener> mDrawListeners;
63
64 /* Used as temporaries by syncViewportInfo */
65 private final ViewTransform mCurrentViewTransform;
66 private final RectF mCurrentViewTransformMargins;
67
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;
73
74 private boolean mForceRedraw;
75
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;
90
91 private ZoomConstraints mZoomConstraints;
92
93 private boolean mGeckoIsReady;
94
95 private final PanZoomController mPanZoomController;
96 private final LayerMarginsAnimator mMarginsAnimator;
97 private LayerView mView;
98
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;
107
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;
123
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);
129
130 Tab tab = Tabs.getInstance().getSelectedTab();
131 if (tab != null) {
132 mZoomConstraints = tab.getZoomConstraints();
133 mViewportMetrics = mViewportMetrics.setIsRTL(tab.getIsRTL());
134 }
135
136 mFrameMetrics = mViewportMetrics;
137
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 }
145
146 public void setOverscrollHandler(final Overscroll listener) {
147 mPanZoomController.setOverscrollHandler(listener);
148 }
149
150 /** Attaches to root layer so that Gecko appears. */
151 public void notifyGeckoReady() {
152 mGeckoIsReady = true;
153
154 mRootLayer = new VirtualLayer(new IntSize(mView.getWidth(), mView.getHeight()));
155 mLayerRenderer = mView.getRenderer();
156
157 sendResizeEventIfNecessary(true);
158
159 DisplayPortCalculator.initPrefs();
160
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 }
173
174 public void destroy() {
175 mPanZoomController.destroy();
176 mMarginsAnimator.destroy();
177 mDrawListeners.clear();
178 }
179
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 }
189
190 if (!mPanZoomController.getRedrawHint()) {
191 return false;
192 }
193
194 return DisplayPortCalculator.aboutToCheckerboard(mViewportMetrics,
195 mPanZoomController.getVelocityVector(), mDisplayPort);
196 }
197
198 Layer getRoot() {
199 return mGeckoIsReady ? mRootLayer : null;
200 }
201
202 public LayerView getView() {
203 return mView;
204 }
205
206 public FloatSize getViewportSize() {
207 return mViewportMetrics.getSize();
208 }
209
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);
220
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 }
232
233 PanZoomController getPanZoomController() {
234 return mPanZoomController;
235 }
236
237 LayerMarginsAnimator getLayerMarginsAnimator() {
238 return mMarginsAnimator;
239 }
240
241 /* Informs Gecko that the screen size has changed. */
242 private void sendResizeEventIfNecessary(boolean force) {
243 DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
244
245 IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
246 IntSize newWindowSize = new IntSize(mView.getWidth(), mView.getHeight());
247
248 boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
249 boolean windowSizeChanged = !mWindowSize.equals(newWindowSize);
250
251 if (!force && !screenSizeChanged && !windowSizeChanged) {
252 return;
253 }
254
255 mScreenSize = newScreenSize;
256 mWindowSize = newWindowSize;
257
258 if (screenSizeChanged) {
259 Log.d(LOGTAG, "Screen-size changed to " + mScreenSize);
260 }
261
262 if (windowSizeChanged) {
263 Log.d(LOGTAG, "Window-size changed to " + mWindowSize);
264 }
265
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 }
271
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;
279
280 mViewportMetrics = mViewportMetrics.setPageRect(rect, cssRect);
281
282 // Page size is owned by the layer client, so no need to notify it of
283 // this change.
284
285 post(new Runnable() {
286 @Override
287 public void run() {
288 mPanZoomController.pageRectUpdated();
289 mView.requestRender();
290 }
291 });
292 }
293
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;
303
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());
307
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 }
316
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 }
329
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 }
348
349 private void adjustViewport(DisplayPortMetrics displayPort) {
350 ImmutableViewportMetrics metrics = getViewportMetrics();
351 ImmutableViewportMetrics clampedMetrics = metrics.clamp();
352
353 RectF margins = new RectF();
354 getFixedMargins(metrics, margins);
355 clampedMetrics = clampedMetrics.setMargins(
356 margins.left, margins.top, margins.right, margins.bottom);
357
358 if (displayPort == null) {
359 displayPort = DisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector());
360 }
361
362 mDisplayPort = displayPort;
363 mGeckoViewport = clampedMetrics;
364
365 if (mRecordDrawTimes) {
366 mDrawTimingQueue.add(displayPort);
367 }
368
369 GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(clampedMetrics, displayPort));
370 }
371
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 }
383
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 }
393
394 /** Viewport message handler. */
395 private DisplayPortMetrics handleViewportMessage(ImmutableViewportMetrics messageMetrics, ViewportMessageType type) {
396 synchronized (getLock()) {
397 ImmutableViewportMetrics newMetrics;
398 ImmutableViewportMetrics oldMetrics = getViewportMetrics();
399
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 }
417
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 });
427
428 setViewportMetrics(newMetrics, type == ViewportMessageType.UPDATE);
429 mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null);
430 }
431 return mDisplayPort;
432 }
433
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 }
450
451 @WrapElementForJNI
452 void contentDocumentChanged() {
453 mContentDocumentIsDisplayed = false;
454 }
455
456 @WrapElementForJNI
457 boolean isContentDocumentDisplayed() {
458 return mContentDocumentIsDisplayed;
459 }
460
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;
481
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;
488
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 }
496
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 }
509
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 }
518
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.
524
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 }
535
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;
547
548 // Enable low-precision drawing, as we're likely to be in danger if
549 // this situation has been encountered.
550 mProgressiveUpdateWasInDanger = true;
551
552 return mProgressiveUpdateData;
553 }
554
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 }
562
563 void setZoomConstraints(ZoomConstraints constraints) {
564 mZoomConstraints = constraints;
565 }
566
567 void setIsRTL(boolean aIsRTL) {
568 synchronized (getLock()) {
569 ImmutableViewportMetrics newMetrics = getViewportMetrics().setIsRTL(aIsRTL);
570 setViewportMetrics(newMetrics, false);
571 }
572 }
573
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();
585
586 Tab tab = Tabs.getInstance().getSelectedTab();
587
588 RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
589 RectF pageRect = RectUtils.scaleAndRound(cssPageRect, zoom);
590
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 });
606
607 setViewportMetrics(newMetrics);
608
609 mView.setBackgroundColor(tab.getBackgroundColor());
610 setZoomConstraints(tab.getZoomConstraints());
611
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();
620
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();
629
630 mContentDocumentIsDisplayed = true;
631 }
632
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 }
650
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();
668
669 mCurrentViewTransform.x = mFrameMetrics.viewportRectLeft;
670 mCurrentViewTransform.y = mFrameMetrics.viewportRectTop;
671 mCurrentViewTransform.scale = mFrameMetrics.zoomFactor;
672
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;
679
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;
684
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);
691
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 }
704
705 if (layersUpdated) {
706 for (DrawListener listener : mDrawListeners) {
707 listener.drawFinished();
708 }
709 }
710
711 return mCurrentViewTransform;
712 }
713
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 }
724
725 return syncViewportInfo(x, y, width, height, resolution, layersUpdated);
726 }
727
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 }
736
737 try {
738 return mLayerRenderer.createFrame(mFrameMetrics);
739 } catch (Exception e) {
740 Log.w(LOGTAG, e);
741 return null;
742 }
743 }
744
745 @WrapElementForJNI(allowMultithread = true)
746 public void activateProgram() {
747 mLayerRenderer.activateDefaultProgram();
748 }
749
750 @WrapElementForJNI(allowMultithread = true)
751 public void deactivateProgram() {
752 mLayerRenderer.deactivateDefaultProgram();
753 }
754
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 }
762
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 }
774
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 }
783
784 /** Implementation of LayerView.Listener */
785 @Override
786 public void surfaceChanged(int width, int height) {
787 setViewportSize(width, height);
788 }
789
790 /** Implementation of PanZoomTarget */
791 @Override
792 public ImmutableViewportMetrics getViewportMetrics() {
793 return mViewportMetrics;
794 }
795
796 /** Implementation of PanZoomTarget */
797 @Override
798 public ZoomConstraints getZoomConstraints() {
799 return mZoomConstraints;
800 }
801
802 /** Implementation of PanZoomTarget */
803 @Override
804 public boolean isFullScreen() {
805 return mView.isFullScreen();
806 }
807
808 /** Implementation of PanZoomTarget */
809 @Override
810 public RectF getMaxMargins() {
811 return mMarginsAnimator.getMaxMargins();
812 }
813
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 }
826
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 }
834
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;
847
848 viewportMetricsChanged(notifyGecko);
849 }
850
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 }
858
859 mView.requestRender();
860 if (notifyGecko && mGeckoIsReady) {
861 geometryChanged(null);
862 }
863 }
864
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 }
877
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 }
893
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 }
908
909 /** Implementation of PanZoomTarget */
910 @Override
911 public void panZoomStopped() {
912 if (mViewportChangeListener != null) {
913 mViewportChangeListener.onPanZoomStopped();
914 }
915 }
916
917 /** Implementation of PanZoomTarget */
918 @Override
919 public void forceRedraw(DisplayPortMetrics displayPort) {
920 mForceRedraw = true;
921 if (mGeckoIsReady) {
922 geometryChanged(displayPort);
923 }
924 }
925
926 /** Implementation of PanZoomTarget */
927 @Override
928 public boolean post(Runnable action) {
929 return mView.post(action);
930 }
931
932 /** Implementation of PanZoomTarget */
933 @Override
934 public void postRenderTask(RenderTask task) {
935 mView.postRenderTask(task);
936 }
937
938 /** Implementation of PanZoomTarget */
939 @Override
940 public void removeRenderTask(RenderTask task) {
941 mView.removeRenderTask(task);
942 }
943
944
945 /** Implementation of PanZoomTarget */
946 @Override
947 public boolean postDelayed(Runnable action, long delayMillis) {
948 return mView.postDelayed(action, delayMillis);
949 }
950
951 /** Implementation of PanZoomTarget */
952 @Override
953 public Object getLock() {
954 return this;
955 }
956
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 }
969
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;
978
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));
987
988 return layerPoint;
989 }
990
991 void setOnMetricsChangedListener(LayerView.OnMetricsChangedListener listener) {
992 mViewportChangeListener = listener;
993 }
994
995 public void addDrawListener(DrawListener listener) {
996 mDrawListeners.add(listener);
997 }
998
999 public void removeDrawListener(DrawListener listener) {
1000 mDrawListeners.remove(listener);
1001 }
1002 }

mercurial