mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 1021804 - Long press on news story links invoke context menu, r=kats, wesj
This commit is contained in:
parent
d73c9ba84e
commit
e8fa8da225
@ -108,7 +108,8 @@ public class GeckoEvent {
|
||||
TELEMETRY_UI_SESSION_STOP(43),
|
||||
TELEMETRY_UI_EVENT(44),
|
||||
GAMEPAD_ADDREMOVE(45),
|
||||
GAMEPAD_DATA(46);
|
||||
GAMEPAD_DATA(46),
|
||||
LONG_PRESS(47);
|
||||
|
||||
public final int value;
|
||||
|
||||
@ -420,6 +421,16 @@ public class GeckoEvent {
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GeckoEvent that contains the data from the LongPressEvent, to be
|
||||
* dispatched in CSS pixels relative to gecko's scroll position.
|
||||
*/
|
||||
public static GeckoEvent createLongPressEvent(MotionEvent m) {
|
||||
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LONG_PRESS);
|
||||
event.initMotionEvent(m, false);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void initMotionEvent(MotionEvent m, boolean keepInViewCoordinates) {
|
||||
mAction = m.getActionMasked();
|
||||
mTime = (System.currentTimeMillis() - SystemClock.elapsedRealtime()) + m.getEventTime();
|
||||
|
@ -1344,7 +1344,8 @@ class JavaPanZoomController
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent motionEvent) {
|
||||
sendPointToGecko("Gesture:LongPress", motionEvent);
|
||||
GeckoEvent e = GeckoEvent.createLongPressEvent(motionEvent);
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -702,7 +702,7 @@ var SelectionHandler = {
|
||||
attachCaret: function sh_attachCaret(aElement) {
|
||||
// Ensure it isn't disabled, isn't handled by Android native dialog, and is editable text element
|
||||
if (aElement.disabled || InputWidgetHelper.hasInputWidget(aElement) || !this.isElementEditableText(aElement)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this._initTargetInfo(aElement, this.TYPE_CURSOR);
|
||||
@ -722,6 +722,8 @@ var SelectionHandler = {
|
||||
handles: [this.HANDLE_TYPE_MIDDLE]
|
||||
});
|
||||
this._updateMenu();
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
// Target initialization for both TYPE_CURSOR and TYPE_SELECTION
|
||||
|
@ -2044,16 +2044,17 @@ var NativeWindow = {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
contextmenus: {
|
||||
items: {}, // a list of context menu items that we may show
|
||||
DEFAULT_HTML5_ORDER: -1, // Sort order for HTML5 context menu items
|
||||
|
||||
init: function() {
|
||||
Services.obs.addObserver(this, "Gesture:LongPress", false);
|
||||
BrowserApp.deck.addEventListener("contextmenu", this.show.bind(this), false);
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
Services.obs.removeObserver(this, "Gesture:LongPress");
|
||||
BrowserApp.deck.removeEventListener("contextmenu", this.show.bind(this), false);
|
||||
},
|
||||
|
||||
add: function() {
|
||||
@ -2295,7 +2296,7 @@ var NativeWindow = {
|
||||
},
|
||||
|
||||
// Returns true if there are any context menu items to show
|
||||
shouldShow: function() {
|
||||
_shouldShow: function() {
|
||||
for (let context in this.menus) {
|
||||
let menu = this.menus[context];
|
||||
if (menu.length > 0) {
|
||||
@ -2378,36 +2379,51 @@ var NativeWindow = {
|
||||
* any html5 context menus we are about to show, and fire some local notifications
|
||||
* for chrome consumers to do lazy menuitem construction
|
||||
*/
|
||||
_sendToContent: function(x, y) {
|
||||
let target = this._findTarget(x, y);
|
||||
if (!target)
|
||||
show: function(event) {
|
||||
// Android Long-press / contextmenu event provides clientX/Y data. This is not provided
|
||||
// by mochitest: test_browserElement_inproc_ContextmenuEvents.html.
|
||||
if (!event.clientX || !event.clientY) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._target = target;
|
||||
// Find the target of the long-press / contextmenu event.
|
||||
this._target = this._findTarget(event.clientX, event.clientY);
|
||||
if (!this._target) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(null, "before-build-contextmenu", "");
|
||||
this._buildMenu(x, y);
|
||||
// Try to build a list of contextmenu items. If successful, actually show the
|
||||
// native context menu by passing the list to Java.
|
||||
this._buildMenu(event.clientX, event.clientY);
|
||||
if (this._shouldShow()) {
|
||||
BrowserEventHandler._cancelTapHighlight();
|
||||
|
||||
// only send the contextmenu event to content if we are planning to show a context menu (i.e. not on every long tap)
|
||||
if (this.shouldShow()) {
|
||||
let event = target.ownerDocument.createEvent("MouseEvent");
|
||||
event.initMouseEvent("contextmenu", true, true, target.defaultView,
|
||||
0, x, y, x, y, false, false, false, false,
|
||||
0, null);
|
||||
target.ownerDocument.defaultView.addEventListener("contextmenu", this, false);
|
||||
target.dispatchEvent(event);
|
||||
} else {
|
||||
this.menus = null;
|
||||
Services.obs.notifyObservers({target: target, x: x, y: y}, "context-menu-not-shown", "");
|
||||
// Consume / preventDefault the event, and show the contextmenu.
|
||||
event.preventDefault();
|
||||
this._innerShow(this._target, event.clientX, event.clientY);
|
||||
this._target = null;
|
||||
|
||||
if (SelectionHandler.canSelect(target)) {
|
||||
if (!SelectionHandler.startSelection(target, {
|
||||
mode: SelectionHandler.SELECT_AT_POINT,
|
||||
x: x,
|
||||
y: y
|
||||
})) {
|
||||
SelectionHandler.attachCaret(target);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If no context-menu for long-press event, it may be meant to trigger text-selection.
|
||||
this.menus = null;
|
||||
Services.obs.notifyObservers(
|
||||
{target: this._target, x: event.clientX, y: event.clientY}, "context-menu-not-shown", "");
|
||||
|
||||
if (SelectionHandler.canSelect(this._target)) {
|
||||
// If textSelection WORD is successful,
|
||||
// consume / preventDefault the context menu event.
|
||||
if (SelectionHandler.startSelection(this._target,
|
||||
{ mode: SelectionHandler.SELECT_AT_POINT, x: event.clientX, y: event.clientY })) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
// If textSelection caret-attachment is successful,
|
||||
// consume / preventDefault the context menu event.
|
||||
if (SelectionHandler.attachCaret(this._target)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -2477,17 +2493,6 @@ var NativeWindow = {
|
||||
}
|
||||
},
|
||||
|
||||
// Actually shows the native context menu by passing a list of context menu items to
|
||||
// show to the Java.
|
||||
_show: function(aEvent) {
|
||||
let popupNode = this._target;
|
||||
this._target = null;
|
||||
if (aEvent.defaultPrevented || !popupNode) {
|
||||
return;
|
||||
}
|
||||
this._innerShow(popupNode, aEvent.clientX, aEvent.clientY);
|
||||
},
|
||||
|
||||
// Walks the DOM tree to find a title from a node
|
||||
_findTitle: function(node) {
|
||||
let title = "";
|
||||
@ -2645,20 +2650,6 @@ var NativeWindow = {
|
||||
}
|
||||
},
|
||||
|
||||
// Called when the contextmenu is done propagating to content. If the event wasn't cancelled, will show a contextmenu.
|
||||
handleEvent: function(aEvent) {
|
||||
BrowserEventHandler._cancelTapHighlight();
|
||||
aEvent.target.ownerDocument.defaultView.removeEventListener("contextmenu", this, false);
|
||||
this._show(aEvent);
|
||||
},
|
||||
|
||||
// Called when a long press is observed in the native Java frontend. Will start the process of generating/showing a contextmenu.
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
let data = JSON.parse(aData);
|
||||
// content gets first crack at cancelling context menus
|
||||
this._sendToContent(data.x, data.y);
|
||||
},
|
||||
|
||||
// XXX - These are stolen from Util.js, we should remove them if we bring it back
|
||||
makeURLAbsolute: function makeURLAbsolute(base, url) {
|
||||
// Note: makeURI() will throw if url is not a valid URI
|
||||
|
@ -454,6 +454,7 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jobject jobj)
|
||||
break;
|
||||
|
||||
case MOTION_EVENT:
|
||||
case LONG_PRESS:
|
||||
mTime = jenv->GetLongField(jobj, jTimeField);
|
||||
mMetaState = jenv->GetIntField(jobj, jMetaStateField);
|
||||
mCount = jenv->GetIntField(jobj, jCountField);
|
||||
|
@ -720,6 +720,7 @@ public:
|
||||
TELEMETRY_UI_EVENT = 44,
|
||||
GAMEPAD_ADDREMOVE = 45,
|
||||
GAMEPAD_DATA = 46,
|
||||
LONG_PRESS = 47,
|
||||
dummy_java_enum_list_end
|
||||
};
|
||||
|
||||
|
@ -865,6 +865,31 @@ nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae)
|
||||
break;
|
||||
}
|
||||
|
||||
// LongPress events mostly trigger contextmenu options, but can also lead to
|
||||
// textSelection processing.
|
||||
case AndroidGeckoEvent::LONG_PRESS: {
|
||||
win->UserActivity();
|
||||
|
||||
nsCOMPtr<nsIObserverService> obsServ = mozilla::services::GetObserverService();
|
||||
obsServ->NotifyObservers(nullptr, "before-build-contextmenu", nullptr);
|
||||
|
||||
nsIntPoint pt;
|
||||
const nsTArray<nsIntPoint>& points = ae->Points();
|
||||
if (points.Length() > 0) {
|
||||
pt = nsIntPoint(points[0].x, points[0].y);
|
||||
}
|
||||
|
||||
// Clamp our point within bounds, and locate the target element for the event.
|
||||
pt.x = clamped(pt.x, 0, std::max(gAndroidBounds.width - 1, 0));
|
||||
pt.y = clamped(pt.y, 0, std::max(gAndroidBounds.height - 1, 0));
|
||||
nsWindow *target = win->FindWindowForPoint(pt);
|
||||
if (target) {
|
||||
// Send the contextmenu event to Gecko.
|
||||
target->OnContextmenuEvent(ae);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case AndroidGeckoEvent::NATIVE_GESTURE_EVENT: {
|
||||
nsIntPoint pt(0,0);
|
||||
const nsTArray<nsIntPoint>& points = ae->Points();
|
||||
@ -1000,6 +1025,36 @@ nsWindow::OnMouseEvent(AndroidGeckoEvent *ae)
|
||||
DispatchEvent(&event);
|
||||
}
|
||||
|
||||
void
|
||||
nsWindow::OnContextmenuEvent(AndroidGeckoEvent *ae)
|
||||
{
|
||||
nsRefPtr<nsWindow> kungFuDeathGrip(this);
|
||||
|
||||
CSSPoint pt;
|
||||
const nsTArray<nsIntPoint>& points = ae->Points();
|
||||
if (points.Length() > 0) {
|
||||
pt = CSSPoint(points[0].x, points[0].y);
|
||||
}
|
||||
|
||||
// Send the contextmenu event.
|
||||
WidgetMouseEvent contextMenuEvent(true, NS_CONTEXTMENU, this,
|
||||
WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
|
||||
contextMenuEvent.refPoint =
|
||||
LayoutDeviceIntPoint(RoundedToInt(pt * GetDefaultScale()));
|
||||
|
||||
nsEventStatus contextMenuStatus;
|
||||
DispatchEvent(&contextMenuEvent, contextMenuStatus);
|
||||
|
||||
// If the contextmenu event was consumed (preventDefault issued), we follow with a
|
||||
// touchcancel event. This avoids followup touchend events passsing through and
|
||||
// triggering further element behaviour such as link-clicks.
|
||||
if (contextMenuStatus == nsEventStatus_eConsumeNoDefault) {
|
||||
WidgetTouchEvent canceltouchEvent = ae->MakeTouchEvent(this);
|
||||
canceltouchEvent.message = NS_TOUCH_CANCEL;
|
||||
DispatchEvent(&canceltouchEvent);
|
||||
}
|
||||
}
|
||||
|
||||
bool nsWindow::OnMultitouchEvent(AndroidGeckoEvent *ae)
|
||||
{
|
||||
nsRefPtr<nsWindow> kungFuDeathGrip(this);
|
||||
|
@ -48,6 +48,7 @@ public:
|
||||
|
||||
nsWindow* FindWindowForPoint(const nsIntPoint& pt);
|
||||
|
||||
void OnContextmenuEvent(mozilla::AndroidGeckoEvent *ae);
|
||||
bool OnMultitouchEvent(mozilla::AndroidGeckoEvent *ae);
|
||||
void OnNativeGestureEvent(mozilla::AndroidGeckoEvent *ae);
|
||||
void OnMouseEvent(mozilla::AndroidGeckoEvent *ae);
|
||||
|
Loading…
Reference in New Issue
Block a user