Path: blob/trunk/third_party/closure/goog/testing/events/events.js
4122 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Event Simulation.8*9* Utility functions for simulating events at the Closure level. All functions10* in this package generate events by calling goog.events.fireListeners,11* rather than interfacing with the browser directly. This is intended for12* testing purposes, and should not be used in production code.13*14* The decision to use Closure events and dispatchers instead of the browser's15* native events and dispatchers was conscious and deliberate. Native event16* dispatchers have their own set of quirks and edge cases. Pure JS dispatchers17* are more robust and transparent.18*19* If you think you need a testing mechanism that uses native Event objects,20* please, please email closure-tech first to explain your use case before you21* sink time into this.22*23* TODO(user): Migrate to explicitly non-nullable types. At present, many24* functions in this file expect non-null inputs but do not explicitly25* indicate this.26*/2728goog.setTestOnly('goog.testing.events');29goog.provide('goog.testing.events');30goog.provide('goog.testing.events.Event');3132goog.require('goog.Disposable');33goog.require('goog.asserts');34goog.require('goog.dom.NodeType');35goog.require('goog.events');36goog.require('goog.events.BrowserEvent');37goog.require('goog.events.EventTarget');38goog.require('goog.events.EventType');39goog.require('goog.events.KeyCodes');40goog.require('goog.object');41goog.require('goog.style');42goog.require('goog.userAgent');43goog.requireType('goog.math.Coordinate');44454647/**48* goog.events.BrowserEvent expects an Event so we provide one for JSCompiler.49*50* This clones a lot of the functionality of goog.events.Event. This used to51* use a mixin, but the mixin results in confusing the two types when compiled.52*53* @param {string} type Event Type.54* @param {Object=} opt_target Reference to the object that is the target of55* this event.56* @constructor57* @extends {Event}58*/59goog.testing.events.Event = function(type, opt_target) {60'use strict';61this.type = type;6263this.target = /** @type {EventTarget} */ (opt_target || null);6465this.currentTarget = this.target;66};676869/**70* Whether to cancel the event in internal capture/bubble processing for IE.71* @type {boolean}72* @public73* @suppress {underscore|visibility} Technically public, but referencing this74* outside this package is strongly discouraged.75*/76goog.testing.events.Event.prototype.propagationStopped_ = false;777879/** @override */80goog.testing.events.Event.prototype.defaultPrevented = false;818283/**84* Return value for in internal capture/bubble processing for IE.85* @type {boolean}86* @public87* @suppress {underscore|visibility} Technically public, but referencing this88* outside this package is strongly discouraged.89*/90goog.testing.events.Event.prototype.returnValue_ = true;919293/** @override */94goog.testing.events.Event.prototype.stopPropagation = function() {95'use strict';96this.propagationStopped_ = true;97};9899100/** @override */101goog.testing.events.Event.prototype.preventDefault = function() {102'use strict';103this.defaultPrevented = true;104this.returnValue_ = false;105};106107/**108* Asserts an event target exists. This will fail if target is not defined.109*110* TODO(nnaze): Gradually add this to the methods in this file, and eventually111* update the method signatures to not take nullables. See112* http://b/8961907113*114* @param {EventTarget} target A target to assert.115* @return {!EventTarget} The target, guaranteed to exist.116* @private117*/118goog.testing.events.assertEventTarget_ = function(target) {119'use strict';120return goog.asserts.assert(target, 'EventTarget should be defined.');121};122123124/**125* A static helper function that sets the mouse position to the event.126* @param {Event} event A simulated native event.127* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's128* target's position (if available), otherwise (0, 0).129* @private130*/131goog.testing.events.setEventClientXY_ = function(event, opt_coords) {132'use strict';133if (!opt_coords && event.target &&134/** @type {!Node} */ (event.target).nodeType ==135goog.dom.NodeType.ELEMENT) {136try {137opt_coords = goog.style.getClientPosition(138/** @type {!Element} **/ (event.target));139} catch (ex) {140// IE sometimes throws if it can't get the position.141}142}143event.clientX = opt_coords ? opt_coords.x : 0;144event.clientY = opt_coords ? opt_coords.y : 0;145146// Pretend the browser window is at (0, 0) of the screen.147event.screenX = event.clientX;148event.screenY = event.clientY;149150// Assume that there was no page scroll.151event.pageX = event.clientX;152event.pageY = event.clientY;153};154155156/**157* Simulates a mousedown, mouseup, and then click on the given event target,158* with the left mouse button.159* @param {EventTarget} target The target for the event.160* @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;161* defaults to `goog.events.BrowserEvent.MouseButton.LEFT`.162* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's163* target's position (if available), otherwise (0, 0).164* @param {Object=} opt_eventProperties Event properties to be mixed into the165* BrowserEvent.166* @return {boolean} The returnValue of the sequence: false if preventDefault()167* was called on any of the events, true otherwise.168*/169goog.testing.events.fireClickSequence = function(170target, opt_button, opt_coords, opt_eventProperties) {171'use strict';172// Fire mousedown, mouseup, and click. Then return the bitwise AND of the 3.173return goog.testing.events.eagerAnd_(174goog.testing.events.fireMouseDownEvent(175target, opt_button, opt_coords, opt_eventProperties),176goog.testing.events.fireMouseUpEvent(177target, opt_button, opt_coords, opt_eventProperties),178goog.testing.events.fireClickEvent(179target, opt_button, opt_coords, opt_eventProperties));180};181182183/**184* Simulates the sequence of events fired by the browser when the user double-185* clicks the given target.186* @param {EventTarget} target The target for the event.187* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's188* target's position (if available), otherwise (0, 0).189* @param {Object=} opt_eventProperties Event properties to be mixed into the190* BrowserEvent.191* @return {boolean} The returnValue of the sequence: false if preventDefault()192* was called on any of the events, true otherwise.193*/194goog.testing.events.fireDoubleClickSequence = function(195target, opt_coords, opt_eventProperties) {196'use strict';197// Fire mousedown, mouseup, click, mousedown, mouseup, click, dblclick.198// Then return the bitwise AND of the 7.199const btn = goog.events.BrowserEvent.MouseButton.LEFT;200return goog.testing.events.eagerAnd_(201goog.testing.events.fireMouseDownEvent(202target, btn, opt_coords, opt_eventProperties),203goog.testing.events.fireMouseUpEvent(204target, btn, opt_coords, opt_eventProperties),205goog.testing.events.fireClickEvent(206target, btn, opt_coords, opt_eventProperties),207// IE fires a selectstart instead of the second mousedown in a208// dblclick, but we don't care about selectstart.209(goog.userAgent.IE ||210goog.testing.events.fireMouseDownEvent(211target, btn, opt_coords, opt_eventProperties)),212goog.testing.events.fireMouseUpEvent(213target, btn, opt_coords, opt_eventProperties),214// IE doesn't fire the second click in a dblclick.215(goog.userAgent.IE ||216goog.testing.events.fireClickEvent(217target, btn, opt_coords, opt_eventProperties)),218goog.testing.events.fireDoubleClickEvent(219target, opt_coords, opt_eventProperties));220};221222223/**224* A non-exhaustive mapping of keys to keyCode. These are not localized and225* are specific to QWERTY keyboards, but are used to augment our testing key226* events as much as possible in order to simulate real browser events. This227* will be used to fill out the `keyCode` field for key events when the `key`228* value is present in this map.229* @private {!Object<number>}230* @final231*/232goog.testing.events.KEY_TO_KEYCODE_MAPPING_ = {233'0': goog.events.KeyCodes.ZERO,234'1': goog.events.KeyCodes.ONE,235'2': goog.events.KeyCodes.TWO,236'3': goog.events.KeyCodes.THREE,237'4': goog.events.KeyCodes.FOUR,238'5': goog.events.KeyCodes.FIVE,239'6': goog.events.KeyCodes.SIX,240'7': goog.events.KeyCodes.SEVEN,241'8': goog.events.KeyCodes.EIGHT,242'9': goog.events.KeyCodes.NINE,243'a': goog.events.KeyCodes.A,244'b': goog.events.KeyCodes.B,245'c': goog.events.KeyCodes.C,246'd': goog.events.KeyCodes.D,247'e': goog.events.KeyCodes.E,248'f': goog.events.KeyCodes.F,249'g': goog.events.KeyCodes.G,250'h': goog.events.KeyCodes.H,251'i': goog.events.KeyCodes.I,252'j': goog.events.KeyCodes.J,253'k': goog.events.KeyCodes.K,254'l': goog.events.KeyCodes.L,255'm': goog.events.KeyCodes.M,256'n': goog.events.KeyCodes.N,257'o': goog.events.KeyCodes.O,258'p': goog.events.KeyCodes.P,259'q': goog.events.KeyCodes.Q,260'r': goog.events.KeyCodes.R,261's': goog.events.KeyCodes.S,262't': goog.events.KeyCodes.T,263'u': goog.events.KeyCodes.U,264'v': goog.events.KeyCodes.V,265'w': goog.events.KeyCodes.W,266'x': goog.events.KeyCodes.X,267'y': goog.events.KeyCodes.Y,268'z': goog.events.KeyCodes.Z269};270271272/**273* Simulates a complete keystroke (keydown, keypress, and keyup). Note that274* if preventDefault is called on the keydown, the keypress will not fire.275*276* @param {EventTarget} target The target for the event.277* @param {string|number} keyOrKeyCode The key value or keycode of the key278* pressed.279* @param {Object=} opt_eventProperties Event properties to be mixed into the280* BrowserEvent.281* @return {boolean} The returnValue of the sequence: false if preventDefault()282* was called on any of the events, true otherwise.283*/284goog.testing.events.fireKeySequence = function(285target, keyOrKeyCode, opt_eventProperties) {286'use strict';287return goog.testing.events.fireNonAsciiKeySequence(288target, keyOrKeyCode, keyOrKeyCode, opt_eventProperties);289};290291292/**293* Simulates a complete keystroke (keydown, keypress, and keyup) when typing294* a non-ASCII character. Same as fireKeySequence, the keypress will not fire295* if preventDefault is called on the keydown.296*297* @param {EventTarget} target The target for the event.298* @param {string|number} keyOrKeyCode The key value or keycode of the keydown299* and keyup events.300* @param {string|number} keyPressKeyOrKeyCode The key value or keycode of the301* keypress event.302* @param {Object=} opt_eventProperties Event properties to be mixed into the303* BrowserEvent.304* @return {boolean} The returnValue of the sequence: false if preventDefault()305* was called on any of the events, true otherwise.306*/307goog.testing.events.fireNonAsciiKeySequence = function(308target, keyOrKeyCode, keyPressKeyOrKeyCode, opt_eventProperties) {309'use strict';310const keydown =311/** @type {!KeyboardEvent} */ (312/** @type {!Event} */ (new goog.testing.events.Event(313goog.events.EventType.KEYDOWN, target)));314const keyup = //315/** @type {!KeyboardEvent} */ (316/** @type {!Event} */ (new goog.testing.events.Event(317goog.events.EventType.KEYUP, target)));318const keypress =319/** @type {!KeyboardEvent} */ (320/** @type {!Event} */ (new goog.testing.events.Event(321goog.events.EventType.KEYPRESS, target)));322323if (typeof keyOrKeyCode === 'string') {324keydown.key = keyup.key = /** @type {string} */ (keyOrKeyCode);325keypress.key = /** @type {string} */ (keyPressKeyOrKeyCode);326327// Try to fill the keyCode field for the key events if we have a known key.328// This is to try and make these mock simulated event as close to real329// browser events as possible.330const mappedKeyCode =331goog.testing.events332.KEY_TO_KEYCODE_MAPPING_[/** @type {string} */ (keyOrKeyCode)333.toLowerCase()];334if (mappedKeyCode) {335keydown.keyCode = keyup.keyCode = mappedKeyCode;336}337338const mappedKeyPressKeyCode =339goog.testing.events.KEY_TO_KEYCODE_MAPPING_[/** @type {string} */ (340keyPressKeyOrKeyCode)341.toLowerCase()];342if (mappedKeyPressKeyCode) {343keypress.keyCode = mappedKeyPressKeyCode;344}345} else {346keydown.keyCode = keyup.keyCode = /** @type {number} */ (keyOrKeyCode);347keypress.keyCode = /** @type {number} */ (keyPressKeyOrKeyCode);348}349350if (opt_eventProperties) {351goog.object.extend(keydown, opt_eventProperties);352goog.object.extend(keyup, opt_eventProperties);353goog.object.extend(keypress, opt_eventProperties);354}355356// Fire keydown, keypress, and keyup. Note that if the keydown is357// prevent-defaulted, then the keypress will not fire.358let result = goog.testing.events.fireBrowserEvent(keydown);359if (typeof keyOrKeyCode === 'string') {360if (/** @type {string} */ (keyPressKeyOrKeyCode) != '' && result) {361result = goog.testing.events.eagerAnd_(362result, goog.testing.events.fireBrowserEvent(keypress));363}364} else {365if (goog.events.KeyCodes.firesKeyPressEvent(366/** @type {number} */ (keyOrKeyCode), undefined, keydown.shiftKey,367keydown.ctrlKey, keydown.altKey, keydown.metaKey) &&368result) {369result = goog.testing.events.eagerAnd_(370result, goog.testing.events.fireBrowserEvent(keypress));371}372}373return goog.testing.events.eagerAnd_(374result, goog.testing.events.fireBrowserEvent(keyup));375};376377378/**379* Simulates a mouseenter event on the given target.380* @param {!EventTarget} target The target for the event.381* @param {?EventTarget} relatedTarget The related target for the event (e.g.,382* the node that the mouse is being moved out of).383* @param {!goog.math.Coordinate=} opt_coords Mouse position. Defaults to384* event's target's position (if available), otherwise (0, 0).385* @return {boolean} The returnValue of the event: false if preventDefault() was386* called on it, true otherwise.387*/388goog.testing.events.fireMouseEnterEvent = function(389target, relatedTarget, opt_coords) {390'use strict';391const mouseenter =392new goog.testing.events.Event(goog.events.EventType.MOUSEENTER, target);393mouseenter.relatedTarget = relatedTarget;394goog.testing.events.setEventClientXY_(mouseenter, opt_coords);395return goog.testing.events.fireBrowserEvent(mouseenter);396};397398399/**400* Simulates a mouseleave event on the given target.401* @param {!EventTarget} target The target for the event.402* @param {?EventTarget} relatedTarget The related target for the event (e.g.,403* the node that the mouse is being moved into).404* @param {!goog.math.Coordinate=} opt_coords Mouse position. Defaults to405* event's target's position (if available), otherwise (0, 0).406* @return {boolean} The returnValue of the event: false if preventDefault() was407* called on it, true otherwise.408*/409goog.testing.events.fireMouseLeaveEvent = function(410target, relatedTarget, opt_coords) {411'use strict';412const mouseleave =413new goog.testing.events.Event(goog.events.EventType.MOUSELEAVE, target);414mouseleave.relatedTarget = relatedTarget;415goog.testing.events.setEventClientXY_(mouseleave, opt_coords);416return goog.testing.events.fireBrowserEvent(mouseleave);417};418419420/**421* Simulates a mouseover event on the given target.422* @param {EventTarget} target The target for the event.423* @param {EventTarget} relatedTarget The related target for the event (e.g.,424* the node that the mouse is being moved out of).425* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's426* target's position (if available), otherwise (0, 0).427* @return {boolean} The returnValue of the event: false if preventDefault() was428* called on it, true otherwise.429*/430goog.testing.events.fireMouseOverEvent = function(431target, relatedTarget, opt_coords) {432'use strict';433const mouseover =434new goog.testing.events.Event(goog.events.EventType.MOUSEOVER, target);435mouseover.relatedTarget = relatedTarget;436goog.testing.events.setEventClientXY_(mouseover, opt_coords);437return goog.testing.events.fireBrowserEvent(mouseover);438};439440441/**442* Simulates a mousemove event on the given target.443* @param {EventTarget} target The target for the event.444* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's445* target's position (if available), otherwise (0, 0).446* @return {boolean} The returnValue of the event: false if preventDefault() was447* called on it, true otherwise.448*/449goog.testing.events.fireMouseMoveEvent = function(target, opt_coords) {450'use strict';451const mousemove =452new goog.testing.events.Event(goog.events.EventType.MOUSEMOVE, target);453454goog.testing.events.setEventClientXY_(mousemove, opt_coords);455return goog.testing.events.fireBrowserEvent(mousemove);456};457458459/**460* Simulates a mouseout event on the given target.461* @param {EventTarget} target The target for the event.462* @param {EventTarget} relatedTarget The related target for the event (e.g.,463* the node that the mouse is being moved into).464* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's465* target's position (if available), otherwise (0, 0).466* @return {boolean} The returnValue of the event: false if preventDefault() was467* called on it, true otherwise.468*/469goog.testing.events.fireMouseOutEvent = function(470target, relatedTarget, opt_coords) {471'use strict';472const mouseout =473new goog.testing.events.Event(goog.events.EventType.MOUSEOUT, target);474mouseout.relatedTarget = relatedTarget;475goog.testing.events.setEventClientXY_(mouseout, opt_coords);476return goog.testing.events.fireBrowserEvent(mouseout);477};478479480/**481* Simulates a mousedown event on the given target.482* @param {EventTarget} target The target for the event.483* @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;484* defaults to `goog.events.BrowserEvent.MouseButton.LEFT`.485* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's486* target's position (if available), otherwise (0, 0).487* @param {Object=} opt_eventProperties Event properties to be mixed into the488* BrowserEvent.489* @return {boolean} The returnValue of the event: false if preventDefault() was490* called on it, true otherwise.491*/492goog.testing.events.fireMouseDownEvent = function(493target, opt_button, opt_coords, opt_eventProperties) {494'use strict';495let button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;496return goog.testing.events.fireMouseButtonEvent_(497goog.events.EventType.MOUSEDOWN, target, button, opt_coords,498opt_eventProperties);499};500501502/**503* Simulates a mouseup event on the given target.504* @param {EventTarget} target The target for the event.505* @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;506* defaults to `goog.events.BrowserEvent.MouseButton.LEFT`.507* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's508* target's position (if available), otherwise (0, 0).509* @param {Object=} opt_eventProperties Event properties to be mixed into the510* BrowserEvent.511* @return {boolean} The returnValue of the event: false if preventDefault() was512* called on it, true otherwise.513*/514goog.testing.events.fireMouseUpEvent = function(515target, opt_button, opt_coords, opt_eventProperties) {516'use strict';517let button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;518return goog.testing.events.fireMouseButtonEvent_(519goog.events.EventType.MOUSEUP, target, button, opt_coords,520opt_eventProperties);521};522523524/**525* Simulates a click event on the given target. IE only supports click with526* the left mouse button.527* @param {EventTarget} target The target for the event.528* @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;529* defaults to `goog.events.BrowserEvent.MouseButton.LEFT`.530* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's531* target's position (if available), otherwise (0, 0).532* @param {Object=} opt_eventProperties Event properties to be mixed into the533* BrowserEvent.534* @return {boolean} The returnValue of the event: false if preventDefault() was535* called on it, true otherwise.536*/537goog.testing.events.fireClickEvent = function(538target, opt_button, opt_coords, opt_eventProperties) {539'use strict';540return goog.testing.events.fireMouseButtonEvent_(541goog.events.EventType.CLICK, target, opt_button, opt_coords,542opt_eventProperties);543};544545546/**547* Simulates a double-click event on the given target. Always double-clicks548* with the left mouse button since no browser supports double-clicking with549* any other buttons.550* @param {EventTarget} target The target for the event.551* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's552* target's position (if available), otherwise (0, 0).553* @param {Object=} opt_eventProperties Event properties to be mixed into the554* BrowserEvent.555* @return {boolean} The returnValue of the event: false if preventDefault() was556* called on it, true otherwise.557*/558goog.testing.events.fireDoubleClickEvent = function(559target, opt_coords, opt_eventProperties) {560'use strict';561return goog.testing.events.fireMouseButtonEvent_(562goog.events.EventType.DBLCLICK, target,563goog.events.BrowserEvent.MouseButton.LEFT, opt_coords,564opt_eventProperties);565};566567568/**569* Helper function to fire a mouse event.570* with the left mouse button since no browser supports double-clicking with571* any other buttons.572* @param {string} type The event type.573* @param {EventTarget} target The target for the event.574* @param {number=} opt_button Mouse button; defaults to575* `goog.events.BrowserEvent.MouseButton.LEFT`.576* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's577* target's position (if available), otherwise (0, 0).578* @param {Object=} opt_eventProperties Event properties to be mixed into the579* BrowserEvent.580* @return {boolean} The returnValue of the event: false if preventDefault() was581* called on it, true otherwise.582* @private583*/584goog.testing.events.fireMouseButtonEvent_ = function(585type, target, opt_button, opt_coords, opt_eventProperties) {586'use strict';587const e = new goog.testing.events.Event(type, target);588e.button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;589goog.testing.events.setEventClientXY_(e, opt_coords);590if (opt_eventProperties) {591goog.object.extend(e, opt_eventProperties);592}593return goog.testing.events.fireBrowserEvent(e);594};595596597/**598* Simulates a contextmenu event on the given target.599* @param {EventTarget} target The target for the event.600* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's601* target's position (if available), otherwise (0, 0).602* @return {boolean} The returnValue of the event: false if preventDefault() was603* called on it, true otherwise.604*/605goog.testing.events.fireContextMenuEvent = function(target, opt_coords) {606'use strict';607const button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?608goog.events.BrowserEvent.MouseButton.LEFT :609goog.events.BrowserEvent.MouseButton.RIGHT;610const contextmenu =611new goog.testing.events.Event(goog.events.EventType.CONTEXTMENU, target);612contextmenu.button = button;613contextmenu.ctrlKey = goog.userAgent.MAC;614goog.testing.events.setEventClientXY_(contextmenu, opt_coords);615return goog.testing.events.fireBrowserEvent(contextmenu);616};617618619/**620* Simulates a mousedown, contextmenu, and the mouseup on the given event621* target, with the right mouse button.622* @param {EventTarget} target The target for the event.623* @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's624* target's position (if available), otherwise (0, 0).625* @return {boolean} The returnValue of the sequence: false if preventDefault()626* was called on any of the events, true otherwise.627*/628goog.testing.events.fireContextMenuSequence = function(target, opt_coords) {629'use strict';630const props = goog.userAgent.MAC ? {ctrlKey: true} : {};631const button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?632goog.events.BrowserEvent.MouseButton.LEFT :633goog.events.BrowserEvent.MouseButton.RIGHT;634635let result =636goog.testing.events.fireMouseDownEvent(target, button, opt_coords, props);637if (goog.userAgent.WINDOWS) {638// All browsers are consistent on Windows.639result = goog.testing.events.eagerAnd_(640result,641goog.testing.events.fireMouseUpEvent(target, button, opt_coords),642goog.testing.events.fireContextMenuEvent(target, opt_coords));643} else {644result = goog.testing.events.eagerAnd_(645result, goog.testing.events.fireContextMenuEvent(target, opt_coords));646647// GECKO on Mac and Linux always fires the mouseup after the contextmenu.648649// WEBKIT is really weird.650//651// On Linux, it sometimes fires mouseup, but most of the time doesn't.652// It's really hard to reproduce consistently. I think there's some653// internal race condition. If contextmenu is preventDefaulted, then654// mouseup always fires.655//656// On Mac, it always fires mouseup and then fires a click.657result = goog.testing.events.eagerAnd_(658result,659goog.testing.events.fireMouseUpEvent(660target, button, opt_coords, props));661662if (goog.userAgent.WEBKIT && goog.userAgent.MAC) {663result = goog.testing.events.eagerAnd_(664result,665goog.testing.events.fireClickEvent(666target, button, opt_coords, props));667}668}669return result;670};671672673/**674* Simulates a popstate event on the given target.675* @param {EventTarget} target The target for the event.676* @param {Object} state History state object.677* @return {boolean} The returnValue of the event: false if preventDefault() was678* called on it, true otherwise.679*/680goog.testing.events.firePopStateEvent = function(target, state) {681'use strict';682const e = /** @type {!PopStateEvent} */ (/** @type {!Event} */ (683new goog.testing.events.Event(goog.events.EventType.POPSTATE, target)));684e.state = state;685return goog.testing.events.fireBrowserEvent(e);686};687688689/**690* Simulate a blur event on the given target.691* @param {EventTarget} target The target for the event.692* @return {boolean} The value returned by firing the blur browser event,693* which returns false iff 'preventDefault' was invoked.694*/695goog.testing.events.fireBlurEvent = function(target) {696'use strict';697const e = new goog.testing.events.Event(goog.events.EventType.BLUR, target);698return goog.testing.events.fireBrowserEvent(e);699};700701702/**703* Simulate a focus event on the given target.704* @param {EventTarget} target The target for the event.705* @return {boolean} The value returned by firing the focus browser event,706* which returns false iff 'preventDefault' was invoked.707*/708goog.testing.events.fireFocusEvent = function(target) {709'use strict';710const e = new goog.testing.events.Event(goog.events.EventType.FOCUS, target);711return goog.testing.events.fireBrowserEvent(e);712};713714715/**716* Simulate a focus-in event on the given target.717* @param {!EventTarget} target The target for the event.718* @return {boolean} The value returned by firing the focus-in browser event,719* which returns false iff 'preventDefault' was invoked.720*/721goog.testing.events.fireFocusInEvent = function(target) {722'use strict';723const e =724new goog.testing.events.Event(goog.events.EventType.FOCUSIN, target);725return goog.testing.events.fireBrowserEvent(e);726};727728729/**730* Simulates an event's capturing and bubbling phases.731* @param {Event} event A simulated native event. It will be wrapped in a732* normalized BrowserEvent and dispatched to Closure listeners on all733* ancestors of its target (inclusive).734* @return {boolean} The returnValue of the event: false if preventDefault() was735* called on it, true otherwise.736*/737goog.testing.events.fireBrowserEvent = function(event) {738'use strict';739event = /** @type {!goog.testing.events.Event} */ (event);740741event.returnValue_ = true;742743// generate a list of ancestors744const ancestors = [];745for (let current = event.target; current; current = current.parentNode) {746ancestors.push(current);747}748749// dispatch capturing listeners750for (let j = ancestors.length - 1; j >= 0 && !event.propagationStopped_;751j--) {752goog.events.fireListeners(753ancestors[j], event.type, true,754new goog.events.BrowserEvent(event, ancestors[j]));755}756757// dispatch bubbling listeners758for (let j = 0; j < ancestors.length && !event.propagationStopped_; j++) {759goog.events.fireListeners(760ancestors[j], event.type, false,761new goog.events.BrowserEvent(event, ancestors[j]));762}763764return event.returnValue_;765};766767768/**769* Simulates a touchstart event on the given target.770* @param {EventTarget} target The target for the event.771* @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's772* target's position (if available), otherwise (0, 0).773* @param {Object=} opt_eventProperties Event properties to be mixed into the774* BrowserEvent.775* @return {boolean} The returnValue of the event: false if preventDefault() was776* called on it, true otherwise.777*/778goog.testing.events.fireTouchStartEvent = function(779target, opt_coords, opt_eventProperties) {780'use strict';781// TODO: Support multi-touch events with array of coordinates.782const touchstart =783new goog.testing.events.Event(goog.events.EventType.TOUCHSTART, target);784goog.testing.events.setEventClientXY_(touchstart, opt_coords);785if (opt_eventProperties) {786goog.object.extend(touchstart, opt_eventProperties);787}788return goog.testing.events.fireBrowserEvent(touchstart);789};790791792/**793* Simulates a touchmove event on the given target.794* @param {EventTarget} target The target for the event.795* @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's796* target's position (if available), otherwise (0, 0).797* @param {Object=} opt_eventProperties Event properties to be mixed into the798* BrowserEvent.799* @return {boolean} The returnValue of the event: false if preventDefault() was800* called on it, true otherwise.801*/802goog.testing.events.fireTouchMoveEvent = function(803target, opt_coords, opt_eventProperties) {804'use strict';805// TODO: Support multi-touch events with array of coordinates.806const touchmove =807new goog.testing.events.Event(goog.events.EventType.TOUCHMOVE, target);808goog.testing.events.setEventClientXY_(touchmove, opt_coords);809if (opt_eventProperties) {810goog.object.extend(touchmove, opt_eventProperties);811}812return goog.testing.events.fireBrowserEvent(touchmove);813};814815816/**817* Simulates a touchend event on the given target.818* @param {EventTarget} target The target for the event.819* @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's820* target's position (if available), otherwise (0, 0).821* @param {Object=} opt_eventProperties Event properties to be mixed into the822* BrowserEvent.823* @return {boolean} The returnValue of the event: false if preventDefault() was824* called on it, true otherwise.825*/826goog.testing.events.fireTouchEndEvent = function(827target, opt_coords, opt_eventProperties) {828'use strict';829// TODO: Support multi-touch events with array of coordinates.830const touchend =831new goog.testing.events.Event(goog.events.EventType.TOUCHEND, target);832goog.testing.events.setEventClientXY_(touchend, opt_coords);833if (opt_eventProperties) {834goog.object.extend(touchend, opt_eventProperties);835}836return goog.testing.events.fireBrowserEvent(touchend);837};838839840/**841* Simulates a simple touch sequence on the given target.842* @param {EventTarget} target The target for the event.843* @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event844* target's position (if available), otherwise (0, 0).845* @param {Object=} opt_eventProperties Event properties to be mixed into the846* BrowserEvent.847* @return {boolean} The returnValue of the sequence: false if preventDefault()848* was called on any of the events, true otherwise.849*/850goog.testing.events.fireTouchSequence = function(851target, opt_coords, opt_eventProperties) {852'use strict';853// TODO: Support multi-touch events with array of coordinates.854// Fire touchstart, touchmove, touchend then return the AND of the 2.855return goog.testing.events.eagerAnd_(856goog.testing.events.fireTouchStartEvent(857target, opt_coords, opt_eventProperties),858goog.testing.events.fireTouchEndEvent(859target, opt_coords, opt_eventProperties));860};861862863/**864* Mixins a listenable into the given object. This turns the object865* into a goog.events.Listenable. This is useful, for example, when866* you need to mock a implementation of listenable and still want it867* to work with goog.events.868* @param {!Object} obj The object to mixin into.869*/870goog.testing.events.mixinListenable = function(obj) {871'use strict';872const listenable = new goog.events.EventTarget();873874listenable.setTargetForTesting(obj);875876const listenablePrototype = goog.events.EventTarget.prototype;877const disposablePrototype = goog.Disposable.prototype;878for (let key in listenablePrototype) {879if (listenablePrototype.hasOwnProperty(key) ||880disposablePrototype.hasOwnProperty(key)) {881const member = listenablePrototype[key];882if (typeof member === 'function') {883obj[key] = goog.bind(member, listenable);884} else {885obj[key] = member;886}887}888}889};890891/**892* Returns the boolean AND of all parameters.893*894* Unlike directly using `&&`, using this function cannot employ895* short-circuiting; all side effects of resolving parameters will occur before896* entering the function body.897*898* @param {boolean} first899* @param {...boolean} rest900* @return {boolean}901* @private902*/903goog.testing.events.eagerAnd_ = function(first, rest) {904'use strict';905for (let i = 1; i < arguments.length; i++) {906first = first && arguments[i];907}908return first;909};910911912