Bug 814252 - use touch event for scrolling if available. r=cjones.

This commit is contained in:
Shih-Chiang Chien 2012-11-28 13:51:50 +08:00
parent ad934e8b01
commit 551b1dede5
9 changed files with 176 additions and 11 deletions

View File

@ -4,7 +4,7 @@
const ContentPanning = {
init: function cp_init() {
['mousedown', 'mouseup', 'mousemove'].forEach(function(type) {
['mousedown', 'mouseup', 'mousemove', 'touchstart', 'touchend', 'touchmove'].forEach(function(type) {
addEventListener(type, ContentPanning, false);
});
@ -12,15 +12,41 @@ const ContentPanning = {
addMessageListener("Gesture:DoubleTap", this._recvDoubleTap.bind(this));
},
evtFilter: '',
_filterEvent: function cp_filterEvent(evt) {
switch (this.evtFilter) {
case 'mouse':
if (evt.type == 'touchstart' || evt.type == 'touchend' || evt.type == 'touchmove') {
return false;
}
break;
case 'touch':
if (evt.type == 'mousedown' || evt.type == 'mouseup' || evt.type == 'mousemove') {
return false;
}
break;
}
return true;
},
handleEvent: function cp_handleEvent(evt) {
// determine scrolling detection is based on touch or mouse event at runtime
if (!this.evtFilter) {
if (evt.type == 'touchstart') this.evtFilter = 'touch';
else if (evt.type == 'mousedown') this.evtFilter = 'mouse';
}
if (evt.defaultPrevented || !this._filterEvent(evt)) return;
switch (evt.type) {
case 'mousedown':
case 'touchstart':
this.onTouchStart(evt);
break;
case 'mousemove':
case 'touchmove':
this.onTouchMove(evt);
break;
case 'mouseup':
case 'touchend':
this.onTouchEnd(evt);
break;
case 'click':
@ -37,12 +63,39 @@ const ContentPanning = {
position: new Point(0 , 0),
findFirstTouch: function cp_findFirstTouch(touches) {
if (!('trackingId' in this)) return undefined;
for (let i = 0; i < touches.length; i++) {
if (touches[i].identifier === this.trackingId)
return touches[i];
}
return undefined;
},
onTouchStart: function cp_onTouchStart(evt) {
let target, screenX, screenY;
if (this.evtFilter == 'touch') {
if ('trackingId' in this) {
return;
}
let firstTouch = evt.changedTouches[0];
this.trackingId = firstTouch.identifier;
target = firstTouch.target;
screenX = firstTouch.screenX;
screenY = firstTouch.screenY;
} else {
target = evt.target;
screenX = evt.screenX;
screenY = evt.screenY;
}
this.dragging = true;
this.panning = false;
let oldTarget = this.target;
[this.target, this.scrollCallback] = this.getPannable(evt.target);
[this.target, this.scrollCallback] = this.getPannable(target);
// If we found a target, that means we have found a scrollable subframe. In
// this case, and if we are using async panning and zooming on the parent
@ -51,7 +104,7 @@ const ContentPanning = {
// time we get a touch end).
if (this.target != null && ContentPanning._asyncPanZoomForViewportFrame) {
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.notifyObservers(docShell, 'cancel-default-pan-zoom', null);
os.notifyObservers(docShell, 'detect-scrollable-subframe', null);
}
// If there is a pan animation running (from a previous pan gesture) and
@ -67,18 +120,24 @@ const ContentPanning = {
}
this.position.set(evt.screenX, evt.screenY);
this.position.set(screenX, screenY);
KineticPanning.record(new Point(0, 0), evt.timeStamp);
},
onTouchEnd: function cp_onTouchEnd(evt) {
if (this.evtFilter == 'touch' && !this.findFirstTouch(evt.changedTouches))
return;
if (!this.dragging)
return;
this.dragging = false;
this.isScrolling = false;
this.onTouchMove(evt);
let click = evt.detail;
delete this.trackingId;
let click = (this.evtFilter == 'touch') ? true : evt.detail;
if (this.target && click && (this.panning || this.preventNextClick)) {
let target = this.target;
let view = target.ownerDocument ? target.ownerDocument.defaultView
@ -90,23 +149,54 @@ const ContentPanning = {
KineticPanning.start(this);
},
isScrolling: false, // Scrolling gesture is executed in BrowserElementScrolling
onTouchMove: function cp_onTouchMove(evt) {
if (!this.dragging || !this.scrollCallback)
return;
let screenX, screenY;
if (this.evtFilter == 'touch') {
let firstTouch = this.findFirstTouch(evt.changedTouches);
if (evt.touches.length > 1 || !firstTouch)
return;
screenX = firstTouch.screenX;
screenY = firstTouch.screenY;
} else {
screenX = evt.screenX;
screenY = evt.screenY;
}
let current = this.position;
let delta = new Point(evt.screenX - current.x, evt.screenY - current.y);
current.set(evt.screenX, evt.screenY);
let delta = new Point(screenX - current.x, screenY - current.y);
current.set(screenX, screenY);
KineticPanning.record(delta, evt.timeStamp);
this.scrollCallback(delta.scale(-1));
let success = this.scrollCallback(delta.scale(-1));
// Stop async-pan-zooming if the subframe is really scrolled.
if (!this.isScrolling && ContentPanning._asyncPanZoomForViewportFrame) {
if (success) {
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.notifyObservers(docShell, 'cancel-default-pan-zoom', null);
} else {
// Let AsyncPanZoomController handle the scrolling gesture.
delete this.trackingId;
return;
}
}
// Successfully scroll the inner scrollable region.
if (success && !this.isScrolling) {
this.isScrolling = true;
}
// If a pan action happens, cancel the active state of the
// current target.
if (!this.panning && KineticPanning.isPan()) {
this.panning = true;
this._resetActive();
}
evt.stopPropagation();
evt.preventDefault();
},

View File

@ -96,6 +96,7 @@ static const nsIntSize kDefaultViewportSize(980, 480);
static const char CANCEL_DEFAULT_PAN_ZOOM[] = "cancel-default-pan-zoom";
static const char BROWSER_ZOOM_TO_RECT[] = "browser-zoom-to-rect";
static const char BEFORE_FIRST_PAINT[] = "before-first-paint";
static const char DETECT_SCROLLABLE_SUBFRAME[] = "detect-scrollable-subframe";
NS_IMETHODIMP
ContentListener::HandleEvent(nsIDOMEvent* aEvent)
@ -241,6 +242,12 @@ TabChild::Observe(nsISupports *aSubject,
HandlePossibleViewportChange();
}
}
} else if (!strcmp(aTopic, DETECT_SCROLLABLE_SUBFRAME)) {
nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
nsCOMPtr<nsITabChild> tabChild(GetTabChildFrom(docShell));
if (tabChild == this) {
mRemoteFrame->DetectScrollableSubframe();
}
}
return NS_OK;
@ -1676,6 +1683,7 @@ TabChild::RecvDestroy()
observerService->RemoveObserver(this, CANCEL_DEFAULT_PAN_ZOOM);
observerService->RemoveObserver(this, BROWSER_ZOOM_TO_RECT);
observerService->RemoveObserver(this, BEFORE_FIRST_PAINT);
observerService->RemoveObserver(this, DETECT_SCROLLABLE_SUBFRAME);
const InfallibleTArray<PIndexedDBChild*>& idbActors =
ManagedPIndexedDBChild();
@ -1803,6 +1811,9 @@ TabChild::InitRenderingState()
observerService->AddObserver(this,
BEFORE_FIRST_PAINT,
false);
observerService->AddObserver(this,
DETECT_SCROLLABLE_SUBFRAME,
false);
}
return true;

View File

@ -237,6 +237,26 @@ nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent)
return rv;
}
if (mDelayPanning && aEvent.mInputType == MULTITOUCH_INPUT) {
const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
if (multiTouchInput.mType == MultiTouchInput::MULTITOUCH_MOVE) {
// Let BrowserElementScrolling perform panning gesture first.
SetState(WAITING_LISTENERS);
mTouchQueue.AppendElement(multiTouchInput);
if (!mTouchListenerTimeoutTask) {
mTouchListenerTimeoutTask =
NewRunnableMethod(this, &AsyncPanZoomController::TimeoutTouchListeners);
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
mTouchListenerTimeoutTask,
TOUCH_LISTENER_TIMEOUT);
}
return nsEventStatus_eConsumeNoDefault;
}
}
switch (aEvent.mInputType) {
case MULTITOUCH_INPUT: {
const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
@ -1089,6 +1109,7 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFr
mLastContentPaintMetrics = aViewportFrame;
mFrameMetrics.mMayHaveTouchListeners = aViewportFrame.mMayHaveTouchListeners;
if (mWaitingForContentToPaint) {
// Remove the oldest sample we have if adding a new sample takes us over our
// desired number of samples.
@ -1182,6 +1203,10 @@ void AsyncPanZoomController::CancelDefaultPanZoom() {
}
}
void AsyncPanZoomController::DetectScrollableSubframe() {
mDelayPanning = true;
}
void AsyncPanZoomController::ZoomToRect(const gfxRect& aRect) {
gfx::Rect zoomToRect(gfx::Rect(aRect.x, aRect.y, aRect.width, aRect.height));
@ -1268,7 +1293,7 @@ void AsyncPanZoomController::ZoomToRect(const gfxRect& aRect) {
}
void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
if (!mFrameMetrics.mMayHaveTouchListeners) {
if (!mFrameMetrics.mMayHaveTouchListeners && !mDelayPanning) {
mTouchQueue.Clear();
return;
}
@ -1280,12 +1305,21 @@ void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
if (mState == WAITING_LISTENERS) {
if (!aPreventDefault) {
SetState(NOTHING);
// Delayed scrolling gesture is pending at TOUCHING state.
if (mDelayPanning) {
SetState(TOUCHING);
} else {
SetState(NOTHING);
}
}
mHandlingTouchQueue = true;
while (!mTouchQueue.IsEmpty()) {
// we need to reset mDelayPanning before handling scrolling gesture.
if (mTouchQueue[0].mType == MultiTouchInput::MULTITOUCH_MOVE) {
mDelayPanning = false;
}
if (!aPreventDefault) {
HandleInputEvent(mTouchQueue[0]);
}

View File

@ -108,7 +108,7 @@ public:
void UpdateCompositionBounds(const nsIntRect& aCompositionBounds);
/**
* We have found a scrollable subframe, so disable our machinery until we hit
* We are scrolling a subframe, so disable our machinery until we hit
* a touch end or a new touch start. This prevents us from accidentally
* panning both the subframe and the parent frame.
*
@ -117,6 +117,12 @@ public:
*/
void CancelDefaultPanZoom();
/**
* We have found a scrollable subframe, so we need to delay the scrolling
* gesture executed and let subframe do the scrolling first.
*/
void DetectScrollableSubframe();
/**
* Kicks an animation to zoom to a rect. This may be either a zoom out or zoom
* in. The actual animation is done on the compositor thread after being set
@ -549,6 +555,12 @@ private:
// and we don't want to queue the events back up again.
bool mHandlingTouchQueue;
// Flag used to determine whether or not we should try scrolling by
// BrowserElementScrolling first. If set, this means we should pend touch move
// event, which not be cosumed by GestureListener. This flag will be reset
// after touch move event has been handled by content process.
bool mDelayPanning;
friend class Axis;
};

View File

@ -42,6 +42,7 @@ parent:
async NotifyCompositorTransaction();
async CancelDefaultPanZoom();
async DetectScrollableSubframe();
async __delete__();

View File

@ -38,6 +38,12 @@ RenderFrameChild::CancelDefaultPanZoom()
SendCancelDefaultPanZoom();
}
void
RenderFrameChild::DetectScrollableSubframe()
{
SendDetectScrollableSubframe();
}
PLayersChild*
RenderFrameChild::AllocPLayers()
{

View File

@ -20,6 +20,7 @@ public:
virtual ~RenderFrameChild() {}
void CancelDefaultPanZoom();
void DetectScrollableSubframe();
void Destroy();

View File

@ -810,6 +810,15 @@ RenderFrameParent::RecvCancelDefaultPanZoom()
return true;
}
bool
RenderFrameParent::RecvDetectScrollableSubframe()
{
if (mPanZoomController) {
mPanZoomController->DetectScrollableSubframe();
}
return true;
}
PLayersParent*
RenderFrameParent::AllocPLayers()
{

View File

@ -109,6 +109,7 @@ protected:
virtual bool RecvNotifyCompositorTransaction() MOZ_OVERRIDE;
virtual bool RecvCancelDefaultPanZoom() MOZ_OVERRIDE;
virtual bool RecvDetectScrollableSubframe() MOZ_OVERRIDE;
virtual PLayersParent* AllocPLayers() MOZ_OVERRIDE;
virtual bool DeallocPLayers(PLayersParent* aLayers) MOZ_OVERRIDE;