AwScrollOffsetManager.java revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
15b955920c1d8f2cd35aae3c85b656578286a8bc1Anders Carlsson// Copyright 2013 The Chromium Authors. All rights reserved. 25d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson// Use of this source code is governed by a BSD-style license that can be 35d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson// found in the LICENSE file. 45d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson 55d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlssonpackage org.chromium.android_webview; 65d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson 75d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlssonimport android.graphics.Rect; 85d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlssonimport android.widget.OverScroller; 95d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson 105d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlssonimport com.google.common.annotations.VisibleForTesting; 115d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson 125d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson/** 135d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson * Takes care of syncing the scroll offset between the Android View system and the 1464bee65a3436e3f0c352fcfe2130676f3502cffeEli Friedman * InProcessViewRenderer. 15d67ef0eed463b43980f04a444155f423114be34bDevang Patel * 1656c00c4868831c9a137ca7b0e16d063cf986d110Lang Hames * Unless otherwise values (sizes, scroll offsets) are in physical pixels. 175d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson */ 182f1986b557fa671c4f8c9dd0d071398edfc073d5Anders Carlsson@VisibleForTesting 197e1dff7a68a4d00e71debafa7f5c259473091746John McCallpublic class AwScrollOffsetManager { 205d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson // Values taken from WebViewClassic. 219fc6a7774643a810c8501dae2323e863fefb623eJohn McCall 2256c00c4868831c9a137ca7b0e16d063cf986d110Lang Hames // The amount of content to overlap between two screens when using pageUp/pageDown methiods. 233ee36af5bbb8c2cd203a140c3785215539cd56b4Devang Patel private static final int PAGE_SCROLL_OVERLAP = 24; 242f1986b557fa671c4f8c9dd0d071398edfc073d5Anders Carlsson // Standard animated scroll speed. 255d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson private static final int STD_SCROLL_ANIMATION_SPEED_PIX_PER_SEC = 480; 265d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson // Time for the longest scroll animation. 275d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson private static final int MAX_SCROLL_ANIMATION_DURATION_MILLISEC = 750; 2855c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck 2934a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson /** 3034a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson * The interface that all users of AwScrollOffsetManager should implement. 31f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall * 32f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall * The unit of all the values in this delegate are physical pixels. 3355c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck */ 3434a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson public interface Delegate { 3534a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // Call View#overScrollBy on the containerView. 3634a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson void overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY, 37f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall int scrollRangeX, int scrollRangeY, boolean isTouchEvent); 3834a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // Call View#scrollTo on the containerView. 3934a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson void scrollContainerViewTo(int x, int y); 4034a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // Store the scroll offset in the native side. This should really be a simple store 4134a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // operation, the native side shouldn't synchronously alter the scroll offset from within 4234a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // this call. 4334a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson void scrollNativeTo(int x, int y); 4434a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson 4534a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson int getContainerViewScrollX(); 4634a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson int getContainerViewScrollY(); 4734a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson 4855c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck void invalidate(); 4934a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson } 5034a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson 5134a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson private final Delegate mDelegate; 5234a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson 5355c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck // Scroll offset as seen by the native side. 5434a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson private int mNativeScrollX; 555d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson private int mNativeScrollY; 5684080ec16ede6a6fe85a1d991690c6bda82a59eeAnders Carlsson 57a04efdf635d35d88e65041fad007225d8c8d64a5Anders Carlsson // How many pixels can we scroll in a given direction. 58f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall private int mMaxHorizontalScrollOffset; 59f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall private int mMaxVerticalScrollOffset; 60f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall 61a04efdf635d35d88e65041fad007225d8c8d64a5Anders Carlsson // Size of the container view. 6255c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck private int mContainerViewWidth; 63f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall private int mContainerViewHeight; 64f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall 6555c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck // Whether we're in the middle of processing a touch event. 66a04efdf635d35d88e65041fad007225d8c8d64a5Anders Carlsson private boolean mProcessingTouchEvent; 67a04efdf635d35d88e65041fad007225d8c8d64a5Anders Carlsson 682acc6e3feda5e4f7d9009bdcf8b1cd777fecfe2dChris Lattner private boolean mFlinging; 69a04efdf635d35d88e65041fad007225d8c8d64a5Anders Carlsson 70a04efdf635d35d88e65041fad007225d8c8d64a5Anders Carlsson // Whether (and to what value) to update the native side scroll offset after we've finished 7155c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck // processing a touch event. 7284080ec16ede6a6fe85a1d991690c6bda82a59eeAnders Carlsson private boolean mApplyDeferredNativeScroll; 7384080ec16ede6a6fe85a1d991690c6bda82a59eeAnders Carlsson private int mDeferredNativeScrollX; 748561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson private int mDeferredNativeScrollY; 75bff225ecf77fb891596ecb1b27196310d268365eJohn McCall 76bff225ecf77fb891596ecb1b27196310d268365eJohn McCall private OverScroller mScroller; 77bff225ecf77fb891596ecb1b27196310d268365eJohn McCall 78bff225ecf77fb891596ecb1b27196310d268365eJohn McCall public AwScrollOffsetManager(Delegate delegate, OverScroller overScroller) { 79bff225ecf77fb891596ecb1b27196310d268365eJohn McCall mDelegate = delegate; 808561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson mScroller = overScroller; 818561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson } 828561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson 838561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson //----- Scroll range and extent calculation methods ------------------------------------------- 84bff225ecf77fb891596ecb1b27196310d268365eJohn McCall 85bff225ecf77fb891596ecb1b27196310d268365eJohn McCall public int computeHorizontalScrollRange() { 86bff225ecf77fb891596ecb1b27196310d268365eJohn McCall return mContainerViewWidth + mMaxHorizontalScrollOffset; 87bff225ecf77fb891596ecb1b27196310d268365eJohn McCall } 88bff225ecf77fb891596ecb1b27196310d268365eJohn McCall 89bff225ecf77fb891596ecb1b27196310d268365eJohn McCall public int computeMaximumHorizontalScrollOffset() { 905fff46b65389f7e7eb576e47c7bc3ca67326a206Ken Dyck return mMaxHorizontalScrollOffset; 91bff225ecf77fb891596ecb1b27196310d268365eJohn McCall } 928561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson 935fff46b65389f7e7eb576e47c7bc3ca67326a206Ken Dyck public int computeHorizontalScrollOffset() { 94bff225ecf77fb891596ecb1b27196310d268365eJohn McCall return mDelegate.getContainerViewScrollX(); 955fff46b65389f7e7eb576e47c7bc3ca67326a206Ken Dyck } 96bff225ecf77fb891596ecb1b27196310d268365eJohn McCall 97bff225ecf77fb891596ecb1b27196310d268365eJohn McCall public int computeVerticalScrollRange() { 98bff225ecf77fb891596ecb1b27196310d268365eJohn McCall return mContainerViewHeight + mMaxVerticalScrollOffset; 99bff225ecf77fb891596ecb1b27196310d268365eJohn McCall } 1005fff46b65389f7e7eb576e47c7bc3ca67326a206Ken Dyck 101bff225ecf77fb891596ecb1b27196310d268365eJohn McCall public int computeMaximumVerticalScrollOffset() { 1025fff46b65389f7e7eb576e47c7bc3ca67326a206Ken Dyck return mMaxVerticalScrollOffset; 103bff225ecf77fb891596ecb1b27196310d268365eJohn McCall } 104bff225ecf77fb891596ecb1b27196310d268365eJohn McCall 105bff225ecf77fb891596ecb1b27196310d268365eJohn McCall public int computeVerticalScrollOffset() { 106bff225ecf77fb891596ecb1b27196310d268365eJohn McCall return mDelegate.getContainerViewScrollY(); 107d103f9f9b401b419e756f8c1849683cd77586067Anders Carlsson } 108bff225ecf77fb891596ecb1b27196310d268365eJohn McCall 1099dc228a1b971aa884766a9bdfdf5fa8ee4730b5bAnders Carlsson public int computeVerticalScrollExtent() { 1107916c997127fe616ba255ba4cade10e5de0c8812John McCall return mContainerViewHeight; 1117916c997127fe616ba255ba4cade10e5de0c8812John McCall } 1127916c997127fe616ba255ba4cade10e5de0c8812John McCall 1137916c997127fe616ba255ba4cade10e5de0c8812John McCall //--------------------------------------------------------------------------------------------- 1147916c997127fe616ba255ba4cade10e5de0c8812John McCall /** 1157916c997127fe616ba255ba4cade10e5de0c8812John McCall * Called when the scroll range changes. This needs to be the size of the on-screen content. 1167916c997127fe616ba255ba4cade10e5de0c8812John McCall */ 1177916c997127fe616ba255ba4cade10e5de0c8812John McCall public void setMaxScrollOffset(int width, int height) { 1187916c997127fe616ba255ba4cade10e5de0c8812John McCall mMaxHorizontalScrollOffset = width; 1197916c997127fe616ba255ba4cade10e5de0c8812John McCall mMaxVerticalScrollOffset = height; 1207916c997127fe616ba255ba4cade10e5de0c8812John McCall } 1217916c997127fe616ba255ba4cade10e5de0c8812John McCall 1227916c997127fe616ba255ba4cade10e5de0c8812John McCall /** 1237916c997127fe616ba255ba4cade10e5de0c8812John McCall * Called when the physical size of the view changes. 1247916c997127fe616ba255ba4cade10e5de0c8812John McCall */ 1257916c997127fe616ba255ba4cade10e5de0c8812John McCall public void setContainerViewSize(int width, int height) { 1267916c997127fe616ba255ba4cade10e5de0c8812John McCall mContainerViewWidth = width; 1279dc228a1b971aa884766a9bdfdf5fa8ee4730b5bAnders Carlsson mContainerViewHeight = height; 1289dc228a1b971aa884766a9bdfdf5fa8ee4730b5bAnders Carlsson } 1297916c997127fe616ba255ba4cade10e5de0c8812John McCall 1307916c997127fe616ba255ba4cade10e5de0c8812John McCall public void syncScrollOffsetFromOnDraw() { 1317916c997127fe616ba255ba4cade10e5de0c8812John McCall // Unfortunately apps override onScrollChanged without calling super which is why we need 1329dc228a1b971aa884766a9bdfdf5fa8ee4730b5bAnders Carlsson // to sync the scroll offset on every onDraw. 1339dc228a1b971aa884766a9bdfdf5fa8ee4730b5bAnders Carlsson onContainerViewScrollChanged(mDelegate.getContainerViewScrollX(), 1345d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson mDelegate.getContainerViewScrollY()); 13534a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson } 1368561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson 137f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall public void setProcessingTouchEvent(boolean processingTouchEvent) { 138f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall assert mProcessingTouchEvent != processingTouchEvent; 13934a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson mProcessingTouchEvent = processingTouchEvent; 140f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall 14134a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson if (!mProcessingTouchEvent && mApplyDeferredNativeScroll) { 142f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall mApplyDeferredNativeScroll = false; 14334a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson scrollNativeTo(mDeferredNativeScrollX, mDeferredNativeScrollY); 14434a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson } 1457916c997127fe616ba255ba4cade10e5de0c8812John McCall } 1467916c997127fe616ba255ba4cade10e5de0c8812John McCall 1477916c997127fe616ba255ba4cade10e5de0c8812John McCall // Called by the native side to scroll the container view. 1487916c997127fe616ba255ba4cade10e5de0c8812John McCall public void scrollContainerViewTo(int x, int y) { 14934a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson mNativeScrollX = x; 15034a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson mNativeScrollY = y; 15134a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson 15234a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson final int scrollX = mDelegate.getContainerViewScrollX(); 15334a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson final int scrollY = mDelegate.getContainerViewScrollY(); 1547916c997127fe616ba255ba4cade10e5de0c8812John McCall final int deltaX = x - scrollX; 1557916c997127fe616ba255ba4cade10e5de0c8812John McCall final int deltaY = y - scrollY; 1567916c997127fe616ba255ba4cade10e5de0c8812John McCall final int scrollRangeX = computeMaximumHorizontalScrollOffset(); 1577916c997127fe616ba255ba4cade10e5de0c8812John McCall final int scrollRangeY = computeMaximumVerticalScrollOffset(); 15855c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck 1598561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson // We use overScrollContainerViewBy to be compatible with WebViewClassic which used this 160f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall // method for handling both over-scroll as well as in-bounds scroll. 16134a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson mDelegate.overScrollContainerViewBy(deltaX, deltaY, scrollX, scrollY, 1627916c997127fe616ba255ba4cade10e5de0c8812John McCall scrollRangeX, scrollRangeY, mProcessingTouchEvent); 1637916c997127fe616ba255ba4cade10e5de0c8812John McCall } 1647916c997127fe616ba255ba4cade10e5de0c8812John McCall 1657916c997127fe616ba255ba4cade10e5de0c8812John McCall public boolean isFlingActive() { 1667916c997127fe616ba255ba4cade10e5de0c8812John McCall return mFlinging; 1677916c997127fe616ba255ba4cade10e5de0c8812John McCall } 1687916c997127fe616ba255ba4cade10e5de0c8812John McCall 1697916c997127fe616ba255ba4cade10e5de0c8812John McCall // Called by the native side to over-scroll the container view. 1707916c997127fe616ba255ba4cade10e5de0c8812John McCall public void overScrollBy(int deltaX, int deltaY) { 1717916c997127fe616ba255ba4cade10e5de0c8812John McCall // TODO(mkosiba): Once http://crbug.com/260663 and http://crbug.com/261239 are fixed it 17234a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // should be possible to uncomment the following asserts: 1732acc6e3feda5e4f7d9009bdcf8b1cd777fecfe2dChris Lattner // if (deltaX < 0) assert mDelegate.getContainerViewScrollX() == 0; 174f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall // if (deltaX > 0) assert mDelegate.getContainerViewScrollX() == 1757916c997127fe616ba255ba4cade10e5de0c8812John McCall // computeMaximumHorizontalScrollOffset(); 1767916c997127fe616ba255ba4cade10e5de0c8812John McCall scrollBy(deltaX, deltaY); 1777916c997127fe616ba255ba4cade10e5de0c8812John McCall } 17855c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck 17934a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson private void scrollBy(int deltaX, int deltaY) { 18034a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson if (deltaX == 0 && deltaY == 0) return; 1817916c997127fe616ba255ba4cade10e5de0c8812John McCall 1827916c997127fe616ba255ba4cade10e5de0c8812John McCall final int scrollX = mDelegate.getContainerViewScrollX(); 1837916c997127fe616ba255ba4cade10e5de0c8812John McCall final int scrollY = mDelegate.getContainerViewScrollY(); 18434a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson final int scrollRangeX = computeMaximumHorizontalScrollOffset(); 1857916c997127fe616ba255ba4cade10e5de0c8812John McCall final int scrollRangeY = computeMaximumVerticalScrollOffset(); 1867916c997127fe616ba255ba4cade10e5de0c8812John McCall 18734a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // The android.view.View.overScrollBy method is used for both scrolling and over-scrolling 1887916c997127fe616ba255ba4cade10e5de0c8812John McCall // which is why we use it here. 1897916c997127fe616ba255ba4cade10e5de0c8812John McCall mDelegate.overScrollContainerViewBy(deltaX, deltaY, scrollX, scrollY, 1907916c997127fe616ba255ba4cade10e5de0c8812John McCall scrollRangeX, scrollRangeY, mProcessingTouchEvent); 19134a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson } 1927916c997127fe616ba255ba4cade10e5de0c8812John McCall 1937916c997127fe616ba255ba4cade10e5de0c8812John McCall private int clampHorizontalScroll(int scrollX) { 1947916c997127fe616ba255ba4cade10e5de0c8812John McCall scrollX = Math.max(0, scrollX); 19534a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson scrollX = Math.min(computeMaximumHorizontalScrollOffset(), scrollX); 19634a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson return scrollX; 1977916c997127fe616ba255ba4cade10e5de0c8812John McCall } 19834a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson 199336a7dc56871ccfeceecc296c9624f66f7ac01ecAnders Carlsson private int clampVerticalScroll(int scrollY) { 2007916c997127fe616ba255ba4cade10e5de0c8812John McCall scrollY = Math.max(0, scrollY); 201336a7dc56871ccfeceecc296c9624f66f7ac01ecAnders Carlsson scrollY = Math.min(computeMaximumVerticalScrollOffset(), scrollY); 20234a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson return scrollY; 2037916c997127fe616ba255ba4cade10e5de0c8812John McCall } 20455c02585de7b02bcb72352f731d9bc342c8282f3Ken Dyck 2059a8ad9b28d54a3adc4cb8061d564f99f80144e30Ken Dyck // Called by the View system as a response to the mDelegate.overScrollContainerViewBy call. 20634a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson public void onContainerViewOverScrolled(int scrollX, int scrollY, boolean clampedX, 20734a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson boolean clampedY) { 2087916c997127fe616ba255ba4cade10e5de0c8812John McCall // Clamp the scroll offset at (0, max). 20934a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson scrollX = clampHorizontalScroll(scrollX); 2107916c997127fe616ba255ba4cade10e5de0c8812John McCall scrollY = clampVerticalScroll(scrollY); 2117916c997127fe616ba255ba4cade10e5de0c8812John McCall 21234a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson mDelegate.scrollContainerViewTo(scrollX, scrollY); 2137916c997127fe616ba255ba4cade10e5de0c8812John McCall 2147916c997127fe616ba255ba4cade10e5de0c8812John McCall // This is only necessary if the containerView scroll offset ends up being different 2157916c997127fe616ba255ba4cade10e5de0c8812John McCall // than the one set from native in which case we want the value stored on the native side 21634a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // to reflect the value stored in the containerView (and not the other way around). 2177916c997127fe616ba255ba4cade10e5de0c8812John McCall scrollNativeTo(mDelegate.getContainerViewScrollX(), mDelegate.getContainerViewScrollY()); 2187916c997127fe616ba255ba4cade10e5de0c8812John McCall } 2197916c997127fe616ba255ba4cade10e5de0c8812John McCall 22034a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // Called by the View system when the scroll offset had changed. This might not get called if 22134a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // the embedder overrides WebView#onScrollChanged without calling super.onScrollChanged. If 22234a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // this method does get called it is called both as a response to the embedder scrolling the 22334a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson // view as well as a response to mDelegate.scrollContainerViewTo. 22434a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson public void onContainerViewScrollChanged(int x, int y) { 22534a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson scrollNativeTo(x, y); 22634a2d384c745ebc39cae45dc1c0c4a6a7012e09bAnders Carlsson } 227a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson 2288561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson private void scrollNativeTo(int x, int y) { 229f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall x = clampHorizontalScroll(x); 230f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall y = clampVerticalScroll(y); 231a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson 232f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall // We shouldn't do the store to native while processing a touch event since that confuses 233a04efdf635d35d88e65041fad007225d8c8d64a5Anders Carlsson // the gesture processing logic. 234a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson if (mProcessingTouchEvent) { 2358561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson mDeferredNativeScrollX = x; 2362acc6e3feda5e4f7d9009bdcf8b1cd777fecfe2dChris Lattner mDeferredNativeScrollY = y; 237c764830bdb6de82baed068889096bd3e52d4cbdaRichard Smith mApplyDeferredNativeScroll = true; 238a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson return; 239f871d0cc377a1367b519a6cce26be74607566ebaJohn McCall } 240a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson 241a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson if (x == mNativeScrollX && y == mNativeScrollY) 242a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson return; 243a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson 244a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson // The scrollNativeTo call should be a simple store, so it's OK to assume it always 245a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson // succeeds. 246a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson mNativeScrollX = x; 247a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson mNativeScrollY = y; 248a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson 249a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson mDelegate.scrollNativeTo(x, y); 250a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson } 251a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson 252a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson // Called whenever some other touch interaction requires the fling gesture to be canceled. 253a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson public void onFlingCancelGesture() { 254a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson // TODO(mkosiba): Support speeding up a fling by flinging again. 255b924124316becf2968a37dab36d0c48ea167666fAnders Carlsson // http://crbug.com/265841 256a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson mScroller.forceFinished(true); 257a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson } 258a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson 259a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson // Called when a fling gesture is not handled by the renderer. 260a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson // We explicitly ask the renderer not to handle fling gestures targeted at the root 261c5685438df6105052b02c9e02f01c34489606308Eli Friedman // scroll layer. 262c5685438df6105052b02c9e02f01c34489606308Eli Friedman public void onUnhandledFlingStartEvent(int velocityX, int velocityY) { 263c5685438df6105052b02c9e02f01c34489606308Eli Friedman flingScroll(-velocityX, -velocityY); 264a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson } 265a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson 266a552ea76b0e4ce4ccdbb845667f0a12da6608c52Anders Carlsson // Starts the fling animation. Called both as a response to a fling gesture and as via the 267a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson // public WebView#flingScroll(int, int) API. 268a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson public void flingScroll(int velocityX, int velocityY) { 269a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson final int scrollX = mDelegate.getContainerViewScrollX(); 270a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson final int scrollY = mDelegate.getContainerViewScrollY(); 271a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson final int scrollRangeX = computeMaximumHorizontalScrollOffset(); 272a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson final int scrollRangeY = computeMaximumVerticalScrollOffset(); 273a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson 274bbf3bacb3e0c1ebb3e8a4a8b1330404a7e379315Jay Foad mScroller.fling(scrollX, scrollY, velocityX, velocityY, 275a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson 0, scrollRangeX, 0, scrollRangeY); 276a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson mDelegate.invalidate(); 27732baf62b9a3aea3b63be6925b64aa182b0a2278eAnders Carlsson } 278a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson 27932baf62b9a3aea3b63be6925b64aa182b0a2278eAnders Carlsson // Called immediately before the draw to update the scroll offset. 2805d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson public void computeScrollAndAbsorbGlow(OverScrollGlow overScrollGlow) { 281a3697c9c155bda93fd2802f37084b620f4738822Anders Carlsson mFlinging = mScroller.computeScrollOffset(); 2825d58a1d50e2644668122b8efb6b603a706ecfd6bAnders Carlsson if (!mFlinging) { 28321c9ad9d29d08a287292c670e7c52bc522c7f8bbAnders Carlsson return; 284c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson } 285c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson 286314e622d20e67ad2f9bd3e3d4473fb23bec93132Anders Carlsson final int oldX = mDelegate.getContainerViewScrollX(); 287378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor final int oldY = mDelegate.getContainerViewScrollY(); 288378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor int x = mScroller.getCurrX(); 289af4403545a50a60d208e6fcae057308d576a92e0Anders Carlsson int y = mScroller.getCurrY(); 290c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson 291c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson final int scrollRangeX = computeMaximumHorizontalScrollOffset(); 292c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson final int scrollRangeY = computeMaximumVerticalScrollOffset(); 293c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson 294c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson if (overScrollGlow != null) { 295c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson overScrollGlow.absorbGlow(x, y, oldX, oldY, scrollRangeX, scrollRangeY, 2963b47733ceac33306bd54ce9d6c7d8eeeae52c7caJohn McCall mScroller.getCurrVelocity()); 297c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson } 298c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson 2993b47733ceac33306bd54ce9d6c7d8eeeae52c7caJohn McCall // The mScroller is configured not to go outside of the scrollable range, so this call 3003b47733ceac33306bd54ce9d6c7d8eeeae52c7caJohn McCall // should never result in attempting to scroll outside of the scrollable region. 301378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor scrollBy(x - oldX, y - oldY); 302378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor 303378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor mDelegate.invalidate(); 304378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor } 305378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor 306378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor private static int computeDurationInMilliSec(int dx, int dy) { 307af4403545a50a60d208e6fcae057308d576a92e0Anders Carlsson int distance = Math.max(Math.abs(dx), Math.abs(dy)); 3083b47733ceac33306bd54ce9d6c7d8eeeae52c7caJohn McCall int duration = distance * 1000 / STD_SCROLL_ANIMATION_SPEED_PIX_PER_SEC; 309314e622d20e67ad2f9bd3e3d4473fb23bec93132Anders Carlsson return Math.min(duration, MAX_SCROLL_ANIMATION_DURATION_MILLISEC); 3103b47733ceac33306bd54ce9d6c7d8eeeae52c7caJohn McCall } 3113b47733ceac33306bd54ce9d6c7d8eeeae52c7caJohn McCall 312c11bb2191088b9e74bec5007a4e05c78b41a8f64Anders Carlsson private boolean animateScrollTo(int x, int y) { 313c11bb2191088b9e74bec5007a4e05c78b41a8f64Anders Carlsson final int scrollX = mDelegate.getContainerViewScrollX(); 3144230d529a8797bbeef2328b60abeae333f7e143fKen Dyck final int scrollY = mDelegate.getContainerViewScrollY(); 3154230d529a8797bbeef2328b60abeae333f7e143fKen Dyck 3164230d529a8797bbeef2328b60abeae333f7e143fKen Dyck x = clampHorizontalScroll(x); 317c11bb2191088b9e74bec5007a4e05c78b41a8f64Anders Carlsson y = clampVerticalScroll(y); 318c11bb2191088b9e74bec5007a4e05c78b41a8f64Anders Carlsson 319c11bb2191088b9e74bec5007a4e05c78b41a8f64Anders Carlsson int dx = x - scrollX; 3203b47733ceac33306bd54ce9d6c7d8eeeae52c7caJohn McCall int dy = y - scrollY; 3213b47733ceac33306bd54ce9d6c7d8eeeae52c7caJohn McCall 322c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson if (dx == 0 && dy == 0) 323af4403545a50a60d208e6fcae057308d576a92e0Anders Carlsson return false; 324c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson 325c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson mScroller.startScroll(scrollX, scrollY, dx, dy, computeDurationInMilliSec(dx, dy)); 326c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson mDelegate.invalidate(); 327c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson 328c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson return true; 3291cbce125b91cad81c8be3f8bbae8df917211176cAnders Carlsson } 330c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson 331c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson /** 332c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson * See {@link android.webkit.WebView#pageUp(boolean)} 333c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson */ 334c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson public boolean pageUp(boolean top) { 335c997d4278d329e18891aac9698fb991b2d4622ebAnders Carlsson final int scrollX = mDelegate.getContainerViewScrollX(); 336182ab5112650d3228291c4dadd64a9f77f5aeb51John McCall final int scrollY = mDelegate.getContainerViewScrollY(); 33750da2cadcc6da86abff6772de65280ace2cabc94John McCall 3381f0fca54676cfa8616e7f3cd7a26788ab937e3cdJohn McCall if (top) { 33950da2cadcc6da86abff6772de65280ace2cabc94John McCall // go to the top of the document 34050da2cadcc6da86abff6772de65280ace2cabc94John McCall return animateScrollTo(scrollX, 0); 34150da2cadcc6da86abff6772de65280ace2cabc94John McCall } 34250da2cadcc6da86abff6772de65280ace2cabc94John McCall int dy = -mContainerViewHeight / 2; 343182ab5112650d3228291c4dadd64a9f77f5aeb51John McCall if (mContainerViewHeight > 2 * PAGE_SCROLL_OVERLAP) { 344ad346f4f678ab1c3222425641d851dc63e9dfa1aJohn McCall dy = -mContainerViewHeight + PAGE_SCROLL_OVERLAP; 34550da2cadcc6da86abff6772de65280ace2cabc94John McCall } 34650da2cadcc6da86abff6772de65280ace2cabc94John McCall // animateScrollTo clamps the argument to the scrollable range so using (scrollY + dy) is 34750da2cadcc6da86abff6772de65280ace2cabc94John McCall // fine. 34850da2cadcc6da86abff6772de65280ace2cabc94John McCall return animateScrollTo(scrollX, scrollY + dy); 34950da2cadcc6da86abff6772de65280ace2cabc94John McCall } 35050da2cadcc6da86abff6772de65280ace2cabc94John McCall 35150da2cadcc6da86abff6772de65280ace2cabc94John McCall /** 35250da2cadcc6da86abff6772de65280ace2cabc94John McCall * See {@link android.webkit.WebView#pageDown(boolean)} 353378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor */ 354378e1e739aed97e9b278beeb20e9f5bbe34c0232Douglas Gregor public boolean pageDown(boolean bottom) { 355182ab5112650d3228291c4dadd64a9f77f5aeb51John McCall final int scrollX = mDelegate.getContainerViewScrollX(); 356182ab5112650d3228291c4dadd64a9f77f5aeb51John McCall final int scrollY = mDelegate.getContainerViewScrollY(); 3577e1dff7a68a4d00e71debafa7f5c259473091746John McCall 3587e1dff7a68a4d00e71debafa7f5c259473091746John McCall if (bottom) { 3597e1dff7a68a4d00e71debafa7f5c259473091746John McCall return animateScrollTo(scrollX, computeVerticalScrollRange()); 3607e1dff7a68a4d00e71debafa7f5c259473091746John McCall } 3617e1dff7a68a4d00e71debafa7f5c259473091746John McCall int dy = mContainerViewHeight / 2; 3627e1dff7a68a4d00e71debafa7f5c259473091746John McCall if (mContainerViewHeight > 2 * PAGE_SCROLL_OVERLAP) { 3637e1dff7a68a4d00e71debafa7f5c259473091746John McCall dy = mContainerViewHeight - PAGE_SCROLL_OVERLAP; 3647e1dff7a68a4d00e71debafa7f5c259473091746John McCall } 3657e1dff7a68a4d00e71debafa7f5c259473091746John McCall // animateScrollTo clamps the argument to the scrollable range so using (scrollY + dy) is 3667e1dff7a68a4d00e71debafa7f5c259473091746John McCall // fine. 3677e1dff7a68a4d00e71debafa7f5c259473091746John McCall return animateScrollTo(scrollX, scrollY + dy); 3687e1dff7a68a4d00e71debafa7f5c259473091746John McCall } 3697e1dff7a68a4d00e71debafa7f5c259473091746John McCall 3707e1dff7a68a4d00e71debafa7f5c259473091746John McCall /** 3717e1dff7a68a4d00e71debafa7f5c259473091746John McCall * See {@link android.webkit.WebView#requestChildRectangleOnScreen(View, Rect, boolean)} 3727e1dff7a68a4d00e71debafa7f5c259473091746John McCall */ 3737e1dff7a68a4d00e71debafa7f5c259473091746John McCall public boolean requestChildRectangleOnScreen(int childOffsetX, int childOffsetY, Rect rect, 3747e1dff7a68a4d00e71debafa7f5c259473091746John McCall boolean immediate) { 3757e1dff7a68a4d00e71debafa7f5c259473091746John McCall // TODO(mkosiba): WebViewClassic immediately returns false if a zoom animation is 3767e1dff7a68a4d00e71debafa7f5c259473091746John McCall // in progress. We currently can't tell if one is happening.. should we instead cancel any 3777e1dff7a68a4d00e71debafa7f5c259473091746John McCall // scroll animation when the size/pageScaleFactor changes? 3787e1dff7a68a4d00e71debafa7f5c259473091746John McCall 3797e1dff7a68a4d00e71debafa7f5c259473091746John McCall // TODO(mkosiba): Take scrollbar width into account in the screenRight/screenBotton 380182ab5112650d3228291c4dadd64a9f77f5aeb51John McCall // calculations. http://crbug.com/269032 381182ab5112650d3228291c4dadd64a9f77f5aeb51John McCall 382607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson final int scrollX = mDelegate.getContainerViewScrollX(); 383607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson final int scrollY = mDelegate.getContainerViewScrollY(); 384cbb67480094b3bcb5b715acd827cbad55e2a204cSean Hunt 385607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson rect.offset(childOffsetX, childOffsetY); 386607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson 387607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson int screenTop = scrollY; 388607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson int screenBottom = scrollY + mContainerViewHeight; 389607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson int scrollYDelta = 0; 390607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson 391607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson if (rect.bottom > screenBottom) { 392607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson int oneThirdOfScreenHeight = mContainerViewHeight / 3; 393607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson if (rect.width() > 2 * oneThirdOfScreenHeight) { 394607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson // If the rectangle is too tall to fit in the bottom two thirds 39580638c5e6395344c1e6096542b0ff3b8bfb2139eAnders Carlsson // of the screen, place it at the top. 396607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson scrollYDelta = rect.top - screenTop; 397607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson } else { 398607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson // If the rectangle will still fit on screen, we want its 399607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson // top to be in the top third of the screen. 400607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight); 4017e1dff7a68a4d00e71debafa7f5c259473091746John McCall } 4027e1dff7a68a4d00e71debafa7f5c259473091746John McCall } else if (rect.top < screenTop) { 4037e1dff7a68a4d00e71debafa7f5c259473091746John McCall scrollYDelta = rect.top - screenTop; 4047e1dff7a68a4d00e71debafa7f5c259473091746John McCall } 4057e1dff7a68a4d00e71debafa7f5c259473091746John McCall 4067e1dff7a68a4d00e71debafa7f5c259473091746John McCall int screenLeft = scrollX; 407bff225ecf77fb891596ecb1b27196310d268365eJohn McCall int screenRight = scrollX + mContainerViewWidth; 408bff225ecf77fb891596ecb1b27196310d268365eJohn McCall int scrollXDelta = 0; 4098561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson 4108561a8666c70f924c8f0209c41b9b77bbbf90607Anders Carlsson if (rect.right > screenRight && rect.left > screenLeft) { 41150da2cadcc6da86abff6772de65280ace2cabc94John McCall if (rect.width() > mContainerViewWidth) { 41250da2cadcc6da86abff6772de65280ace2cabc94John McCall scrollXDelta += (rect.left - screenLeft); 413d7722d9d76a851e7897f4127626616d3b1b8e530Eli Friedman } else { 4147c2349be2d11143a2e59a167fd43362a3bf4585eJohn McCall scrollXDelta += (rect.right - screenRight); 415f394078fde147dcf27e9b6a7965517388d64dcb6Eli Friedman } 4167c2349be2d11143a2e59a167fd43362a3bf4585eJohn McCall } else if (rect.left < screenLeft) { 417410ffb2bc5f072d58a73c14560345bcf77dec1ccJohn McCall scrollXDelta -= (screenLeft - rect.left); 418649b4a1a9b5e6f768ca0cb84bd97b00f51083e15Chad Rosier } 419558d2abc7f9fd6801cc7677200992313ae90b5d8John McCall 420558d2abc7f9fd6801cc7677200992313ae90b5d8John McCall if (scrollYDelta == 0 && scrollXDelta == 0) { 421594d5e8bd9870080aad6a761538e272bc2dfcc13Anders Carlsson return false; 4224e4d08403ca5cfd4d558fa2936215d3a4e5a528dDavid Blaikie } 423c1cfdf8647a499b6b3024f4bd14a236cddb23988Anders Carlsson 4241f0fca54676cfa8616e7f3cd7a26788ab937e3cdJohn McCall if (immediate) { 4251f0fca54676cfa8616e7f3cd7a26788ab937e3cdJohn McCall scrollBy(scrollXDelta, scrollYDelta); 426607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson return true; 427607d037c3f4376cbc8979d0eb9cd2f49a58ea033Anders Carlsson } else { 428fb8cc253420e93cee33d29df5a2bdae6aaf16e39Douglas Gregor return animateScrollTo(scrollX + scrollXDelta, scrollY + scrollYDelta); 429fb8cc253420e93cee33d29df5a2bdae6aaf16e39Douglas Gregor } 4300bdb5aa1a3384d194b0e14a9ecbe3309ff33daa3Eli Friedman } 431fb8cc253420e93cee33d29df5a2bdae6aaf16e39Douglas Gregor} 432fb8cc253420e93cee33d29df5a2bdae6aaf16e39Douglas Gregor