browserevent.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. // Copyright 2005 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview A patched, standardized event object for browser events.
  16. *
  17. * <pre>
  18. * The patched event object contains the following members:
  19. * - type {string} Event type, e.g. 'click'
  20. * - target {Object} The element that actually triggered the event
  21. * - currentTarget {Object} The element the listener is attached to
  22. * - relatedTarget {Object} For mouseover and mouseout, the previous object
  23. * - offsetX {number} X-coordinate relative to target
  24. * - offsetY {number} Y-coordinate relative to target
  25. * - clientX {number} X-coordinate relative to viewport
  26. * - clientY {number} Y-coordinate relative to viewport
  27. * - screenX {number} X-coordinate relative to the edge of the screen
  28. * - screenY {number} Y-coordinate relative to the edge of the screen
  29. * - button {number} Mouse button. Use isButton() to test.
  30. * - keyCode {number} Key-code
  31. * - ctrlKey {boolean} Was ctrl key depressed
  32. * - altKey {boolean} Was alt key depressed
  33. * - shiftKey {boolean} Was shift key depressed
  34. * - metaKey {boolean} Was meta key depressed
  35. * - defaultPrevented {boolean} Whether the default action has been prevented
  36. * - state {Object} History state object
  37. *
  38. * NOTE: The keyCode member contains the raw browser keyCode. For normalized
  39. * key and character code use {@link goog.events.KeyHandler}.
  40. * </pre>
  41. *
  42. * @author arv@google.com (Erik Arvidsson)
  43. */
  44. goog.provide('goog.events.BrowserEvent');
  45. goog.provide('goog.events.BrowserEvent.MouseButton');
  46. goog.require('goog.events.BrowserFeature');
  47. goog.require('goog.events.Event');
  48. goog.require('goog.events.EventType');
  49. goog.require('goog.reflect');
  50. goog.require('goog.userAgent');
  51. /**
  52. * Accepts a browser event object and creates a patched, cross browser event
  53. * object.
  54. * The content of this object will not be initialized if no event object is
  55. * provided. If this is the case, init() needs to be invoked separately.
  56. * @param {Event=} opt_e Browser event object.
  57. * @param {EventTarget=} opt_currentTarget Current target for event.
  58. * @constructor
  59. * @extends {goog.events.Event}
  60. */
  61. goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
  62. goog.events.BrowserEvent.base(this, 'constructor', opt_e ? opt_e.type : '');
  63. /**
  64. * Target that fired the event.
  65. * @override
  66. * @type {Node}
  67. */
  68. this.target = null;
  69. /**
  70. * Node that had the listener attached.
  71. * @override
  72. * @type {Node|undefined}
  73. */
  74. this.currentTarget = null;
  75. /**
  76. * For mouseover and mouseout events, the related object for the event.
  77. * @type {Node}
  78. */
  79. this.relatedTarget = null;
  80. /**
  81. * X-coordinate relative to target.
  82. * @type {number}
  83. */
  84. this.offsetX = 0;
  85. /**
  86. * Y-coordinate relative to target.
  87. * @type {number}
  88. */
  89. this.offsetY = 0;
  90. /**
  91. * X-coordinate relative to the window.
  92. * @type {number}
  93. */
  94. this.clientX = 0;
  95. /**
  96. * Y-coordinate relative to the window.
  97. * @type {number}
  98. */
  99. this.clientY = 0;
  100. /**
  101. * X-coordinate relative to the monitor.
  102. * @type {number}
  103. */
  104. this.screenX = 0;
  105. /**
  106. * Y-coordinate relative to the monitor.
  107. * @type {number}
  108. */
  109. this.screenY = 0;
  110. /**
  111. * Which mouse button was pressed.
  112. * @type {number}
  113. */
  114. this.button = 0;
  115. /**
  116. * Key of key press.
  117. * @type {string}
  118. */
  119. this.key = '';
  120. /**
  121. * Keycode of key press.
  122. * @type {number}
  123. */
  124. this.keyCode = 0;
  125. /**
  126. * Keycode of key press.
  127. * @type {number}
  128. */
  129. this.charCode = 0;
  130. /**
  131. * Whether control was pressed at time of event.
  132. * @type {boolean}
  133. */
  134. this.ctrlKey = false;
  135. /**
  136. * Whether alt was pressed at time of event.
  137. * @type {boolean}
  138. */
  139. this.altKey = false;
  140. /**
  141. * Whether shift was pressed at time of event.
  142. * @type {boolean}
  143. */
  144. this.shiftKey = false;
  145. /**
  146. * Whether the meta key was pressed at time of event.
  147. * @type {boolean}
  148. */
  149. this.metaKey = false;
  150. /**
  151. * History state object, only set for PopState events where it's a copy of the
  152. * state object provided to pushState or replaceState.
  153. * @type {Object}
  154. */
  155. this.state = null;
  156. /**
  157. * Whether the default platform modifier key was pressed at time of event.
  158. * (This is control for all platforms except Mac, where it's Meta.)
  159. * @type {boolean}
  160. */
  161. this.platformModifierKey = false;
  162. /**
  163. * The browser event object.
  164. * @private {Event}
  165. */
  166. this.event_ = null;
  167. if (opt_e) {
  168. this.init(opt_e, opt_currentTarget);
  169. }
  170. };
  171. goog.inherits(goog.events.BrowserEvent, goog.events.Event);
  172. /**
  173. * Normalized button constants for the mouse.
  174. * @enum {number}
  175. */
  176. goog.events.BrowserEvent.MouseButton = {
  177. LEFT: 0,
  178. MIDDLE: 1,
  179. RIGHT: 2
  180. };
  181. /**
  182. * Static data for mapping mouse buttons.
  183. * @type {!Array<number>}
  184. */
  185. goog.events.BrowserEvent.IEButtonMap = [
  186. 1, // LEFT
  187. 4, // MIDDLE
  188. 2 // RIGHT
  189. ];
  190. /**
  191. * Accepts a browser event object and creates a patched, cross browser event
  192. * object.
  193. * @param {Event} e Browser event object.
  194. * @param {EventTarget=} opt_currentTarget Current target for event.
  195. */
  196. goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
  197. var type = this.type = e.type;
  198. /**
  199. * On touch devices use the first "changed touch" as the relevant touch.
  200. * @type {Touch}
  201. */
  202. var relevantTouch = e.changedTouches ? e.changedTouches[0] : null;
  203. // TODO(nicksantos): Change this.target to type EventTarget.
  204. this.target = /** @type {Node} */ (e.target) || e.srcElement;
  205. // TODO(nicksantos): Change this.currentTarget to type EventTarget.
  206. this.currentTarget = /** @type {Node} */ (opt_currentTarget);
  207. var relatedTarget = /** @type {Node} */ (e.relatedTarget);
  208. if (relatedTarget) {
  209. // There's a bug in FireFox where sometimes, relatedTarget will be a
  210. // chrome element, and accessing any property of it will get a permission
  211. // denied exception. See:
  212. // https://bugzilla.mozilla.org/show_bug.cgi?id=497780
  213. if (goog.userAgent.GECKO) {
  214. if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) {
  215. relatedTarget = null;
  216. }
  217. }
  218. // TODO(arv): Use goog.events.EventType when it has been refactored into its
  219. // own file.
  220. } else if (type == goog.events.EventType.MOUSEOVER) {
  221. relatedTarget = e.fromElement;
  222. } else if (type == goog.events.EventType.MOUSEOUT) {
  223. relatedTarget = e.toElement;
  224. }
  225. this.relatedTarget = relatedTarget;
  226. if (!goog.isNull(relevantTouch)) {
  227. this.clientX = relevantTouch.clientX !== undefined ? relevantTouch.clientX :
  228. relevantTouch.pageX;
  229. this.clientY = relevantTouch.clientY !== undefined ? relevantTouch.clientY :
  230. relevantTouch.pageY;
  231. this.screenX = relevantTouch.screenX || 0;
  232. this.screenY = relevantTouch.screenY || 0;
  233. } else {
  234. // Webkit emits a lame warning whenever layerX/layerY is accessed.
  235. // http://code.google.com/p/chromium/issues/detail?id=101733
  236. this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ?
  237. e.offsetX :
  238. e.layerX;
  239. this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ?
  240. e.offsetY :
  241. e.layerY;
  242. this.clientX = e.clientX !== undefined ? e.clientX : e.pageX;
  243. this.clientY = e.clientY !== undefined ? e.clientY : e.pageY;
  244. this.screenX = e.screenX || 0;
  245. this.screenY = e.screenY || 0;
  246. }
  247. this.button = e.button;
  248. this.keyCode = e.keyCode || 0;
  249. this.key = e.key || '';
  250. this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0);
  251. this.ctrlKey = e.ctrlKey;
  252. this.altKey = e.altKey;
  253. this.shiftKey = e.shiftKey;
  254. this.metaKey = e.metaKey;
  255. this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
  256. this.state = e.state;
  257. this.event_ = e;
  258. if (e.defaultPrevented) {
  259. this.preventDefault();
  260. }
  261. };
  262. /**
  263. * Tests to see which button was pressed during the event. This is really only
  264. * useful in IE and Gecko browsers. And in IE, it's only useful for
  265. * mousedown/mouseup events, because click only fires for the left mouse button.
  266. *
  267. * Safari 2 only reports the left button being clicked, and uses the value '1'
  268. * instead of 0. Opera only reports a mousedown event for the middle button, and
  269. * no mouse events for the right button. Opera has default behavior for left and
  270. * middle click that can only be overridden via a configuration setting.
  271. *
  272. * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html.
  273. *
  274. * @param {goog.events.BrowserEvent.MouseButton} button The button
  275. * to test for.
  276. * @return {boolean} True if button was pressed.
  277. */
  278. goog.events.BrowserEvent.prototype.isButton = function(button) {
  279. if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) {
  280. if (this.type == 'click') {
  281. return button == goog.events.BrowserEvent.MouseButton.LEFT;
  282. } else {
  283. return !!(
  284. this.event_.button & goog.events.BrowserEvent.IEButtonMap[button]);
  285. }
  286. } else {
  287. return this.event_.button == button;
  288. }
  289. };
  290. /**
  291. * Whether this has an "action"-producing mouse button.
  292. *
  293. * By definition, this includes left-click on windows/linux, and left-click
  294. * without the ctrl key on Macs.
  295. *
  296. * @return {boolean} The result.
  297. */
  298. goog.events.BrowserEvent.prototype.isMouseActionButton = function() {
  299. // Webkit does not ctrl+click to be a right-click, so we
  300. // normalize it to behave like Gecko and Opera.
  301. return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) &&
  302. !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey);
  303. };
  304. /**
  305. * @override
  306. */
  307. goog.events.BrowserEvent.prototype.stopPropagation = function() {
  308. goog.events.BrowserEvent.superClass_.stopPropagation.call(this);
  309. if (this.event_.stopPropagation) {
  310. this.event_.stopPropagation();
  311. } else {
  312. this.event_.cancelBubble = true;
  313. }
  314. };
  315. /**
  316. * @override
  317. */
  318. goog.events.BrowserEvent.prototype.preventDefault = function() {
  319. goog.events.BrowserEvent.superClass_.preventDefault.call(this);
  320. var be = this.event_;
  321. if (!be.preventDefault) {
  322. be.returnValue = false;
  323. if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) {
  324. try {
  325. // Most keys can be prevented using returnValue. Some special keys
  326. // require setting the keyCode to -1 as well:
  327. //
  328. // In IE7:
  329. // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6)
  330. //
  331. // In IE8:
  332. // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event)
  333. //
  334. // We therefore do this for all function keys as well as when Ctrl key
  335. // is pressed.
  336. var VK_F1 = 112;
  337. var VK_F12 = 123;
  338. if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) {
  339. be.keyCode = -1;
  340. }
  341. } catch (ex) {
  342. // IE throws an 'access denied' exception when trying to change
  343. // keyCode in some situations (e.g. srcElement is input[type=file],
  344. // or srcElement is an anchor tag rewritten by parent's innerHTML).
  345. // Do nothing in this case.
  346. }
  347. }
  348. } else {
  349. be.preventDefault();
  350. }
  351. };
  352. /**
  353. * @return {Event} The underlying browser event object.
  354. */
  355. goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
  356. return this.event_;
  357. };