mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
Gecko work for bug 823619: Dispatch spec-compliant mouse events when touch events are available. r=cjones,jlebar,schien,vingtetun a=blocking-basecamp
This is a rollowup of the following patches Bug 823619, part 1: Make TabChild dispatch spec-compliant compat mouse events. r=mwu Bug 823619, part 2: Use touch event for scrolling if available. r=cjones,schien,vingtetun a=blocking-basecamp
This commit is contained in:
parent
d714fbe57a
commit
56e0349b73
@ -283,10 +283,14 @@ pref("content.image.allow_locking", true);
|
||||
pref("image.mem.min_discard_timeout_ms", 10000);
|
||||
pref("image.mem.max_decoded_image_kb", 5120); /* 5MB */
|
||||
|
||||
// XXX this isn't a good check for "are touch events supported", but
|
||||
// we don't really have a better one at the moment.
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
// enable touch events interfaces
|
||||
pref("dom.w3c_touch_events.enabled", 1);
|
||||
pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
|
||||
pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_SAFE_BROWSING
|
||||
// Safe browsing does nothing unless this pref is set
|
||||
|
@ -6,8 +6,35 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const ContentPanning = {
|
||||
// Are we listening to touch or mouse events?
|
||||
watchedEventsType: '',
|
||||
|
||||
// Are mouse events being delivered to this content along with touch
|
||||
// events, in violation of spec?
|
||||
hybridEvents: false,
|
||||
|
||||
init: function cp_init() {
|
||||
['mousedown', 'mouseup', 'mousemove'].forEach(function(type) {
|
||||
var events;
|
||||
try {
|
||||
content.document.createEvent('TouchEvent');
|
||||
events = ['touchstart', 'touchend', 'touchmove'];
|
||||
this.watchedEventsType = 'touch';
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
// The gonk widget backend does not deliver mouse events per
|
||||
// spec. Third-party content isn't exposed to this behavior,
|
||||
// but that behavior creates some extra work for us here.
|
||||
let appInfo = Cc["@mozilla.org/xre/app-info;1"];
|
||||
let isParentProcess =
|
||||
!appInfo || appInfo.getService(Ci.nsIXULRuntime)
|
||||
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
||||
this.hybridEvents = isParentProcess;
|
||||
#endif
|
||||
} catch(e) {
|
||||
// Touch events aren't supported, so fall back on mouse.
|
||||
events = ['mousedown', 'mouseup', 'mousemove'];
|
||||
this.watchedEventsType = 'mouse';
|
||||
}
|
||||
events.forEach(function(type) {
|
||||
addEventListener(type, ContentPanning, false);
|
||||
});
|
||||
|
||||
@ -16,14 +43,20 @@ const ContentPanning = {
|
||||
},
|
||||
|
||||
handleEvent: function cp_handleEvent(evt) {
|
||||
if (evt.defaultPrevented)
|
||||
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':
|
||||
@ -40,21 +73,68 @@ const ContentPanning = {
|
||||
|
||||
position: new Point(0 , 0),
|
||||
|
||||
findPrimaryPointer: function cp_findPrimaryPointer(touches) {
|
||||
if (!('primaryPointerId' in this))
|
||||
return null;
|
||||
|
||||
for (let i = 0; i < touches.length; i++) {
|
||||
if (touches[i].identifier === this.primaryPointerId) {
|
||||
return touches[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
onTouchStart: function cp_onTouchStart(evt) {
|
||||
let screenX, screenY;
|
||||
if (this.watchedEventsType == 'touch') {
|
||||
if ('primaryPointerId' in this) {
|
||||
return;
|
||||
}
|
||||
|
||||
let firstTouch = evt.changedTouches[0];
|
||||
this.primaryPointerId = firstTouch.identifier;
|
||||
this.pointerDownTarget = firstTouch.target;
|
||||
screenX = firstTouch.screenX;
|
||||
screenY = firstTouch.screenY;
|
||||
} else {
|
||||
this.pointerDownTarget = 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(this.pointerDownTarget);
|
||||
|
||||
// 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
|
||||
// frame, inform the pan/zoom controller that it should not attempt to
|
||||
// handle any touch events it gets until the next batch (meaning the next
|
||||
// time we get a touch end).
|
||||
if (this.target != null && ContentPanning._asyncPanZoomForViewportFrame) {
|
||||
if (this.target != null && this._asyncPanZoomForViewportFrame) {
|
||||
this.detectingScrolling = true;
|
||||
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 we have a pointer down target and we're not async
|
||||
// pan/zooming, we may need to fill in for EventStateManager in
|
||||
// setting the active state on the target element. Set a timer to
|
||||
// ensure the pointer-down target is active. (If it's already
|
||||
// active, the timer is a no-op.)
|
||||
if (this.pointerDownTarget !== null && !this.detectingScrolling) {
|
||||
// If there's no possibility this is a drag/pan, activate now.
|
||||
// Otherwise wait a little bit to see if the gesture isn't a
|
||||
// tap.
|
||||
if (this.target === null) {
|
||||
this.notify(this._activationTimer);
|
||||
} else {
|
||||
this._activationTimer.initWithCallback(this,
|
||||
this._activationDelayMs,
|
||||
Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a pan animation running (from a previous pan gesture) and
|
||||
@ -69,19 +149,38 @@ const ContentPanning = {
|
||||
this.preventNextClick = true;
|
||||
}
|
||||
|
||||
|
||||
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.dragging)
|
||||
let touch = null;
|
||||
if (!this.dragging ||
|
||||
(this.watchedEventsType == 'touch' &&
|
||||
!(touch = this.findPrimaryPointer(evt.changedTouches)))) {
|
||||
return;
|
||||
this.dragging = false;
|
||||
}
|
||||
|
||||
this.onTouchMove(evt);
|
||||
// !isPan() and evt.detail should always give the same answer here
|
||||
// since they use the same heuristics, but use the native gecko
|
||||
// computation when possible.
|
||||
//
|
||||
// NB: when we're using touch events, then !KineticPanning.isPan()
|
||||
// => this.panning, so we'll never attempt to block the click
|
||||
// event. That's OK however, because we won't fire a synthetic
|
||||
// click when we're using touch events and this touch series
|
||||
// wasn't a "tap" gesture.
|
||||
let click = (this.watchedEventsType == 'mouse') ?
|
||||
evt.detail : !KineticPanning.isPan();
|
||||
// Additionally, if we're seeing non-compliant hybrid events, a
|
||||
// "real" click will be generated if we started and ended on the
|
||||
// same element.
|
||||
if (this.hybridEvents) {
|
||||
let target =
|
||||
content.document.elementFromPoint(touch.clientX, touch.clientY);
|
||||
click |= (target === this.pointerDownTarget);
|
||||
}
|
||||
|
||||
let click = evt.detail;
|
||||
if (this.target && click && (this.panning || this.preventNextClick)) {
|
||||
let target = this.target;
|
||||
let view = target.ownerDocument ? target.ownerDocument.defaultView
|
||||
@ -89,31 +188,84 @@ const ContentPanning = {
|
||||
view.addEventListener('click', this, true, true);
|
||||
}
|
||||
|
||||
if (this.panning)
|
||||
this._resetActive();
|
||||
this.dragging = false;
|
||||
this.detectingScrolling = false;
|
||||
delete this.primaryPointerId;
|
||||
this._activationTimer.cancel();
|
||||
|
||||
if (this.panning) {
|
||||
KineticPanning.start(this);
|
||||
}
|
||||
},
|
||||
|
||||
// True when there's an async pan-zoom controll watching the
|
||||
// outermost scrollable frame, and we're waiting to see whether
|
||||
// we're going to take over from it and synchronously scroll an
|
||||
// inner scrollable frame.
|
||||
detectingScrolling: false,
|
||||
|
||||
onTouchMove: function cp_onTouchMove(evt) {
|
||||
if (!this.dragging || !this.scrollCallback)
|
||||
if (!this.dragging)
|
||||
return;
|
||||
|
||||
let screenX, screenY;
|
||||
if (this.watchedEventsType == 'touch') {
|
||||
let primaryTouch = this.findPrimaryPointer(evt.changedTouches);
|
||||
if (evt.touches.length > 1 || !primaryTouch)
|
||||
return;
|
||||
screenX = primaryTouch.screenX;
|
||||
screenY = primaryTouch.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);
|
||||
|
||||
// There's no possibility of us panning anything.
|
||||
if (!this.scrollCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isPan = KineticPanning.isPan();
|
||||
if (this.detectingScrolling) {
|
||||
this.detectingScrolling = false;
|
||||
// Stop async-pan-zooming if the user is panning the subframe.
|
||||
if (isPan) {
|
||||
// We're going to drive synchronously scrolling an inner frame.
|
||||
Services.obs.notifyObservers(docShell, 'cancel-default-pan-zoom', null);
|
||||
} else {
|
||||
// Let AsyncPanZoomController handle the scrolling gesture.
|
||||
this.scrollCallback = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.scrollCallback(delta.scale(-1));
|
||||
|
||||
// If a pan action happens, cancel the active state of the
|
||||
// current target.
|
||||
if (!this.panning && KineticPanning.isPan()) {
|
||||
if (!this.panning && isPan) {
|
||||
this.panning = true;
|
||||
this._resetActive();
|
||||
this._activationTimer.cancel();
|
||||
}
|
||||
|
||||
if (this.panning) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
}
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
},
|
||||
|
||||
// nsITimerCallback
|
||||
notify: function cp_notify(timer) {
|
||||
this._setActive(this.pointerDownTarget);
|
||||
},
|
||||
|
||||
onKineticBegin: function cp_onKineticBegin(evt) {
|
||||
},
|
||||
@ -249,11 +401,27 @@ const ContentPanning = {
|
||||
.getService(Ci.inIDOMUtils);
|
||||
},
|
||||
|
||||
_resetActive: function cp_resetActive() {
|
||||
let root = this.target.ownerDocument || this.target.document;
|
||||
get _activationTimer() {
|
||||
delete this._activationTimer;
|
||||
return this._activationTimer = Cc["@mozilla.org/timer;1"]
|
||||
.createInstance(Ci.nsITimer);
|
||||
},
|
||||
|
||||
get _activationDelayMs() {
|
||||
let delay = Services.prefs.getIntPref('ui.touch_activation.delay_ms');
|
||||
delete this._activationDelayMs;
|
||||
return this._activationDelayMs = delay;
|
||||
},
|
||||
|
||||
_resetActive: function cp_resetActive() {
|
||||
let elt = this.target || this.pointerDownTarget;
|
||||
let root = elt.ownerDocument || elt.document;
|
||||
this._setActive(root.documentElement);
|
||||
},
|
||||
|
||||
_setActive: function cp_setActive(elt) {
|
||||
const kStateActive = 0x00000001;
|
||||
this._domUtils.setContentState(root.documentElement, kStateActive);
|
||||
this._domUtils.setContentState(elt, kStateActive);
|
||||
},
|
||||
|
||||
get _asyncPanZoomForViewportFrame() {
|
||||
|
@ -97,6 +97,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)
|
||||
@ -162,6 +163,8 @@ TabChild::TabChild(const TabContext& aContext, uint32_t aChromeFlags)
|
||||
, mChromeFlags(aChromeFlags)
|
||||
, mOuterRect(0, 0, 0, 0)
|
||||
, mInnerSize(0, 0)
|
||||
, mActivePointerId(-1)
|
||||
, mTapHoldTimer(nullptr)
|
||||
, mOldViewportWidth(0.0f)
|
||||
, mLastBackgroundColor(NS_RGB(255, 255, 255))
|
||||
, mDidFakeShow(false)
|
||||
@ -243,6 +246,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;
|
||||
@ -1349,42 +1358,135 @@ TabChild::RecvMouseWheelEvent(const WheelEvent& event)
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::DispatchSynthesizedMouseEvent(const nsTouchEvent& aEvent)
|
||||
TabChild::DispatchSynthesizedMouseEvent(uint32_t aMsg, uint64_t aTime,
|
||||
const nsIntPoint& aRefPoint)
|
||||
{
|
||||
// Synthesize a phony mouse event.
|
||||
uint32_t msg;
|
||||
switch (aEvent.message) {
|
||||
case NS_TOUCH_START:
|
||||
msg = NS_MOUSE_BUTTON_DOWN;
|
||||
break;
|
||||
case NS_TOUCH_MOVE:
|
||||
msg = NS_MOUSE_MOVE;
|
||||
break;
|
||||
case NS_TOUCH_END:
|
||||
case NS_TOUCH_CANCEL:
|
||||
msg = NS_MOUSE_BUTTON_UP;
|
||||
break;
|
||||
default:
|
||||
MOZ_NOT_REACHED("Unknown touch event message");
|
||||
}
|
||||
MOZ_ASSERT(aMsg == NS_MOUSE_MOVE || aMsg == NS_MOUSE_BUTTON_DOWN ||
|
||||
aMsg == NS_MOUSE_BUTTON_UP);
|
||||
|
||||
nsIntPoint refPoint(0, 0);
|
||||
if (aEvent.touches.Length()) {
|
||||
refPoint = aEvent.touches[0]->mRefPoint;
|
||||
}
|
||||
|
||||
nsMouseEvent event(true, msg, NULL,
|
||||
nsMouseEvent event(true, aMsg, NULL,
|
||||
nsMouseEvent::eReal, nsMouseEvent::eNormal);
|
||||
event.refPoint = refPoint;
|
||||
event.time = aEvent.time;
|
||||
event.refPoint = aRefPoint;
|
||||
event.time = aTime;
|
||||
event.button = nsMouseEvent::eLeftButton;
|
||||
if (msg != NS_MOUSE_MOVE) {
|
||||
if (aMsg != NS_MOUSE_MOVE) {
|
||||
event.clickCount = 1;
|
||||
}
|
||||
|
||||
DispatchWidgetEvent(event);
|
||||
}
|
||||
|
||||
static nsDOMTouch*
|
||||
GetTouchForIdentifier(const nsTouchEvent& aEvent, int32_t aId)
|
||||
{
|
||||
for (uint32_t i = 0; i < aEvent.touches.Length(); ++i) {
|
||||
nsDOMTouch* touch = static_cast<nsDOMTouch*>(aEvent.touches[i].get());
|
||||
if (touch->mIdentifier == aId) {
|
||||
return touch;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::UpdateTapState(const nsTouchEvent& aEvent, nsEventStatus aStatus)
|
||||
{
|
||||
static bool sHavePrefs;
|
||||
static bool sClickHoldContextMenusEnabled;
|
||||
static nsIntSize sDragThreshold;
|
||||
static int32_t sContextMenuDelayMs;
|
||||
if (!sHavePrefs) {
|
||||
sHavePrefs = true;
|
||||
Preferences::AddBoolVarCache(&sClickHoldContextMenusEnabled,
|
||||
"ui.click_hold_context_menus", true);
|
||||
Preferences::AddIntVarCache(&sDragThreshold.width,
|
||||
"ui.dragThresholdX", 25);
|
||||
Preferences::AddIntVarCache(&sDragThreshold.height,
|
||||
"ui.dragThresholdY", 25);
|
||||
Preferences::AddIntVarCache(&sContextMenuDelayMs,
|
||||
"ui.click_hold_context_menus.delay", 500);
|
||||
}
|
||||
|
||||
bool currentlyTrackingTouch = (mActivePointerId >= 0);
|
||||
if (aEvent.message == NS_TOUCH_START) {
|
||||
if (currentlyTrackingTouch || aEvent.touches.Length() > 1) {
|
||||
// We're tracking a possible tap for another point, or we saw a
|
||||
// touchstart for a later pointer after we canceled tracking of
|
||||
// the first point. Ignore this one.
|
||||
return;
|
||||
}
|
||||
if (aStatus == nsEventStatus_eConsumeNoDefault ||
|
||||
nsIPresShell::gPreventMouseEvents) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsDOMTouch* touch = static_cast<nsDOMTouch*>(aEvent.touches[0].get());
|
||||
mGestureDownPoint = touch->mRefPoint;
|
||||
mActivePointerId = touch->mIdentifier;
|
||||
if (sClickHoldContextMenusEnabled) {
|
||||
MOZ_ASSERT(!mTapHoldTimer);
|
||||
mTapHoldTimer = NewRunnableMethod(this,
|
||||
&TabChild::FireContextMenuEvent);
|
||||
MessageLoop::current()->PostDelayedTask(FROM_HERE, mTapHoldTimer,
|
||||
sContextMenuDelayMs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're not tracking a touch or this event doesn't include the
|
||||
// one we care about, bail.
|
||||
if (!currentlyTrackingTouch) {
|
||||
return;
|
||||
}
|
||||
nsDOMTouch* trackedTouch = GetTouchForIdentifier(aEvent, mActivePointerId);
|
||||
if (!trackedTouch) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIntPoint currentPoint = trackedTouch->mRefPoint;
|
||||
int64_t time = aEvent.time;
|
||||
switch (aEvent.message) {
|
||||
case NS_TOUCH_MOVE:
|
||||
if (abs(currentPoint.x - mGestureDownPoint.x) > sDragThreshold.width ||
|
||||
abs(currentPoint.y - mGestureDownPoint.y) > sDragThreshold.height) {
|
||||
CancelTapTracking();
|
||||
}
|
||||
return;
|
||||
|
||||
case NS_TOUCH_END:
|
||||
if (!nsIPresShell::gPreventMouseEvents) {
|
||||
DispatchSynthesizedMouseEvent(NS_MOUSE_MOVE, time, currentPoint);
|
||||
DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_DOWN, time, currentPoint);
|
||||
DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_UP, time, currentPoint);
|
||||
}
|
||||
// fall through
|
||||
case NS_TOUCH_CANCEL:
|
||||
CancelTapTracking();
|
||||
return;
|
||||
|
||||
default:
|
||||
NS_WARNING("Unknown touch event type");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::FireContextMenuEvent()
|
||||
{
|
||||
MOZ_ASSERT(mTapHoldTimer && mActivePointerId >= 0);
|
||||
RecvHandleLongTap(mGestureDownPoint);
|
||||
CancelTapTracking();
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::CancelTapTracking()
|
||||
{
|
||||
mActivePointerId = -1;
|
||||
if (mTapHoldTimer) {
|
||||
mTapHoldTimer->Cancel();
|
||||
}
|
||||
mTapHoldTimer = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvRealTouchEvent(const nsTouchEvent& aEvent)
|
||||
{
|
||||
@ -1398,8 +1500,8 @@ TabChild::RecvRealTouchEvent(const nsTouchEvent& aEvent)
|
||||
if (innerWindow && innerWindow->HasTouchEventListeners()) {
|
||||
SendContentReceivedTouch(nsIPresShell::gPreventMouseEvents);
|
||||
}
|
||||
} else if (status != nsEventStatus_eConsumeNoDefault) {
|
||||
DispatchSynthesizedMouseEvent(aEvent);
|
||||
} else {
|
||||
UpdateTapState(aEvent, status);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1680,6 +1782,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();
|
||||
@ -1815,6 +1918,9 @@ TabChild::InitRenderingState()
|
||||
observerService->AddObserver(this,
|
||||
BEFORE_FIRST_PAINT,
|
||||
false);
|
||||
observerService->AddObserver(this,
|
||||
DETECT_SCROLLABLE_SUBFRAME,
|
||||
false);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -384,8 +384,18 @@ private:
|
||||
void DispatchMessageManagerMessage(const nsAString& aMessageName,
|
||||
const nsACString& aJSONData);
|
||||
|
||||
// Sends a simulated mouse event from a touch event for compatibility.
|
||||
void DispatchSynthesizedMouseEvent(const nsTouchEvent& aEvent);
|
||||
void DispatchSynthesizedMouseEvent(uint32_t aMsg, uint64_t aTime,
|
||||
const nsIntPoint& aRefPoint);
|
||||
|
||||
// These methods are used for tracking synthetic mouse events
|
||||
// dispatched for compatibility. On each touch event, we
|
||||
// UpdateTapState(). If we've detected that the current gesture
|
||||
// isn't a tap, then we CancelTapTracking(). In the meantime, we
|
||||
// may detect a context-menu event, and if so we
|
||||
// FireContextMenuEvent().
|
||||
void FireContextMenuEvent();
|
||||
void CancelTapTracking();
|
||||
void UpdateTapState(const nsTouchEvent& aEvent, nsEventStatus aStatus);
|
||||
|
||||
nsresult
|
||||
BrowserFrameProvideWindow(nsIDOMWindow* aOpener,
|
||||
@ -411,6 +421,15 @@ private:
|
||||
uint32_t mChromeFlags;
|
||||
nsIntRect mOuterRect;
|
||||
nsIntSize mInnerSize;
|
||||
// When we're tracking a possible tap gesture, this is the "down"
|
||||
// point of the touchstart.
|
||||
nsIntPoint mGestureDownPoint;
|
||||
// The touch identifier of the active gesture.
|
||||
int32_t mActivePointerId;
|
||||
// A timer task that fires if the tap-hold timeout is exceeded by
|
||||
// the touch we're tracking. That is, if touchend or a touchmove
|
||||
// that exceeds the gesture threshold doesn't happen.
|
||||
CancelableTask* mTapHoldTimer;
|
||||
float mOldViewportWidth;
|
||||
nscolor mLastBackgroundColor;
|
||||
ScrollingBehavior mScrolling;
|
||||
|
@ -247,6 +247,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();
|
||||
@ -1130,6 +1150,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.
|
||||
@ -1223,6 +1244,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));
|
||||
|
||||
@ -1309,7 +1334,7 @@ void AsyncPanZoomController::ZoomToRect(const gfxRect& aRect) {
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
|
||||
if (!mFrameMetrics.mMayHaveTouchListeners) {
|
||||
if (!mFrameMetrics.mMayHaveTouchListeners && !mDelayPanning) {
|
||||
mTouchQueue.Clear();
|
||||
return;
|
||||
}
|
||||
@ -1321,12 +1346,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]);
|
||||
}
|
||||
|
@ -109,7 +109,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.
|
||||
*
|
||||
@ -118,6 +118,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
|
||||
@ -585,6 +591,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, we delay delivering
|
||||
// touchmove events to GestureListener until BrowserElementScrolling
|
||||
// decides whether it wants to handle panning for this touch series.
|
||||
bool mDelayPanning;
|
||||
|
||||
friend class Axis;
|
||||
};
|
||||
|
||||
|
@ -42,6 +42,7 @@ parent:
|
||||
async NotifyCompositorTransaction();
|
||||
|
||||
async CancelDefaultPanZoom();
|
||||
async DetectScrollableSubframe();
|
||||
|
||||
async __delete__();
|
||||
|
||||
|
@ -38,6 +38,12 @@ RenderFrameChild::CancelDefaultPanZoom()
|
||||
SendCancelDefaultPanZoom();
|
||||
}
|
||||
|
||||
void
|
||||
RenderFrameChild::DetectScrollableSubframe()
|
||||
{
|
||||
SendDetectScrollableSubframe();
|
||||
}
|
||||
|
||||
PLayersChild*
|
||||
RenderFrameChild::AllocPLayers()
|
||||
{
|
||||
|
@ -20,6 +20,7 @@ public:
|
||||
virtual ~RenderFrameChild() {}
|
||||
|
||||
void CancelDefaultPanZoom();
|
||||
void DetectScrollableSubframe();
|
||||
|
||||
void Destroy();
|
||||
|
||||
|
@ -829,6 +829,15 @@ RenderFrameParent::RecvCancelDefaultPanZoom()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
RenderFrameParent::RecvDetectScrollableSubframe()
|
||||
{
|
||||
if (mPanZoomController) {
|
||||
mPanZoomController->DetectScrollableSubframe();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
PLayersParent*
|
||||
RenderFrameParent::AllocPLayers()
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -3927,3 +3927,8 @@ pref("dom.placeholder.show_on_focus", true);
|
||||
pref("wap.UAProf.url", "");
|
||||
pref("wap.UAProf.tagname", "x-wap-profile");
|
||||
|
||||
// If the user puts a finger down on an element and we think the user
|
||||
// might be executing a pan gesture, how long do we wait before
|
||||
// tentatively deciding the gesture is actually a tap and activating
|
||||
// the target element?
|
||||
pref("ui.touch_activation.delay_ms", 50);
|
||||
|
Loading…
Reference in New Issue
Block a user