<NME> mouse_bindings.ts
<BEF> /**
 * @license
 * Copyright 2017 Google Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file Facility for triggering named actions in response to mouse events.
 */

import {RefCounted} from 'neuroglancer/util/disposable';
import {ActionEvent, dispatchEventWithModifiers, EventActionMap, EventActionMapInterface, registerActionListener} from 'neuroglancer/util/event_action_map';

export class MouseEventBinder<EventMap extends EventActionMapInterface> extends RefCounted {
  private dispatch(baseIdentifier: string, event: MouseEvent) {
    dispatchEventWithModifiers(baseIdentifier, event, event, this.eventMap);
  }
  constructor(public target: EventTarget, public eventMap: EventMap) {
    super();
    this.registerEventListener(target, 'wheel', (event: WheelEvent) => {
      this.dispatch('wheel', event);
    });
    this.registerEventListener(target, 'click', (event: MouseEvent) => {
      this.dispatch(`click${event.button}`, event);
    });
    this.registerEventListener(target, 'dblclick', (event: MouseEvent) => {
      this.dispatch(`dblclick${event.button}`, event);
    });
    this.registerEventListener(target, 'mousedown', (event: MouseEvent) => {
      this.dispatch(`mousedown${event.button}`, event);
    });
    this.registerEventListener(target, 'mouseup', (event: MouseEvent) => {
      this.dispatch(`mouseup${event.button}`, event);
    });
  }
      // https://github.com/google/neuroglancer/issues/365
      //
      // Notes:
      //
      // - If both the left and right mouse buttons are both pressed at the same
      //   time, this method of disambiguation does not work.
      //
      // - Firefox seems to "remember" that this translation of the button
      //   number was done, and also sends the `mouseup` event with a button
      //   number of 2, even if the control key was released before releasing
      //   the mouse button.  That means `mouse_drag.ts` works unmodified with
      //   this translation (since it waits for a mouseup event with the same
      //   button number).
      //
      // - This method of disambiguation does not work for `mouseup` events,
      //   since the button has already been released and therefore is not
      //   included in `buttons` anyway.  Fortunately `mouseup` events are not
      //   commonly used in Neuroglancer.
      if (button === 2 && (event.buttons & 3) === 1) {
        // `button` is 2 (right button), but only the left button is currently pressed.
        button = 0;
      }
      this.dispatch(`mousedown${button}`, event);
    });
    this.registerEventListener(target, 'mouseup', (event: MouseEvent) => {
      if (commonHandler !== undefined) commonHandler(event);
      this.dispatch(`mouseup${event.button}`, event);
    });
  }
}

export {EventActionMap, registerActionListener};
export type {EventActionMapInterface, ActionEvent}

<MSG> tests: improve robustness of Python webdriver tests in Firefox

<DFF> @@ -25,21 +25,28 @@ export class MouseEventBinder<EventMap extends EventActionMapInterface> extends
   private dispatch(baseIdentifier: string, event: MouseEvent) {
     dispatchEventWithModifiers(baseIdentifier, event, event, this.eventMap);
   }
-  constructor(public target: EventTarget, public eventMap: EventMap) {
+  constructor(
+      public target: EventTarget, public eventMap: EventMap,
+      commonHandler?: (event: MouseEvent) => void) {
     super();
     this.registerEventListener(target, 'wheel', (event: WheelEvent) => {
+      if (commonHandler !== undefined) commonHandler(event);
       this.dispatch('wheel', event);
     });
     this.registerEventListener(target, 'click', (event: MouseEvent) => {
+      if (commonHandler !== undefined) commonHandler(event);
       this.dispatch(`click${event.button}`, event);
     });
     this.registerEventListener(target, 'dblclick', (event: MouseEvent) => {
+      if (commonHandler !== undefined) commonHandler(event);
       this.dispatch(`dblclick${event.button}`, event);
     });
     this.registerEventListener(target, 'mousedown', (event: MouseEvent) => {
+      if (commonHandler !== undefined) commonHandler(event);
       this.dispatch(`mousedown${event.button}`, event);
     });
     this.registerEventListener(target, 'mouseup', (event: MouseEvent) => {
+      if (commonHandler !== undefined) commonHandler(event);
       this.dispatch(`mouseup${event.button}`, event);
     });
   }

tests: improve robustness of Python webdriver tests in Firefox