EMMA Coverage Report (generated Fri Aug 23 16:39:17 PDT 2013)
[all classes][org.chromium.android_webview]

COVERAGE SUMMARY FOR SOURCE FILE [AwScrollOffsetManager.java]

nameclass, %method, %block, %line, %
AwScrollOffsetManager.java100% (1/1)94%  (29/31)88%  (565/642)91%  (141.4/156)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class AwScrollOffsetManager100% (1/1)94%  (29/31)88%  (565/642)91%  (141.4/156)
computeVerticalScrollExtent (): int 0%   (0/1)0%   (0/3)0%   (0/1)
onFlingCancelGesture (): void 0%   (0/1)0%   (0/5)0%   (0/2)
pageUp (boolean): boolean 100% (1/1)39%  (15/38)50%  (4/8)
pageDown (boolean): boolean 100% (1/1)43%  (16/37)50%  (4/8)
<static initializer> 100% (1/1)75%  (6/8)75%  (0.8/1)
setProcessingTouchEvent (boolean): void 100% (1/1)86%  (25/29)93%  (5.6/6)
requestChildRectangleOnScreen (int, int, Rect, boolean): boolean 100% (1/1)87%  (114/131)93%  (26/28)
animateScrollTo (int, int): boolean 100% (1/1)96%  (43/45)91%  (10/11)
AwScrollOffsetManager (AwScrollOffsetManager$Delegate, OverScroller): void 100% (1/1)100% (9/9)100% (4/4)
clampHorizontalScroll (int): int 100% (1/1)100% (11/11)100% (3/3)
clampVerticalScroll (int): int 100% (1/1)100% (11/11)100% (3/3)
computeDurationInMilliSec (int, int): int 100% (1/1)100% (16/16)100% (3/3)
computeHorizontalScrollOffset (): int 100% (1/1)100% (4/4)100% (1/1)
computeHorizontalScrollRange (): int 100% (1/1)100% (6/6)100% (1/1)
computeMaximumHorizontalScrollOffset (): int 100% (1/1)100% (6/6)100% (1/1)
computeMaximumVerticalScrollOffset (): int 100% (1/1)100% (6/6)100% (1/1)
computeScrollAndAbsorbGlow (OverScrollGlow): void 100% (1/1)100% (54/54)100% (13/13)
computeVerticalScrollOffset (): int 100% (1/1)100% (4/4)100% (1/1)
computeVerticalScrollRange (): int 100% (1/1)100% (6/6)100% (1/1)
flingScroll (int, int): void 100% (1/1)100% (29/29)100% (7/7)
onContainerViewOverScrolled (int, int, boolean, boolean): void 100% (1/1)100% (22/22)100% (5/5)
onContainerViewScrollChanged (int, int): void 100% (1/1)100% (5/5)100% (2/2)
onFlingStartGesture (int, int): void 100% (1/1)100% (7/7)100% (3/3)
onUnhandledFlingStartEvent (): void 100% (1/1)100% (9/9)100% (2/2)
overScrollBy (int, int): void 100% (1/1)100% (5/5)100% (2/2)
scrollBy (int, int): void 100% (1/1)100% (31/31)100% (7/7)
scrollContainerViewTo (int, int): void 100% (1/1)100% (40/40)100% (10/10)
scrollNativeTo (int, int): void 100% (1/1)100% (42/42)100% (13/13)
setContainerViewSize (int, int): void 100% (1/1)100% (7/7)100% (3/3)
setContentSize (int, int): void 100% (1/1)100% (7/7)100% (3/3)
syncScrollOffsetFromOnDraw (): void 100% (1/1)100% (9/9)100% (2/2)

1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4 
5package org.chromium.android_webview;
6 
7import android.graphics.Rect;
8import android.widget.OverScroller;
9 
10import com.google.common.annotations.VisibleForTesting;
11 
12import org.chromium.base.CalledByNative;
13 
14/**
15 * Takes care of syncing the scroll offset between the Android View system and the
16 * InProcessViewRenderer.
17 *
18 * Unless otherwise values (sizes, scroll offsets) are in physical pixels.
19 */
20@VisibleForTesting
21public class AwScrollOffsetManager {
22    // Values taken from WebViewClassic.
23 
24    // The amount of content to overlap between two screens when using pageUp/pageDown methiods.
25    private static final int PAGE_SCROLL_OVERLAP = 24;
26    // Standard animated scroll speed.
27    private static final int STD_SCROLL_ANIMATION_SPEED_PIX_PER_SEC = 480;
28    // Time for the longest scroll animation.
29    private static final int MAX_SCROLL_ANIMATION_DURATION_MILLISEC = 750;
30 
31    // The unit of all the values in this delegate are physical pixels.
32    public interface Delegate {
33        // Call View#overScrollBy on the containerView.
34        void overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY,
35                int scrollRangeX, int scrollRangeY, boolean isTouchEvent);
36        // Call View#scrollTo on the containerView.
37        void scrollContainerViewTo(int x, int y);
38        // Store the scroll offset in the native side. This should really be a simple store
39        // operation, the native side shouldn't synchronously alter the scroll offset from within
40        // this call.
41        void scrollNativeTo(int x, int y);
42 
43        int getContainerViewScrollX();
44        int getContainerViewScrollY();
45 
46        void invalidate();
47    }
48 
49    private final Delegate mDelegate;
50 
51    // Scroll offset as seen by the native side.
52    private int mNativeScrollX;
53    private int mNativeScrollY;
54 
55    // Content size.
56    private int mContentWidth;
57    private int mContentHeight;
58 
59    // Size of the container view.
60    private int mContainerViewWidth;
61    private int mContainerViewHeight;
62 
63    // Whether we're in the middle of processing a touch event.
64    private boolean mProcessingTouchEvent;
65 
66    // Whether (and to what value) to update the native side scroll offset after we've finished
67    // provessing a touch event.
68    private boolean mApplyDeferredNativeScroll;
69    private int mDeferredNativeScrollX;
70    private int mDeferredNativeScrollY;
71 
72    // The velocity of the last recorded fling,
73    private int mLastFlingVelocityX;
74    private int mLastFlingVelocityY;
75 
76    private OverScroller mScroller;
77 
78    public AwScrollOffsetManager(Delegate delegate, OverScroller overScroller) {
79        mDelegate = delegate;
80        mScroller = overScroller;
81    }
82 
83    //----- Scroll range and extent calculation methods -------------------------------------------
84 
85    public int computeHorizontalScrollRange() {
86        return Math.max(mContainerViewWidth, mContentWidth);
87    }
88 
89    public int computeMaximumHorizontalScrollOffset() {
90        return computeHorizontalScrollRange() - mContainerViewWidth;
91    }
92 
93    public int computeHorizontalScrollOffset() {
94        return mDelegate.getContainerViewScrollX();
95    }
96 
97    public int computeVerticalScrollRange() {
98        return Math.max(mContainerViewHeight, mContentHeight);
99    }
100 
101    public int computeMaximumVerticalScrollOffset() {
102        return computeVerticalScrollRange() - mContainerViewHeight;
103    }
104 
105    public int computeVerticalScrollOffset() {
106        return mDelegate.getContainerViewScrollY();
107    }
108 
109    public int computeVerticalScrollExtent() {
110        return mContainerViewHeight;
111    }
112 
113    //---------------------------------------------------------------------------------------------
114 
115    // Called when the content size changes. This needs to be the size of the on-screen content and
116    // therefore we can't use the WebContentsDelegate preferred size.
117    public void setContentSize(int width, int height) {
118        mContentWidth = width;
119        mContentHeight = height;
120    }
121 
122    // Called when the physical size of the view changes.
123    public void setContainerViewSize(int width, int height) {
124        mContainerViewWidth = width;
125        mContainerViewHeight = height;
126    }
127 
128    public void syncScrollOffsetFromOnDraw() {
129        // Unfortunately apps override onScrollChanged without calling super which is why we need
130        // to sync the scroll offset on every onDraw.
131        onContainerViewScrollChanged(mDelegate.getContainerViewScrollX(),
132                mDelegate.getContainerViewScrollY());
133    }
134 
135    public void setProcessingTouchEvent(boolean processingTouchEvent) {
136        assert mProcessingTouchEvent != processingTouchEvent;
137        mProcessingTouchEvent = processingTouchEvent;
138 
139        if (!mProcessingTouchEvent && mApplyDeferredNativeScroll) {
140            mApplyDeferredNativeScroll = false;
141            scrollNativeTo(mDeferredNativeScrollX, mDeferredNativeScrollY);
142        }
143    }
144 
145    // Called by the native side to attempt to scroll the container view.
146    public void scrollContainerViewTo(int x, int y) {
147        mNativeScrollX = x;
148        mNativeScrollY = y;
149 
150        final int scrollX = mDelegate.getContainerViewScrollX();
151        final int scrollY = mDelegate.getContainerViewScrollY();
152        final int deltaX = x - scrollX;
153        final int deltaY = y - scrollY;
154        final int scrollRangeX = computeMaximumHorizontalScrollOffset();
155        final int scrollRangeY = computeMaximumVerticalScrollOffset();
156 
157        // We use overScrollContainerViewBy to be compatible with WebViewClassic which used this
158        // method for handling both over-scroll as well as in-bounds scroll.
159        mDelegate.overScrollContainerViewBy(deltaX, deltaY, scrollX, scrollY,
160                scrollRangeX, scrollRangeY, mProcessingTouchEvent);
161    }
162 
163    // Called by the native side to over-scroll the container view.
164    public void overScrollBy(int deltaX, int deltaY) {
165        // TODO(mkosiba): Once http://crbug.com/260663 and http://crbug.com/261239 are fixed it
166        // should be possible to uncomment the following asserts:
167        // if (deltaX < 0) assert mDelegate.getContainerViewScrollX() == 0;
168        // if (deltaX > 0) assert mDelegate.getContainerViewScrollX() ==
169        //          computeMaximumHorizontalScrollOffset();
170        scrollBy(deltaX, deltaY);
171    }
172 
173    private void scrollBy(int deltaX, int deltaY) {
174        if (deltaX == 0 && deltaY == 0) return;
175 
176        final int scrollX = mDelegate.getContainerViewScrollX();
177        final int scrollY = mDelegate.getContainerViewScrollY();
178        final int scrollRangeX = computeMaximumHorizontalScrollOffset();
179        final int scrollRangeY = computeMaximumVerticalScrollOffset();
180 
181        // The android.view.View.overScrollBy method is used for both scrolling and over-scrolling
182        // which is why we use it here.
183        mDelegate.overScrollContainerViewBy(deltaX, deltaY, scrollX, scrollY,
184                scrollRangeX, scrollRangeY, mProcessingTouchEvent);
185    }
186 
187    private int clampHorizontalScroll(int scrollX) {
188        scrollX = Math.max(0, scrollX);
189        scrollX = Math.min(computeMaximumHorizontalScrollOffset(), scrollX);
190        return scrollX;
191    }
192 
193    private int clampVerticalScroll(int scrollY) {
194        scrollY = Math.max(0, scrollY);
195        scrollY = Math.min(computeMaximumVerticalScrollOffset(), scrollY);
196        return scrollY;
197    }
198 
199    // Called by the View system as a response to the mDelegate.overScrollContainerViewBy call.
200    public void onContainerViewOverScrolled(int scrollX, int scrollY, boolean clampedX,
201            boolean clampedY) {
202        // Clamp the scroll offset at (0, max).
203        scrollX = clampHorizontalScroll(scrollX);
204        scrollY = clampVerticalScroll(scrollY);
205 
206        mDelegate.scrollContainerViewTo(scrollX, scrollY);
207 
208        // This is only necessary if the containerView scroll offset ends up being different
209        // than the one set from native in which case we want the value stored on the native side
210        // to reflect the value stored in the containerView (and not the other way around).
211        scrollNativeTo(mDelegate.getContainerViewScrollX(), mDelegate.getContainerViewScrollY());
212    }
213 
214    // Called by the View system when the scroll offset had changed. This might not get called if
215    // the embedder overrides WebView#onScrollChanged without calling super.onScrollChanged. If
216    // this method does get called it is called both as a response to the embedder scrolling the
217    // view as well as a response to mDelegate.scrollContainerViewTo.
218    public void onContainerViewScrollChanged(int x, int y) {
219        scrollNativeTo(x, y);
220    }
221 
222    private void scrollNativeTo(int x, int y) {
223        x = clampHorizontalScroll(x);
224        y = clampVerticalScroll(y);
225 
226        // We shouldn't do the store to native while processing a touch event since that confuses
227        // the gesture processing logic.
228        if (mProcessingTouchEvent) {
229            mDeferredNativeScrollX = x;
230            mDeferredNativeScrollY = y;
231            mApplyDeferredNativeScroll = true;
232            return;
233        }
234 
235        if (x == mNativeScrollX && y == mNativeScrollY)
236            return;
237 
238        // The scrollNativeTo call should be a simple store, so it's OK to assume it always
239        // succeeds.
240        mNativeScrollX = x;
241        mNativeScrollY = y;
242 
243        mDelegate.scrollNativeTo(x, y);
244    }
245 
246    // Called at the beginning of every fling gesture.
247    public void onFlingStartGesture(int velocityX, int velocityY) {
248        mLastFlingVelocityX = velocityX;
249        mLastFlingVelocityY = velocityY;
250    }
251 
252    // Called whenever some other touch interaction requires the fling gesture to be canceled.
253    public void onFlingCancelGesture() {
254        // TODO(mkosiba): Support speeding up a fling by flinging again.
255        // http://crbug.com/265841
256        mScroller.forceFinished(true);
257    }
258 
259    // Called when a fling gesture is not handled by the renderer.
260    // We explicitly ask the renderer not to handle fling gestures targeted at the root
261    // scroll layer.
262    public void onUnhandledFlingStartEvent() {
263        flingScroll(-mLastFlingVelocityX, -mLastFlingVelocityY);
264    }
265 
266    // Starts the fling animation. Called both as a response to a fling gesture and as via the
267    // public WebView#flingScroll(int, int) API.
268    public void flingScroll(int velocityX, int velocityY) {
269        final int scrollX = mDelegate.getContainerViewScrollX();
270        final int scrollY = mDelegate.getContainerViewScrollY();
271        final int rangeX = computeMaximumHorizontalScrollOffset();
272        final int rangeY = computeMaximumVerticalScrollOffset();
273 
274        mScroller.fling(scrollX, scrollY, velocityX, velocityY,
275                0, rangeX, 0, rangeY);
276        mDelegate.invalidate();
277    }
278 
279    // Called immediately before the draw to update the scroll offset.
280    public void computeScrollAndAbsorbGlow(OverScrollGlow overScrollGlow) {
281        final boolean stillAnimating = mScroller.computeScrollOffset();
282        if (!stillAnimating) return;
283 
284        final int oldX = mDelegate.getContainerViewScrollX();
285        final int oldY = mDelegate.getContainerViewScrollY();
286        int x = mScroller.getCurrX();
287        int y = mScroller.getCurrY();
288 
289        int rangeX = computeMaximumHorizontalScrollOffset();
290        int rangeY = computeMaximumVerticalScrollOffset();
291 
292        if (overScrollGlow != null) {
293            overScrollGlow.absorbGlow(x, y, oldX, oldY, rangeX, rangeY,
294                    mScroller.getCurrVelocity());
295        }
296 
297        // The mScroller is configured not to go outside of the scrollable range, so this call
298        // should never result in attempting to scroll outside of the scrollable region.
299        scrollBy(x - oldX, y - oldY);
300 
301        mDelegate.invalidate();
302    }
303 
304    private static int computeDurationInMilliSec(int dx, int dy) {
305        int distance = Math.max(Math.abs(dx), Math.abs(dy));
306        int duration = distance * 1000 / STD_SCROLL_ANIMATION_SPEED_PIX_PER_SEC;
307        return Math.min(duration, MAX_SCROLL_ANIMATION_DURATION_MILLISEC);
308    }
309 
310    private boolean animateScrollTo(int x, int y) {
311        final int scrollX = mDelegate.getContainerViewScrollX();
312        final int scrollY = mDelegate.getContainerViewScrollY();
313 
314        x = clampHorizontalScroll(x);
315        y = clampVerticalScroll(y);
316 
317        int dx = x - scrollX;
318        int dy = y - scrollY;
319 
320        if (dx == 0 && dy == 0)
321            return false;
322 
323        mScroller.startScroll(scrollX, scrollY, dx, dy, computeDurationInMilliSec(dx, dy));
324        mDelegate.invalidate();
325 
326        return true;
327    }
328 
329    /**
330     * See {@link WebView#pageUp(boolean)}
331     */
332    public boolean pageUp(boolean top) {
333        final int scrollX = mDelegate.getContainerViewScrollX();
334        final int scrollY = mDelegate.getContainerViewScrollY();
335 
336        if (top) {
337            // go to the top of the document
338            return animateScrollTo(scrollX, 0);
339        }
340        int dy = -mContainerViewHeight / 2;
341        if (mContainerViewHeight > 2 * PAGE_SCROLL_OVERLAP) {
342            dy = -mContainerViewHeight + PAGE_SCROLL_OVERLAP;
343        }
344        // animateScrollTo clamps the argument to the scrollable range so using (scrollY + dy) is
345        // fine.
346        return animateScrollTo(scrollX, scrollY + dy);
347    }
348 
349    /**
350     * See {@link WebView#pageDown(boolean)}
351     */
352    public boolean pageDown(boolean bottom) {
353        final int scrollX = mDelegate.getContainerViewScrollX();
354        final int scrollY = mDelegate.getContainerViewScrollY();
355 
356        if (bottom) {
357            return animateScrollTo(scrollX, computeVerticalScrollRange());
358        }
359        int dy = mContainerViewHeight / 2;
360        if (mContainerViewHeight > 2 * PAGE_SCROLL_OVERLAP) {
361            dy = mContainerViewHeight - PAGE_SCROLL_OVERLAP;
362        }
363        // animateScrollTo clamps the argument to the scrollable range so using (scrollY + dy) is
364        // fine.
365        return animateScrollTo(scrollX, scrollY + dy);
366    }
367 
368    /**
369     * See {@link WebView#requestChildRectangleOnScreen(View, Rect, boolean)}
370     */
371    public boolean requestChildRectangleOnScreen(int childOffsetX, int childOffsetY, Rect rect,
372            boolean immediate) {
373        // TODO(mkosiba): WebViewClassic immediately returns false if a zoom animation is
374        // in progress. We currently can't tell if one is happening.. should we instead cancel any
375        // scroll animation when the size/pageScaleFactor changes?
376 
377        // TODO(mkosiba): Take scrollbar width into account in the screenRight/screenBotton
378        // calculations. http://crbug.com/269032
379 
380        final int scrollX = mDelegate.getContainerViewScrollX();
381        final int scrollY = mDelegate.getContainerViewScrollY();
382 
383        rect.offset(childOffsetX, childOffsetY);
384 
385        int screenTop = scrollY;
386        int screenBottom = scrollY + mContainerViewHeight;
387        int scrollYDelta = 0;
388 
389        if (rect.bottom > screenBottom) {
390            int oneThirdOfScreenHeight = mContainerViewHeight / 3;
391            if (rect.width() > 2 * oneThirdOfScreenHeight) {
392                // If the rectangle is too tall to fit in the bottom two thirds
393                // of the screen, place it at the top.
394                scrollYDelta = rect.top - screenTop;
395            } else {
396                // If the rectangle will still fit on screen, we want its
397                // top to be in the top third of the screen.
398                scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight);
399            }
400        } else if (rect.top < screenTop) {
401            scrollYDelta = rect.top - screenTop;
402        }
403 
404        int screenLeft = scrollX;
405        int screenRight = scrollX + mContainerViewWidth;
406        int scrollXDelta = 0;
407 
408        if (rect.right > screenRight && rect.left > screenLeft) {
409            if (rect.width() > mContainerViewWidth) {
410                scrollXDelta += (rect.left - screenLeft);
411            } else {
412                scrollXDelta += (rect.right - screenRight);
413            }
414        } else if (rect.left < screenLeft) {
415            scrollXDelta -= (screenLeft - rect.left);
416        }
417 
418        if (scrollYDelta == 0 && scrollXDelta == 0) {
419            return false;
420        }
421 
422        if (immediate) {
423            scrollBy(scrollXDelta, scrollYDelta);
424            return true;
425        } else {
426            return animateScrollTo(scrollX + scrollXDelta, scrollY + scrollYDelta);
427        }
428    }
429}

[all classes][org.chromium.android_webview]
EMMA 2.0.5312 (C) Vladimir Roubtsov