3

I want to listen for a middle-button press (mousedown event) and conditionally either take an action (not relevant what specifically) or revert to the default, which would be to enter "autoscroll mode" with the autoscroll icon displayed until the user releases the middle button.

My code listens for the mousedown event and checks whether the pressed button is the middle button. Then determines whether to take the default action, and if not, prevents the default action by calling event.preventDefault. The problem is that there is a delay between the actual mouse press and executing the action, so when the user presses the button and holds it, the action is not taken immediately. Instead, mouse movement is observed for a short amount of time, and based on this movement, either a custom action is taken after the button is released, or the default action for mousedown is taken immediately. In the case the default action is taken, I want Firefox to behave as if the user pressed the middle button, eg. to start autoscroll. This is not happening, since the browser chrome does not react to synthetic events (events produced programmatically).

How do I go about triggering autoscroll in Firefox programmatically? Is there a way to change the behavior (eg. to make autoscroll listen for synthetic events) using userChrome.css?

var elem = document.getElementById('watchedElement')
var active = false

elem.onmousedown = function(event) {
  if (event.button !== MIDDLE_BUTTON || active) { return }
  event.preventDefault()

  startParams = getStartParams()
  active = true
}

elem.onmousemove = function(event) {
  if (!active) { return }

  if (shouldTakeDefaultAction()) {
    elem.dispatchEvent(new MouseEvent('mousedown',
                           {'button': MIDDLE_BUTTON, 'which': MIDDLE_BUTTON}))
    active = false
  }
}

elem.onmouseup = function(event) {
  if (event.button !== MIDDLE_BUTTON || !active) { return }
  event.preventDefault()

  takeCustomAction()
  active = false
}

EDIT: More information

I have made a Greasemonkey plugin for YouTube, which does this:

  • when I click the middle button (without dragging), toggles fullscreen mode
  • when I drag left / right with the middle button pressed, it seeks in the video backward / forward
  • when I drag up / down, I want scrolling

So the default action has to be prevented, and when the mouse has been dragged over a certain distance, we can decide whether to seek or scroll. The angle of the trajectory is computed and evaluated once a certain amount of pixels has been "traveled" by the drag. If the drag is vertical, then I want to activate scrolling, otherwise wait for the button to be released and then seek. The video is sought only after the button is released, eg. no "live preview" while dragging.

kyrill
  • 986
  • 10
  • 27
  • Can you provide more detail on the conditions for the custom behavior to trigger? I assume you're probably trying to do something like gesture recognition? Could it be practical to base the decision on mouse movement observed _before_ the click? Or let the default behavior trigger and then (somehow?) cancel it a few milliseconds later if it looks like the user is trying to perform a gesture instead of scrolling? – Ilmari Karonen Feb 22 '19 at 12:56
  • @IlmariKaronen I added some more information. – kyrill Feb 24 '19 at 13:56
  • Have you tried creating a [WheelEvent](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent) instead of a MouseEvent? (Just a suggestion you might want to look into, not sure if this will fix your problem). – JiFus Feb 27 '19 at 17:40
  • No, that does not do what I want. – kyrill Feb 27 '19 at 17:44

1 Answers1

3

As far as I know, there's no way to trigger Firefox's autoscroll mode from within JavaScript. However, it apparently is possible to interrupt it while it's active, e.g. by calling window.scrollTo().

Also, dragging the mouse cursor horizontally while autoscroll mode is active does nothing if the page doesn't in fact scroll horizontally. Most web pages don't.

Based on those observations, here's (a sketch of) an alternative solution:

  1. Let the middle click propagate and trigger its default action, enabling autoscroll mode.

  2. Do your horizontal drag detection as you normally do.

  3. If you detect a horizontal drag, call window.scrollTo(window.scrollX, window.scrollY), which will turn off Firefox's autoscroll mode, and then proceed with the video seek.

The main disadvantages I see with this method are a) that the autoscroll indicator will be briefly visible while you're detecting the drag direction, and b) that if the user drags the mouse slightly diagonally, they may still end up scrolling the page a few pixels up or down. (This probably depends on how careful and coordinated your users are. The autoscroll feature itself seems to have a small built-in "dead zone", such that you need to move the mouse cursor up or down by at least a dozen or so pixels before the page actually starts scrolling vertically.)

The former seems like an unavoidable but minor interface quirk; your users might not even really notice it. To mitigate the latter issue, you might want to save the values of window.scrollX and window.scrollY in step 1, and use the saved values in step 3, thereby snapping the page back to the scroll position it had before the start of the drag. This might still result in an annoying "jerk" as the page scrolls a bit and then snaps back, but it may be less annoying than having the page move permanently down.

Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
  • Sounds reasonable, but unfortunately doesn't answer the question "how to trigger autoscroll programmatically", so I'm hesitant to accept this answer. But thank you for the suggestion, I will try it out. – kyrill Feb 24 '19 at 20:17
  • I think this answer is trying to say _"you can't trigger it programmatically, but here's how you can achieve a similar end result"_. You basically need to do the opposite of what you are doing: 1) allow the event to bubble, thus triggering autoscroll for a brief period. 2) Then flip the logic inside of your `shouldTakeDefaultAction` method. I would rename it to `shouldPreventAutoScroll`, which will call `window.scrollTo()` and thus disables autoscroll. – Ryan Wheale Feb 28 '19 at 21:25