1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/gfx/SubdocumentScrollHelper.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,148 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.gfx; 1.10 + 1.11 +import org.mozilla.gecko.GeckoAppShell; 1.12 +import org.mozilla.gecko.GeckoEvent; 1.13 +import org.mozilla.gecko.EventDispatcher; 1.14 +import org.mozilla.gecko.util.GeckoEventListener; 1.15 + 1.16 +import org.json.JSONException; 1.17 +import org.json.JSONObject; 1.18 + 1.19 +import android.graphics.PointF; 1.20 +import android.os.Handler; 1.21 +import android.util.Log; 1.22 + 1.23 +class SubdocumentScrollHelper implements GeckoEventListener { 1.24 + private static final String LOGTAG = "GeckoSubdocScroll"; 1.25 + 1.26 + private static String MESSAGE_PANNING_OVERRIDE = "Panning:Override"; 1.27 + private static String MESSAGE_CANCEL_OVERRIDE = "Panning:CancelOverride"; 1.28 + private static String MESSAGE_SCROLL = "Gesture:Scroll"; 1.29 + private static String MESSAGE_SCROLL_ACK = "Gesture:ScrollAck"; 1.30 + 1.31 + private final Handler mUiHandler; 1.32 + private final EventDispatcher mEventDispatcher; 1.33 + 1.34 + /* This is the amount of displacement we have accepted but not yet sent to JS; this is 1.35 + * only valid when mOverrideScrollPending is true. */ 1.36 + private final PointF mPendingDisplacement; 1.37 + 1.38 + /* When this is true, we're sending scroll events to JS to scroll the active subdocument. */ 1.39 + private boolean mOverridePanning; 1.40 + 1.41 + /* When this is true, we have received an ack for the last scroll event we sent to JS, and 1.42 + * are ready to send the next scroll event. Note we only ever have one scroll event inflight 1.43 + * at a time. */ 1.44 + private boolean mOverrideScrollAck; 1.45 + 1.46 + /* When this is true, we have a pending scroll that we need to send to JS; we were unable 1.47 + * to send it when it was initially requested because mOverrideScrollAck was not true. */ 1.48 + private boolean mOverrideScrollPending; 1.49 + 1.50 + /* When this is true, the last scroll event we sent actually did some amount of scrolling on 1.51 + * the subdocument; we use this to decide when we have reached the end of the subdocument. */ 1.52 + private boolean mScrollSucceeded; 1.53 + 1.54 + SubdocumentScrollHelper(EventDispatcher eventDispatcher) { 1.55 + // mUiHandler will be bound to the UI thread since that's where this constructor runs 1.56 + mUiHandler = new Handler(); 1.57 + mPendingDisplacement = new PointF(); 1.58 + 1.59 + mEventDispatcher = eventDispatcher; 1.60 + registerEventListener(MESSAGE_PANNING_OVERRIDE); 1.61 + registerEventListener(MESSAGE_CANCEL_OVERRIDE); 1.62 + registerEventListener(MESSAGE_SCROLL_ACK); 1.63 + } 1.64 + 1.65 + void destroy() { 1.66 + unregisterEventListener(MESSAGE_PANNING_OVERRIDE); 1.67 + unregisterEventListener(MESSAGE_CANCEL_OVERRIDE); 1.68 + unregisterEventListener(MESSAGE_SCROLL_ACK); 1.69 + } 1.70 + 1.71 + private void registerEventListener(String event) { 1.72 + mEventDispatcher.registerEventListener(event, this); 1.73 + } 1.74 + 1.75 + private void unregisterEventListener(String event) { 1.76 + mEventDispatcher.unregisterEventListener(event, this); 1.77 + } 1.78 + 1.79 + boolean scrollBy(PointF displacement) { 1.80 + if (! mOverridePanning) { 1.81 + return false; 1.82 + } 1.83 + 1.84 + if (! mOverrideScrollAck) { 1.85 + mOverrideScrollPending = true; 1.86 + mPendingDisplacement.x += displacement.x; 1.87 + mPendingDisplacement.y += displacement.y; 1.88 + return true; 1.89 + } 1.90 + 1.91 + JSONObject json = new JSONObject(); 1.92 + try { 1.93 + json.put("x", displacement.x); 1.94 + json.put("y", displacement.y); 1.95 + } catch (JSONException e) { 1.96 + Log.e(LOGTAG, "Error forming subwindow scroll message: ", e); 1.97 + } 1.98 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(MESSAGE_SCROLL, json.toString())); 1.99 + 1.100 + mOverrideScrollAck = false; 1.101 + mOverrideScrollPending = false; 1.102 + // clear the |mPendingDisplacement| after serializing |displacement| to 1.103 + // JSON because they might be the same object 1.104 + mPendingDisplacement.x = 0; 1.105 + mPendingDisplacement.y = 0; 1.106 + 1.107 + return true; 1.108 + } 1.109 + 1.110 + void cancel() { 1.111 + mOverridePanning = false; 1.112 + } 1.113 + 1.114 + boolean scrolling() { 1.115 + return mOverridePanning; 1.116 + } 1.117 + 1.118 + boolean lastScrollSucceeded() { 1.119 + return mScrollSucceeded; 1.120 + } 1.121 + 1.122 + // GeckoEventListener implementation 1.123 + 1.124 + @Override 1.125 + public void handleMessage(final String event, final JSONObject message) { 1.126 + // This comes in on the Gecko thread; hand off the handling to the UI thread. 1.127 + mUiHandler.post(new Runnable() { 1.128 + @Override 1.129 + public void run() { 1.130 + try { 1.131 + if (MESSAGE_PANNING_OVERRIDE.equals(event)) { 1.132 + mOverridePanning = true; 1.133 + mOverrideScrollAck = true; 1.134 + mOverrideScrollPending = false; 1.135 + mScrollSucceeded = true; 1.136 + } else if (MESSAGE_CANCEL_OVERRIDE.equals(event)) { 1.137 + mOverridePanning = false; 1.138 + } else if (MESSAGE_SCROLL_ACK.equals(event)) { 1.139 + mOverrideScrollAck = true; 1.140 + mScrollSucceeded = message.getBoolean("scrolled"); 1.141 + if (mOverridePanning && mOverrideScrollPending) { 1.142 + scrollBy(mPendingDisplacement); 1.143 + } 1.144 + } 1.145 + } catch (Exception e) { 1.146 + Log.e(LOGTAG, "Exception handling message", e); 1.147 + } 1.148 + } 1.149 + }); 1.150 + } 1.151 +}