I’ve built a Chrome extension (https://chrome.google.com/webstore/detail/omnibear/cjieakdeocmiimmphkfhdfbihhncoocn). It adds an item to the context menu, so the user can right-click an element on the page and reply to the associated “entry” on their blog.
My problem is, whether or not I can find an “entry” depends on where they click, and involves traversing up the DOM tree. I have the code in place that highlights the entry when the contextmenu event is fired, but this is too late to update the text in the context menu. Is there a way to update the context menu text dynamically as it is opened?
(e.g., I would like it to say “Reply to page” if no entry is found, or “Reply to tweet” if a tweet is found)
No, in a Chrome extension/Firefox WebExtension, there is no way to have your JavaScript make dynamic changes to the context menu at the time the context menu opens. The only thing that dynamically changes at the time the context menu opens is: if you have added a context menu item that has the selection context for its ContextType, then you can specify %s in the title, which will be replaced with the current selection.
Change the context menu before it begins to open
Mouse events
To do what you desire, you need to make the changes to the context menu prior to the beginning of the context menu being opened. This can be accomplished by having a content script that listens to the mouseup event. You may have to end up using the mousedown event, or one or more of the mouseenter/mouseleave/mouseover/mouseout events. If you do need to use mousedown, once that event fires you should then start listening to some of the mouseenter/mouseleave/mouseover/mouseout events to tell when the element the mouse is over changes, or just assume the user releases the button on the same element on which they pressed it down.
If the mouseup/mousedown event is for button=2, then you will need to message your background script to change the context menu with contextMenus.update(). There are multiple asynchronous portions of this process. This may make for various race conditions, and may necessitate using events which give you earlier notification than mouseup (e.g. mousedown, mouseenter/mouseleave/mouseover/mouseout).
What events you need to watch for may be operating system/platform/windowing system dependent. You will need to test to determine what is needed in each environment you plan to support.
Keyboard events
The context menu can also be opened using a few keyboard sequences. You will need to listen for and perform the same messaging as is needed for mouse events. You will need to determine what these events are in all operating systems/platforms/windowing systems which you are supporting.
Linux
On Linux it appears to vary at least based on platform.
OSX
On OSX, it appears that a keyboard shortcut is only available if enabled.
Windows
On Windows, the keyboard shortcuts to open the context menu are:
Context Menu key
You will need to detect the following event sequence:
keydown { target: <body>, key: "ContextMenu", charCode: 0, keyCode: 93 }
keypress { target: <body>, key: "ContextMenu", charCode: 0, keyCode: 93 }
keyup { target: <body>, key: "ContextMenu", charCode: 0, keyCode: 93 }
The context menu does not open until around the same time the keyup event fires. I did not test to see if the keyup happens early enough for you to make changes to the context menu prior to it opening.
Keyboard combination: Shift-F10
You will need to detect the following event sequence:
keydown Shift { target: <body>, key: "Shift", charCode: 0, keyCode: 16, shiftKey: true }
keydown Shift { target: <body>, key: "F10", charCode: 0, keyCode: 121, shiftKey: true }
keypress Shift { target: <body>, key: "F10", charCode: 0, keyCode: 121, shiftKey: true }
In this case, the context menu opens at around the same time as the shifted F10 keydown/keypress events fire. No keyup event fires for this keyboard sequence.
Related
I have the following basic setup
const St = imports.gi.St;
const Webkit = imports.gi.WebKit2;
const Gtk = imports.gi.Gtk;
let window, webView;
function _handleclick(){
window = new Gtk.Window({
type: Gtk.WindowType.TOPLEVEL
decorated: false,
skip_taskbar_hint: true,
skip_pager_hint: true,
resizable: false,
});
window.set_default_size(400, 600);
webView = new Webkit.WebView();
webView.load_uri(https://www.example.com);
window.add(webView);
window.set_position(Gtk.WindowPosition.MOUSE);
window.show_all();
}
The function is called when the user clicks a St.Bin object, defined like this
button = new St.Bin({
style_class: 'panel-button',
reactive: true,
can_focus: true,
x_expand: true,
y_expand: false,
track_hover: true
});
when this function is called, the Gtk.Window containing the WebView is drawn successfully, and the website displayed can be focused and interacted with using the keyboard (tab, enter, alt+tab, etc.) but it doesn't appear to pick up any mouse events. Even though clicking on the window will allow it to grab focus, the elements of the displayed website do not respond to any mouse interaction, be it clicking or scrolling.
I've been going over the documentation for both WebView and Window, according to the former, "WebKitWebView is scrollable by itself", which seems to imply that it should already be listening for mouse events, and from my spotty understanding of this article on Gtk event propagation, since the window has focus, it should be propagating the events to the GtkWidget; clearly there's something I'm failing to understand here or perhaps the mouse event is being picked up by something else? The website show in the webview seems to receive an event when the mouse pointer enters or exits the webview area, but no button presses or other movement.
I have tried fumbling around with a bunch of direrent attibutes and functions I've found in the documentation but every resource online would seem to indicate that the basic setup of a webview inside a gtk window should work without further tweaking.
I want to dispatch user actions on a text input field. I want it to be as if an actual person has used their keyboard to click a text input field, hit the spacebar, and then hit the backspace key. With the code I used nothing has happened.
THE JSFIDDLE https://jsfiddle.net/zvrhfq9j/
THE HTML
<input type="text" name="psychTree-savedNodes" style="width:20%" />
THE JS
$('input[name="psychTree-savedNodes"]').focus();
$('input[name="psychTree-savedNodes"]').trigger({type: 'keypress', which: 32, keyCode: 32});
$('input[name="psychTree-savedNodes"]').trigger({type: 'keyup', which: 32, keyCode: 32});
$('input[name="psychTree-savedNodes"]').trigger({type: 'keydown', which: 32, keyCode: 32});
$('input[name="psychTree-savedNodes"]').trigger({type: 'keypress', which: 8, keyCode: 8});
$('input[name="psychTree-savedNodes"]').trigger({type: 'keyup', which: 8, keyCode: 8});
$('input[name="psychTree-savedNodes"]').trigger({type: 'keydown', which: 8, keyCode: 8});
THE SOLUTION: was to actually dispatch the events. Thanks for those that actually answered!
You can simulate user actions, but they won't perform the default functions because it will set isTrusted to false (for security reasons).
For instance, you can build an event to dispatch to a text field that "types the letter 'a'". It will (err, should) trigger any custom functions bound to that event handler (el.onkeydown(e){ if( e.key == 'a' ) …), but it will not type the letter a into the text field, or otherwise process default browser functionality based on that keystroke.
It's a browser implementation, and not something you can get around. So while you can't "type" directly into fields, you can run events based off the event handlers that are attached to those specific events.
I've whipped up a codepen example to show what I mean: https://codepen.io/xhynk/pen/jOPbWzz
The page loads blue, with 2 fields
In 1 second, it will run a function that "simulates" a "click > space > backspace" chain of events.
The events will display what they did inside the "no" input
The page turns green to show it went off.
If you're so inclined, you can change the event codes to letters to see that the actual keystrokes never appear in the "yes" input. I've added the "no" box that has keydown and onclick event handlers to show what events fired and in what order by changing the value of it.
You can also manually click on the "yes" input, then hit "space" and "backspace" and see they too fire the event handler functions (the event functions will run like they did when they were simulated) but this time they will actually show up in the box (because you actually did them, so they are trusted events).
Long story short, you can't fully simulate "typing" keypress events that are trusted, but you can handle those specific keypress events however you want (even changing the value of the current input if you so choose).
Edit: I see a chain of comments has spawned up at the top. This is a browser function that you can't get around, unless you find (or build) a browser that handles untrusted isTrusted:false events. This doesn't have anything to do with local or sandbox programming environments.
Note this question. I see that there are other approaches besides just triggering the tab keypress event, but I'd still like to know why triggering the tab key press event doesn't move focus to the next input field.
Code Pen
HTML
<textarea></textarea>
<textarea></textarea>
<textarea></textarea>
JS
$('textarea').on('keydown', function(e) {
if (e.metaKey && e.which === 40) {
console.log('test');
$(this).trigger({
type: 'keypress',
which: 9
});
}
});
Because the tab event is a native browser event/action for changing focus. The .trigger() function only triggers the event handlers that are assigned to it. Note there is more information given from jQuery's site:
The .trigger() function cannot be used to mimic native browser events, such as clicking on a file input box or an anchor tag. This is because, there is no event handler attached using jQuery's event system that corresponds to these events.
There is a plug-in for this though called jquery-simulate to handle this. That being said the tab key changing focus is actually a default action in the web browser. Firing a browsers native event does not mean it will do it's default action, as the documentation for KeyboardEvents mentions:
Note that manually firing an event does not generate the default action associated with that event. For example, manually firing a key event does not cause that letter to appear in a focused text input. In the case of UI events, this is important for security reasons, as it prevents scripts from simulating user actions that interact with the browser itself.
I have an add-on, where when user holds down right mouse button, they can scroll the wheel and it will change tabs.
Now in Windows, if user pushes down on right button it doesn't open context menu. In linux it opens context menu on mouse down.
DOMMouseScroll event listner is on the chrome window ie: window.addEventListener('DOMMouseScroll', func, true)
So if you copy this code and paste it to scratchapd:
window.addEventListener('DOMMouseScroll', function() {
window.removeEventListener('DOMMouseScroll', arguments.callee, true);
console.log('scroll caught and removed')
}, true)
then go to the browser window, right click to open context menu, and scroll while mouse is over the context-menu, in windows, the scroll is caught. In linux it is not. (curious note here: mousedown events are caught if listener added on window and the click was in the context-menu).
I thought setting consume rollup event to no consume would solve it. But it's not.
Any ideas on how to fix this issue and does consumeRollupEvent have any affect here?
Currently i was setting consume rollup event to false in the popupshowing event of context-menu:
noConsume: function(event) {
if (event.target != document.getElementById('contentAreaContextMenu')) { return }
if (event.target.popupBoxObject) {
//event.target.popupBoxObject.setConsumeRollupEvent(Components.interfaces.nsIPopupBoxObject.ROLLUP_NO_CONSUME); //no longer support setConsumeRollupEvent
}
event.target.setAttribute('consumeoutsideclicks', false);
event.target.consumeoutsideclicks = false;
},
Right now I'm suspecting it's the DOMMouseScroll event that's not being caught, so i changed it to addEventListner('wheel'... and sent user test addon, waiting for his word back.
I don't think setConsumeRollupEvent will help you to intercept the scroll events. It refers to the rolling up of the context menu (i.e. the event that caused the context menu to close); nothing to do with mouse wheel rolling.
https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/setConsumeRollupEvent
It might be worth concentrating on why there is a difference in behaviour between Windows and Linux. It might be a Firefox bug or at the very least it might give you a clue about the best way to work around it. Maybe there is a way to prevent the context menu being invoked on MouseDown in Linux, it sounds like that would be the most desirable outcome for your add-on.
=I am trying to develop a simple drag/drop UI in my web application. An item can be dragged by a mouse or a finger and then can be dropped into one of several drop zones. When an item is dragged over a drop zone (but not yet released), that zone is highlighted, marking safe landing location. That works perfectly fine with mouse events, but I'm stuck with touchstart/touchmove/touchend family on the iPhone/iPad.
The problem is that when an item's ontouchmove event handler is called, its event.touches[0].target always points to the originating HTML element (the item) and not the element which is currently under the finger. Moreover, when an item is dragged by finger over some drop zone, that drop zone's own touchmove handlers isn't called at all. That essentially means I can't determine when a finger is above any of the drop zones, and therefore can't highlight them as needed. At the same time, when using a mouse, mousedown is correctly fired for all HTML elements under the cursor.
Some people confirm that it's supposed to work like that, for instance http://www.sitepen.com/blog/2008/07/10/touching-and-gesturing-on-the-iphone/:
For those of you coming from the normal web design world, in a normal mousemove event, the node passed in the target attribute is usually what the mouse is currently over. But in all iPhone touch events, the target is a reference to the originating node.
Question: is there any way to determine the actual element under a finger (NOT the initially touched element which can be different in many circumstances)?
That's certainly not how event targets are supposed to work. Yet another DOM inconsistency that we're probably all now stuck with forever, due to a vendor coming up with extensions behind closed doors without any review.
Use document.elementFromPoint to work around it.
document.elementFromPoint(event.clientX, event.clientY);
The accepted answer from 2010 no longer works: touchmove does not have a clientX or clientY attribute. (I'm guessing it used to since the answer has a number of upvotes, but it doesn't currently.)
Current solution is:
var myLocation = event.originalEvent.changedTouches[0];
var realTarget = document.elementFromPoint(myLocation.clientX, myLocation.clientY);
Tested and works on:
Safari on iOS
Chrome on iOS
Chrome on Android
Chrome on touch-enabled Windows desktop
FF on touch-enabled Windows desktop
Does NOT work on:
IE on touch-enabled Windows desktop
Not tested on:
Windows Phone
I've encountered the same problem on Android (WebView + Phonegap). I want to be able to drag elements around and detect when they are being dragged over a certain other element.
For some reason touch-events seem to ignore the pointer-events attribute value.
Mouse:
if pointer-events="visiblePainted" is set then event.target will point to the dragged element.
if pointer-events="none" is set then event.target will point to the element under the dragged element (my drag-over zone)
This is how things are supposed to work and why we have the pointer-events attribute in the first place.
Touch:
event.target always points to the dragged element, regardless of pointer-events value which is IMHO wrong.
My workaround is to create my own drag-event object (a common interface for both mouse and touch events) that holds the event coordinates and the target:
for mouse events I simply reuse the mouse event as is
for touch event I use:
DragAndDrop.prototype.getDragEventFromTouch = function (event) {
var touch = event.touches.item(0);
return {
screenX: touch.screenX,
screenY: touch.screenY,
clientX: touch.clientX,
clientY: touch.clientY,
pageX: touch.pageX,
pageY: touch.pageY,
target: document.elementFromPoint(touch.screenX, touch.screenY)
};
};
And then use that for processing (checking whether the dragged object is in my drag-over zone). For some reason document.elementFromPoint() seems to respect the pointer-events value even on Android.
Try using event.target.releasePointerCapture(event.pointerId) in the pointerdown handler.
We're now in 2022, this is intended and specified behavior - it's called "Implict Pointer Capture"
See the W3 spec on Pointer Events
Direct manipulation devices should behave exactly as if setPointerCapture was called on the target element just before the invocation of any pointerdown listeners. The hasPointerCapture API may be used (eg. within any pointerdown listener) to determine whether this has occurred.
elementFromPoint is a possible solution, but it seems you can also use releasePointerCapture as shown in the following demo. Touching and holding on the green div will get mouse move events for targets outside of it, whereas the red div has the default behavior.
const outputDiv = document.getElementById('output-div');
const releaseDiv = document.getElementById('test-release-div');
const noreleaseDiv = document.getElementById('test-norelease-div');
releaseDiv.addEventListener('pointerdown', function(e) {
outputDiv.innerHTML = "releaseDiv-pointerdown";
if (e.target.hasPointerCapture(e.pointerId)) {
e.target.releasePointerCapture(e.pointerId);
}
});
noreleaseDiv.addEventListener('pointerdown', function(e) {
outputDiv.innerHTML = "noreleaseDiv-pointerdown";
});
document.addEventListener('pointermove', function(e) {
outputDiv.innerHTML = e.target.id;
});
<div id="output-div"></div>
<div id="test-release-div" style="width:300px;height:100px;background-color:green;touch-action:none;user-select:none">Touch down here and move around, this releases implicit pointer capture</div>
<div id="test-norelease-div" style="width:300px;height:100px;background-color:red;touch-action:none;user-select:none">Touch down here and move around, this doesn't release implicit pointer capture<div>
So touch events have different "philosophy" when it comes to how they interact:
Mouse moves = "hover" like behavior
Touch moves = "drags" like behavior
This difference comes from the fact that there can not be a touchmove without touchstart event preceding it as a user has to touch screen to start this interaction. With mouse of course a user can mousemove all over the screen without ever pressing buttoon (mousedown event)
This is why basically we can't hope to use things like hover effects with touch:
element:hover {
background-color: yellow;
}
And this is why when user touches the screen with 1 finger the first event (touchstart) acquires the target element and the subsequent events (touchmove) will hold the reference to the original element where touch started. It feels wrong but there is this logic that you might need original target info as well. So ideally in future there should be both (source target and current target) available.
So common practice of today (2018) where screens can be mouse AND touch at the same time is still to attach both listeners (mouse and touch) and then "normalize" event coordinates and use above mentioned browser api to find element in those coordinates:
// get coordinates depending on pointer type:
var xcoord = event.touches? event.touches[0].pageX : event.pageX;
var ycoord = event.touches? event.touches[0].pageY : event.pageY;
// get element in coordinates:
var targetElement = document.elementFromPoint(xcoord, ycoord);
// validate if this is a valid element for our case:
if (targetElement && targetElement.classList.contains("dropZone")) {
}
JSP64's answer didn't fully work since event.originalEvent always returned undefined. A slight modification as follows works now.
var myLocation = event.touches[0];
var realTarget = document.elementFromPoint(myLocation.clientX, myLocation.clientY);