property-descriptor.ts 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /**
  2. * @license
  3. * Copyright Google Inc. All Rights Reserved.
  4. *
  5. * Use of this source code is governed by an MIT-style license that can be
  6. * found in the LICENSE file at https://angular.io/license
  7. */
  8. /**
  9. * @fileoverview
  10. * @suppress {globalThis}
  11. */
  12. import {isBrowser, isMix, isNode, ObjectDefineProperty, ObjectGetOwnPropertyDescriptor, ObjectGetPrototypeOf, patchClass, patchOnProperties, wrapWithCurrentZone, zoneSymbol} from '../common/utils';
  13. import * as webSocketPatch from './websocket';
  14. const globalEventHandlersEventNames = [
  15. 'abort',
  16. 'animationcancel',
  17. 'animationend',
  18. 'animationiteration',
  19. 'auxclick',
  20. 'beforeinput',
  21. 'blur',
  22. 'cancel',
  23. 'canplay',
  24. 'canplaythrough',
  25. 'change',
  26. 'compositionstart',
  27. 'compositionupdate',
  28. 'compositionend',
  29. 'cuechange',
  30. 'click',
  31. 'close',
  32. 'contextmenu',
  33. 'curechange',
  34. 'dblclick',
  35. 'drag',
  36. 'dragend',
  37. 'dragenter',
  38. 'dragexit',
  39. 'dragleave',
  40. 'dragover',
  41. 'drop',
  42. 'durationchange',
  43. 'emptied',
  44. 'ended',
  45. 'error',
  46. 'focus',
  47. 'focusin',
  48. 'focusout',
  49. 'gotpointercapture',
  50. 'input',
  51. 'invalid',
  52. 'keydown',
  53. 'keypress',
  54. 'keyup',
  55. 'load',
  56. 'loadstart',
  57. 'loadeddata',
  58. 'loadedmetadata',
  59. 'lostpointercapture',
  60. 'mousedown',
  61. 'mouseenter',
  62. 'mouseleave',
  63. 'mousemove',
  64. 'mouseout',
  65. 'mouseover',
  66. 'mouseup',
  67. 'mousewheel',
  68. 'orientationchange',
  69. 'pause',
  70. 'play',
  71. 'playing',
  72. 'pointercancel',
  73. 'pointerdown',
  74. 'pointerenter',
  75. 'pointerleave',
  76. 'pointerlockchange',
  77. 'mozpointerlockchange',
  78. 'webkitpointerlockerchange',
  79. 'pointerlockerror',
  80. 'mozpointerlockerror',
  81. 'webkitpointerlockerror',
  82. 'pointermove',
  83. 'pointout',
  84. 'pointerover',
  85. 'pointerup',
  86. 'progress',
  87. 'ratechange',
  88. 'reset',
  89. 'resize',
  90. 'scroll',
  91. 'seeked',
  92. 'seeking',
  93. 'select',
  94. 'selectionchange',
  95. 'selectstart',
  96. 'show',
  97. 'sort',
  98. 'stalled',
  99. 'submit',
  100. 'suspend',
  101. 'timeupdate',
  102. 'volumechange',
  103. 'touchcancel',
  104. 'touchmove',
  105. 'touchstart',
  106. 'touchend',
  107. 'transitioncancel',
  108. 'transitionend',
  109. 'waiting',
  110. 'wheel'
  111. ];
  112. const documentEventNames = [
  113. 'afterscriptexecute', 'beforescriptexecute', 'DOMContentLoaded', 'fullscreenchange',
  114. 'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange', 'fullscreenerror',
  115. 'mozfullscreenerror', 'webkitfullscreenerror', 'msfullscreenerror', 'readystatechange',
  116. 'visibilitychange'
  117. ];
  118. const windowEventNames = [
  119. 'absolutedeviceorientation',
  120. 'afterinput',
  121. 'afterprint',
  122. 'appinstalled',
  123. 'beforeinstallprompt',
  124. 'beforeprint',
  125. 'beforeunload',
  126. 'devicelight',
  127. 'devicemotion',
  128. 'deviceorientation',
  129. 'deviceorientationabsolute',
  130. 'deviceproximity',
  131. 'hashchange',
  132. 'languagechange',
  133. 'message',
  134. 'mozbeforepaint',
  135. 'offline',
  136. 'online',
  137. 'paint',
  138. 'pageshow',
  139. 'pagehide',
  140. 'popstate',
  141. 'rejectionhandled',
  142. 'storage',
  143. 'unhandledrejection',
  144. 'unload',
  145. 'userproximity',
  146. 'vrdisplyconnected',
  147. 'vrdisplaydisconnected',
  148. 'vrdisplaypresentchange'
  149. ];
  150. const htmlElementEventNames = [
  151. 'beforecopy', 'beforecut', 'beforepaste', 'copy', 'cut', 'paste', 'dragstart', 'loadend',
  152. 'animationstart', 'search', 'transitionrun', 'transitionstart', 'webkitanimationend',
  153. 'webkitanimationiteration', 'webkitanimationstart', 'webkittransitionend'
  154. ];
  155. const mediaElementEventNames =
  156. ['encrypted', 'waitingforkey', 'msneedkey', 'mozinterruptbegin', 'mozinterruptend'];
  157. const ieElementEventNames = [
  158. 'activate',
  159. 'afterupdate',
  160. 'ariarequest',
  161. 'beforeactivate',
  162. 'beforedeactivate',
  163. 'beforeeditfocus',
  164. 'beforeupdate',
  165. 'cellchange',
  166. 'controlselect',
  167. 'dataavailable',
  168. 'datasetchanged',
  169. 'datasetcomplete',
  170. 'errorupdate',
  171. 'filterchange',
  172. 'layoutcomplete',
  173. 'losecapture',
  174. 'move',
  175. 'moveend',
  176. 'movestart',
  177. 'propertychange',
  178. 'resizeend',
  179. 'resizestart',
  180. 'rowenter',
  181. 'rowexit',
  182. 'rowsdelete',
  183. 'rowsinserted',
  184. 'command',
  185. 'compassneedscalibration',
  186. 'deactivate',
  187. 'help',
  188. 'mscontentzoom',
  189. 'msmanipulationstatechanged',
  190. 'msgesturechange',
  191. 'msgesturedoubletap',
  192. 'msgestureend',
  193. 'msgesturehold',
  194. 'msgesturestart',
  195. 'msgesturetap',
  196. 'msgotpointercapture',
  197. 'msinertiastart',
  198. 'mslostpointercapture',
  199. 'mspointercancel',
  200. 'mspointerdown',
  201. 'mspointerenter',
  202. 'mspointerhover',
  203. 'mspointerleave',
  204. 'mspointermove',
  205. 'mspointerout',
  206. 'mspointerover',
  207. 'mspointerup',
  208. 'pointerout',
  209. 'mssitemodejumplistitemremoved',
  210. 'msthumbnailclick',
  211. 'stop',
  212. 'storagecommit'
  213. ];
  214. const webglEventNames = ['webglcontextrestored', 'webglcontextlost', 'webglcontextcreationerror'];
  215. const formEventNames = ['autocomplete', 'autocompleteerror'];
  216. const detailEventNames = ['toggle'];
  217. const frameEventNames = ['load'];
  218. const frameSetEventNames = ['blur', 'error', 'focus', 'load', 'resize', 'scroll', 'messageerror'];
  219. const marqueeEventNames = ['bounce', 'finish', 'start'];
  220. const XMLHttpRequestEventNames = [
  221. 'loadstart', 'progress', 'abort', 'error', 'load', 'progress', 'timeout', 'loadend',
  222. 'readystatechange'
  223. ];
  224. const IDBIndexEventNames =
  225. ['upgradeneeded', 'complete', 'abort', 'success', 'error', 'blocked', 'versionchange', 'close'];
  226. const websocketEventNames = ['close', 'error', 'open', 'message'];
  227. const workerEventNames = ['error', 'message'];
  228. export const eventNames = globalEventHandlersEventNames.concat(
  229. webglEventNames, formEventNames, detailEventNames, documentEventNames, windowEventNames,
  230. htmlElementEventNames, ieElementEventNames);
  231. export interface IgnoreProperty {
  232. target: any;
  233. ignoreProperties: string[];
  234. }
  235. function filterProperties(
  236. target: any, onProperties: string[], ignoreProperties: IgnoreProperty[]): string[] {
  237. if (!ignoreProperties) {
  238. return onProperties;
  239. }
  240. const tip: IgnoreProperty[] = ignoreProperties.filter(ip => ip.target === target);
  241. if (!tip || tip.length === 0) {
  242. return onProperties;
  243. }
  244. const targetIgnoreProperties: string[] = tip[0].ignoreProperties;
  245. return onProperties.filter(op => targetIgnoreProperties.indexOf(op) === -1);
  246. }
  247. export function patchFilteredProperties(
  248. target: any, onProperties: string[], ignoreProperties: IgnoreProperty[], prototype?: any) {
  249. // check whether target is available, sometimes target will be undefined
  250. // because different browser or some 3rd party plugin.
  251. if (!target) {
  252. return;
  253. }
  254. const filteredProperties: string[] = filterProperties(target, onProperties, ignoreProperties);
  255. patchOnProperties(target, filteredProperties, prototype);
  256. }
  257. export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) {
  258. if (isNode && !isMix) {
  259. return;
  260. }
  261. const supportsWebSocket = typeof WebSocket !== 'undefined';
  262. if (canPatchViaPropertyDescriptor()) {
  263. const ignoreProperties: IgnoreProperty[] = _global.__Zone_ignore_on_properties;
  264. // for browsers that we can patch the descriptor: Chrome & Firefox
  265. if (isBrowser) {
  266. const internalWindow: any = window;
  267. // in IE/Edge, onProp not exist in window object, but in WindowPrototype
  268. // so we need to pass WindowPrototype to check onProp exist or not
  269. patchFilteredProperties(
  270. internalWindow, eventNames.concat(['messageerror']), ignoreProperties,
  271. ObjectGetPrototypeOf(internalWindow));
  272. patchFilteredProperties(Document.prototype, eventNames, ignoreProperties);
  273. if (typeof internalWindow['SVGElement'] !== 'undefined') {
  274. patchFilteredProperties(
  275. internalWindow['SVGElement'].prototype, eventNames, ignoreProperties);
  276. }
  277. patchFilteredProperties(Element.prototype, eventNames, ignoreProperties);
  278. patchFilteredProperties(HTMLElement.prototype, eventNames, ignoreProperties);
  279. patchFilteredProperties(HTMLMediaElement.prototype, mediaElementEventNames, ignoreProperties);
  280. patchFilteredProperties(
  281. HTMLFrameSetElement.prototype, windowEventNames.concat(frameSetEventNames),
  282. ignoreProperties);
  283. patchFilteredProperties(
  284. HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames), ignoreProperties);
  285. patchFilteredProperties(HTMLFrameElement.prototype, frameEventNames, ignoreProperties);
  286. patchFilteredProperties(HTMLIFrameElement.prototype, frameEventNames, ignoreProperties);
  287. const HTMLMarqueeElement = internalWindow['HTMLMarqueeElement'];
  288. if (HTMLMarqueeElement) {
  289. patchFilteredProperties(HTMLMarqueeElement.prototype, marqueeEventNames, ignoreProperties);
  290. }
  291. const Worker = internalWindow['Worker'];
  292. if (Worker) {
  293. patchFilteredProperties(Worker.prototype, workerEventNames, ignoreProperties);
  294. }
  295. }
  296. patchFilteredProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames, ignoreProperties);
  297. const XMLHttpRequestEventTarget = _global['XMLHttpRequestEventTarget'];
  298. if (XMLHttpRequestEventTarget) {
  299. patchFilteredProperties(
  300. XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype,
  301. XMLHttpRequestEventNames, ignoreProperties);
  302. }
  303. if (typeof IDBIndex !== 'undefined') {
  304. patchFilteredProperties(IDBIndex.prototype, IDBIndexEventNames, ignoreProperties);
  305. patchFilteredProperties(IDBRequest.prototype, IDBIndexEventNames, ignoreProperties);
  306. patchFilteredProperties(IDBOpenDBRequest.prototype, IDBIndexEventNames, ignoreProperties);
  307. patchFilteredProperties(IDBDatabase.prototype, IDBIndexEventNames, ignoreProperties);
  308. patchFilteredProperties(IDBTransaction.prototype, IDBIndexEventNames, ignoreProperties);
  309. patchFilteredProperties(IDBCursor.prototype, IDBIndexEventNames, ignoreProperties);
  310. }
  311. if (supportsWebSocket) {
  312. patchFilteredProperties(WebSocket.prototype, websocketEventNames, ignoreProperties);
  313. }
  314. } else {
  315. // Safari, Android browsers (Jelly Bean)
  316. patchViaCapturingAllTheEvents();
  317. patchClass('XMLHttpRequest');
  318. if (supportsWebSocket) {
  319. webSocketPatch.apply(api, _global);
  320. }
  321. }
  322. }
  323. function canPatchViaPropertyDescriptor() {
  324. if ((isBrowser || isMix) && !ObjectGetOwnPropertyDescriptor(HTMLElement.prototype, 'onclick') &&
  325. typeof Element !== 'undefined') {
  326. // WebKit https://bugs.webkit.org/show_bug.cgi?id=134364
  327. // IDL interface attributes are not configurable
  328. const desc = ObjectGetOwnPropertyDescriptor(Element.prototype, 'onclick');
  329. if (desc && !desc.configurable) return false;
  330. }
  331. const ON_READY_STATE_CHANGE = 'onreadystatechange';
  332. const XMLHttpRequestPrototype = XMLHttpRequest.prototype;
  333. const xhrDesc = ObjectGetOwnPropertyDescriptor(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE);
  334. // add enumerable and configurable here because in opera
  335. // by default XMLHttpRequest.prototype.onreadystatechange is undefined
  336. // without adding enumerable and configurable will cause onreadystatechange
  337. // non-configurable
  338. // and if XMLHttpRequest.prototype.onreadystatechange is undefined,
  339. // we should set a real desc instead a fake one
  340. if (xhrDesc) {
  341. ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, {
  342. enumerable: true,
  343. configurable: true,
  344. get: function() {
  345. return true;
  346. }
  347. });
  348. const req = new XMLHttpRequest();
  349. const result = !!req.onreadystatechange;
  350. // restore original desc
  351. ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, xhrDesc || {});
  352. return result;
  353. } else {
  354. const SYMBOL_FAKE_ONREADYSTATECHANGE = zoneSymbol('fake');
  355. ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, {
  356. enumerable: true,
  357. configurable: true,
  358. get: function() {
  359. return this[SYMBOL_FAKE_ONREADYSTATECHANGE];
  360. },
  361. set: function(value) {
  362. this[SYMBOL_FAKE_ONREADYSTATECHANGE] = value;
  363. }
  364. });
  365. const req = new XMLHttpRequest();
  366. const detectFunc = () => {};
  367. req.onreadystatechange = detectFunc;
  368. const result = (req as any)[SYMBOL_FAKE_ONREADYSTATECHANGE] === detectFunc;
  369. req.onreadystatechange = null;
  370. return result;
  371. }
  372. }
  373. const unboundKey = zoneSymbol('unbound');
  374. // Whenever any eventListener fires, we check the eventListener target and all parents
  375. // for `onwhatever` properties and replace them with zone-bound functions
  376. // - Chrome (for now)
  377. function patchViaCapturingAllTheEvents() {
  378. for (let i = 0; i < eventNames.length; i++) {
  379. const property = eventNames[i];
  380. const onproperty = 'on' + property;
  381. self.addEventListener(property, function(event) {
  382. let elt: any = <Node>event.target, bound, source;
  383. if (elt) {
  384. source = elt.constructor['name'] + '.' + onproperty;
  385. } else {
  386. source = 'unknown.' + onproperty;
  387. }
  388. while (elt) {
  389. if (elt[onproperty] && !elt[onproperty][unboundKey]) {
  390. bound = wrapWithCurrentZone(elt[onproperty], source);
  391. bound[unboundKey] = elt[onproperty];
  392. elt[onproperty] = bound;
  393. }
  394. elt = elt.parentElement;
  395. }
  396. }, true);
  397. }
  398. }