utils.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  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. * Suppress closure compiler errors about unknown 'Zone' variable
  10. * @fileoverview
  11. * @suppress {undefinedVars,globalThis,missingRequire}
  12. */
  13. // issue #989, to reduce bundle size, use short name
  14. /** Object.getOwnPropertyDescriptor */
  15. export const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
  16. /** Object.defineProperty */
  17. export const ObjectDefineProperty = Object.defineProperty;
  18. /** Object.getPrototypeOf */
  19. export const ObjectGetPrototypeOf = Object.getPrototypeOf;
  20. /** Object.create */
  21. export const ObjectCreate = Object.create;
  22. /** Array.prototype.slice */
  23. export const ArraySlice = Array.prototype.slice;
  24. /** addEventListener string const */
  25. export const ADD_EVENT_LISTENER_STR = 'addEventListener';
  26. /** removeEventListener string const */
  27. export const REMOVE_EVENT_LISTENER_STR = 'removeEventListener';
  28. /** zoneSymbol addEventListener */
  29. export const ZONE_SYMBOL_ADD_EVENT_LISTENER = Zone.__symbol__(ADD_EVENT_LISTENER_STR);
  30. /** zoneSymbol removeEventListener */
  31. export const ZONE_SYMBOL_REMOVE_EVENT_LISTENER = Zone.__symbol__(REMOVE_EVENT_LISTENER_STR);
  32. /** true string const */
  33. export const TRUE_STR = 'true';
  34. /** false string const */
  35. export const FALSE_STR = 'false';
  36. /** __zone_symbol__ string const */
  37. export const ZONE_SYMBOL_PREFIX = '__zone_symbol__';
  38. export function wrapWithCurrentZone<T extends Function>(callback: T, source: string): T {
  39. return Zone.current.wrap(callback, source);
  40. }
  41. export function scheduleMacroTaskWithCurrentZone(
  42. source: string, callback: Function, data?: TaskData, customSchedule?: (task: Task) => void,
  43. customCancel?: (task: Task) => void): MacroTask {
  44. return Zone.current.scheduleMacroTask(source, callback, data, customSchedule, customCancel);
  45. }
  46. // Hack since TypeScript isn't compiling this for a worker.
  47. declare const WorkerGlobalScope: any;
  48. export const zoneSymbol = Zone.__symbol__;
  49. const isWindowExists = typeof window !== 'undefined';
  50. const internalWindow: any = isWindowExists ? window : undefined;
  51. const _global: any = isWindowExists && internalWindow || typeof self === 'object' && self || global;
  52. const REMOVE_ATTRIBUTE = 'removeAttribute';
  53. const NULL_ON_PROP_VALUE: [any] = [null];
  54. export function bindArguments(args: any[], source: string): any[] {
  55. for (let i = args.length - 1; i >= 0; i--) {
  56. if (typeof args[i] === 'function') {
  57. args[i] = wrapWithCurrentZone(args[i], source + '_' + i);
  58. }
  59. }
  60. return args;
  61. }
  62. export function patchPrototype(prototype: any, fnNames: string[]) {
  63. const source = prototype.constructor['name'];
  64. for (let i = 0; i < fnNames.length; i++) {
  65. const name = fnNames[i];
  66. const delegate = prototype[name];
  67. if (delegate) {
  68. const prototypeDesc = ObjectGetOwnPropertyDescriptor(prototype, name);
  69. if (!isPropertyWritable(prototypeDesc)) {
  70. continue;
  71. }
  72. prototype[name] = ((delegate: Function) => {
  73. const patched: any = function() {
  74. return delegate.apply(this, bindArguments(<any>arguments, source + '.' + name));
  75. };
  76. attachOriginToPatched(patched, delegate);
  77. return patched;
  78. })(delegate);
  79. }
  80. }
  81. }
  82. export function isPropertyWritable(propertyDesc: any) {
  83. if (!propertyDesc) {
  84. return true;
  85. }
  86. if (propertyDesc.writable === false) {
  87. return false;
  88. }
  89. return !(typeof propertyDesc.get === 'function' && typeof propertyDesc.set === 'undefined');
  90. }
  91. export const isWebWorker: boolean =
  92. (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope);
  93. // Make sure to access `process` through `_global` so that WebPack does not accidentally browserify
  94. // this code.
  95. export const isNode: boolean =
  96. (!('nw' in _global) && typeof _global.process !== 'undefined' &&
  97. {}.toString.call(_global.process) === '[object process]');
  98. export const isBrowser: boolean =
  99. !isNode && !isWebWorker && !!(isWindowExists && internalWindow['HTMLElement']);
  100. // we are in electron of nw, so we are both browser and nodejs
  101. // Make sure to access `process` through `_global` so that WebPack does not accidentally browserify
  102. // this code.
  103. export const isMix: boolean = typeof _global.process !== 'undefined' &&
  104. {}.toString.call(_global.process) === '[object process]' && !isWebWorker &&
  105. !!(isWindowExists && internalWindow['HTMLElement']);
  106. const zoneSymbolEventNames: {[eventName: string]: string} = {};
  107. const wrapFn = function(event: Event) {
  108. // https://github.com/angular/zone.js/issues/911, in IE, sometimes
  109. // event will be undefined, so we need to use window.event
  110. event = event || _global.event;
  111. if (!event) {
  112. return;
  113. }
  114. let eventNameSymbol = zoneSymbolEventNames[event.type];
  115. if (!eventNameSymbol) {
  116. eventNameSymbol = zoneSymbolEventNames[event.type] = zoneSymbol('ON_PROPERTY' + event.type);
  117. }
  118. const target = this || event.target || _global;
  119. const listener = target[eventNameSymbol];
  120. let result;
  121. if (isBrowser && target === internalWindow && event.type === 'error') {
  122. // window.onerror have different signiture
  123. // https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror#window.onerror
  124. // and onerror callback will prevent default when callback return true
  125. const errorEvent: ErrorEvent = event as any;
  126. result = listener &&
  127. listener.call(
  128. this, errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno,
  129. errorEvent.error);
  130. if (result === true) {
  131. event.preventDefault();
  132. }
  133. } else {
  134. result = listener && listener.apply(this, arguments);
  135. if (result != undefined && !result) {
  136. event.preventDefault();
  137. }
  138. }
  139. return result;
  140. };
  141. export function patchProperty(obj: any, prop: string, prototype?: any) {
  142. let desc = ObjectGetOwnPropertyDescriptor(obj, prop);
  143. if (!desc && prototype) {
  144. // when patch window object, use prototype to check prop exist or not
  145. const prototypeDesc = ObjectGetOwnPropertyDescriptor(prototype, prop);
  146. if (prototypeDesc) {
  147. desc = {enumerable: true, configurable: true};
  148. }
  149. }
  150. // if the descriptor not exists or is not configurable
  151. // just return
  152. if (!desc || !desc.configurable) {
  153. return;
  154. }
  155. const onPropPatchedSymbol = zoneSymbol('on' + prop + 'patched');
  156. if (obj.hasOwnProperty(onPropPatchedSymbol) && obj[onPropPatchedSymbol]) {
  157. return;
  158. }
  159. // A property descriptor cannot have getter/setter and be writable
  160. // deleting the writable and value properties avoids this error:
  161. //
  162. // TypeError: property descriptors must not specify a value or be writable when a
  163. // getter or setter has been specified
  164. delete desc.writable;
  165. delete desc.value;
  166. const originalDescGet = desc.get;
  167. const originalDescSet = desc.set;
  168. // substr(2) cuz 'onclick' -> 'click', etc
  169. const eventName = prop.substr(2);
  170. let eventNameSymbol = zoneSymbolEventNames[eventName];
  171. if (!eventNameSymbol) {
  172. eventNameSymbol = zoneSymbolEventNames[eventName] = zoneSymbol('ON_PROPERTY' + eventName);
  173. }
  174. desc.set = function(newValue) {
  175. // in some of windows's onproperty callback, this is undefined
  176. // so we need to check it
  177. let target = this;
  178. if (!target && obj === _global) {
  179. target = _global;
  180. }
  181. if (!target) {
  182. return;
  183. }
  184. let previousValue = target[eventNameSymbol];
  185. if (previousValue) {
  186. target.removeEventListener(eventName, wrapFn);
  187. }
  188. // issue #978, when onload handler was added before loading zone.js
  189. // we should remove it with originalDescSet
  190. if (originalDescSet) {
  191. originalDescSet.apply(target, NULL_ON_PROP_VALUE);
  192. }
  193. if (typeof newValue === 'function') {
  194. target[eventNameSymbol] = newValue;
  195. target.addEventListener(eventName, wrapFn, false);
  196. } else {
  197. target[eventNameSymbol] = null;
  198. }
  199. };
  200. // The getter would return undefined for unassigned properties but the default value of an
  201. // unassigned property is null
  202. desc.get = function() {
  203. // in some of windows's onproperty callback, this is undefined
  204. // so we need to check it
  205. let target = this;
  206. if (!target && obj === _global) {
  207. target = _global;
  208. }
  209. if (!target) {
  210. return null;
  211. }
  212. const listener = target[eventNameSymbol];
  213. if (listener) {
  214. return listener;
  215. } else if (originalDescGet) {
  216. // result will be null when use inline event attribute,
  217. // such as <button onclick="func();">OK</button>
  218. // because the onclick function is internal raw uncompiled handler
  219. // the onclick will be evaluated when first time event was triggered or
  220. // the property is accessed, https://github.com/angular/zone.js/issues/525
  221. // so we should use original native get to retrieve the handler
  222. let value = originalDescGet && originalDescGet.call(this);
  223. if (value) {
  224. desc!.set!.call(this, value);
  225. if (typeof target[REMOVE_ATTRIBUTE] === 'function') {
  226. target.removeAttribute(prop);
  227. }
  228. return value;
  229. }
  230. }
  231. return null;
  232. };
  233. ObjectDefineProperty(obj, prop, desc);
  234. obj[onPropPatchedSymbol] = true;
  235. }
  236. export function patchOnProperties(obj: any, properties: string[]|null, prototype?: any) {
  237. if (properties) {
  238. for (let i = 0; i < properties.length; i++) {
  239. patchProperty(obj, 'on' + properties[i], prototype);
  240. }
  241. } else {
  242. const onProperties = [];
  243. for (const prop in obj) {
  244. if (prop.substr(0, 2) == 'on') {
  245. onProperties.push(prop);
  246. }
  247. }
  248. for (let j = 0; j < onProperties.length; j++) {
  249. patchProperty(obj, onProperties[j], prototype);
  250. }
  251. }
  252. }
  253. const originalInstanceKey = zoneSymbol('originalInstance');
  254. // wrap some native API on `window`
  255. export function patchClass(className: string) {
  256. const OriginalClass = _global[className];
  257. if (!OriginalClass) return;
  258. // keep original class in global
  259. _global[zoneSymbol(className)] = OriginalClass;
  260. _global[className] = function() {
  261. const a = bindArguments(<any>arguments, className);
  262. switch (a.length) {
  263. case 0:
  264. this[originalInstanceKey] = new OriginalClass();
  265. break;
  266. case 1:
  267. this[originalInstanceKey] = new OriginalClass(a[0]);
  268. break;
  269. case 2:
  270. this[originalInstanceKey] = new OriginalClass(a[0], a[1]);
  271. break;
  272. case 3:
  273. this[originalInstanceKey] = new OriginalClass(a[0], a[1], a[2]);
  274. break;
  275. case 4:
  276. this[originalInstanceKey] = new OriginalClass(a[0], a[1], a[2], a[3]);
  277. break;
  278. default:
  279. throw new Error('Arg list too long.');
  280. }
  281. };
  282. // attach original delegate to patched function
  283. attachOriginToPatched(_global[className], OriginalClass);
  284. const instance = new OriginalClass(function() {});
  285. let prop;
  286. for (prop in instance) {
  287. // https://bugs.webkit.org/show_bug.cgi?id=44721
  288. if (className === 'XMLHttpRequest' && prop === 'responseBlob') continue;
  289. (function(prop) {
  290. if (typeof instance[prop] === 'function') {
  291. _global[className].prototype[prop] = function() {
  292. return this[originalInstanceKey][prop].apply(this[originalInstanceKey], arguments);
  293. };
  294. } else {
  295. ObjectDefineProperty(_global[className].prototype, prop, {
  296. set: function(fn) {
  297. if (typeof fn === 'function') {
  298. this[originalInstanceKey][prop] = wrapWithCurrentZone(fn, className + '.' + prop);
  299. // keep callback in wrapped function so we can
  300. // use it in Function.prototype.toString to return
  301. // the native one.
  302. attachOriginToPatched(this[originalInstanceKey][prop], fn);
  303. } else {
  304. this[originalInstanceKey][prop] = fn;
  305. }
  306. },
  307. get: function() {
  308. return this[originalInstanceKey][prop];
  309. }
  310. });
  311. }
  312. }(prop));
  313. }
  314. for (prop in OriginalClass) {
  315. if (prop !== 'prototype' && OriginalClass.hasOwnProperty(prop)) {
  316. _global[className][prop] = OriginalClass[prop];
  317. }
  318. }
  319. }
  320. export function copySymbolProperties(src: any, dest: any) {
  321. if (typeof (Object as any).getOwnPropertySymbols !== 'function') {
  322. return;
  323. }
  324. const symbols: any = (Object as any).getOwnPropertySymbols(src);
  325. symbols.forEach((symbol: any) => {
  326. const desc = Object.getOwnPropertyDescriptor(src, symbol);
  327. Object.defineProperty(dest, symbol, {
  328. get: function() {
  329. return src[symbol];
  330. },
  331. set: function(value: any) {
  332. if (desc && (!desc.writable || typeof desc.set !== 'function')) {
  333. // if src[symbol] is not writable or not have a setter, just return
  334. return;
  335. }
  336. src[symbol] = value;
  337. },
  338. enumerable: desc ? desc.enumerable : true,
  339. configurable: desc ? desc.configurable : true
  340. });
  341. });
  342. }
  343. let shouldCopySymbolProperties = false;
  344. export function setShouldCopySymbolProperties(flag: boolean) {
  345. shouldCopySymbolProperties = flag;
  346. }
  347. export function patchMethod(
  348. target: any, name: string,
  349. patchFn: (delegate: Function, delegateName: string, name: string) => (self: any, args: any[]) =>
  350. any): Function|null {
  351. let proto = target;
  352. while (proto && !proto.hasOwnProperty(name)) {
  353. proto = ObjectGetPrototypeOf(proto);
  354. }
  355. if (!proto && target[name]) {
  356. // somehow we did not find it, but we can see it. This happens on IE for Window properties.
  357. proto = target;
  358. }
  359. const delegateName = zoneSymbol(name);
  360. let delegate: Function|null = null;
  361. if (proto && !(delegate = proto[delegateName])) {
  362. delegate = proto[delegateName] = proto[name];
  363. // check whether proto[name] is writable
  364. // some property is readonly in safari, such as HtmlCanvasElement.prototype.toBlob
  365. const desc = proto && ObjectGetOwnPropertyDescriptor(proto, name);
  366. if (isPropertyWritable(desc)) {
  367. const patchDelegate = patchFn(delegate!, delegateName, name);
  368. proto[name] = function() {
  369. return patchDelegate(this, arguments as any);
  370. };
  371. attachOriginToPatched(proto[name], delegate);
  372. if (shouldCopySymbolProperties) {
  373. copySymbolProperties(delegate, proto[name]);
  374. }
  375. }
  376. }
  377. return delegate;
  378. }
  379. export interface MacroTaskMeta extends TaskData {
  380. name: string;
  381. target: any;
  382. cbIdx: number;
  383. args: any[];
  384. }
  385. // TODO: @JiaLiPassion, support cancel task later if necessary
  386. export function patchMacroTask(
  387. obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MacroTaskMeta) {
  388. let setNative: Function|null = null;
  389. function scheduleTask(task: Task) {
  390. const data = <MacroTaskMeta>task.data;
  391. data.args[data.cbIdx] = function() {
  392. task.invoke.apply(this, arguments);
  393. };
  394. setNative!.apply(data.target, data.args);
  395. return task;
  396. }
  397. setNative = patchMethod(obj, funcName, (delegate: Function) => function(self: any, args: any[]) {
  398. const meta = metaCreator(self, args);
  399. if (meta.cbIdx >= 0 && typeof args[meta.cbIdx] === 'function') {
  400. return scheduleMacroTaskWithCurrentZone(meta.name, args[meta.cbIdx], meta, scheduleTask);
  401. } else {
  402. // cause an error by calling it directly.
  403. return delegate.apply(self, args);
  404. }
  405. });
  406. }
  407. export interface MicroTaskMeta extends TaskData {
  408. name: string;
  409. target: any;
  410. cbIdx: number;
  411. args: any[];
  412. }
  413. export function patchMicroTask(
  414. obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MicroTaskMeta) {
  415. let setNative: Function|null = null;
  416. function scheduleTask(task: Task) {
  417. const data = <MacroTaskMeta>task.data;
  418. data.args[data.cbIdx] = function() {
  419. task.invoke.apply(this, arguments);
  420. };
  421. setNative!.apply(data.target, data.args);
  422. return task;
  423. }
  424. setNative = patchMethod(obj, funcName, (delegate: Function) => function(self: any, args: any[]) {
  425. const meta = metaCreator(self, args);
  426. if (meta.cbIdx >= 0 && typeof args[meta.cbIdx] === 'function') {
  427. return Zone.current.scheduleMicroTask(meta.name, args[meta.cbIdx], meta, scheduleTask);
  428. } else {
  429. // cause an error by calling it directly.
  430. return delegate.apply(self, args);
  431. }
  432. });
  433. }
  434. export function attachOriginToPatched(patched: Function, original: any) {
  435. (patched as any)[zoneSymbol('OriginalDelegate')] = original;
  436. }
  437. let isDetectedIEOrEdge = false;
  438. let ieOrEdge = false;
  439. export function isIE() {
  440. try {
  441. const ua = internalWindow.navigator.userAgent;
  442. if (ua.indexOf('MSIE ') !== -1 || ua.indexOf('Trident/') !== -1) {
  443. return true;
  444. }
  445. } catch (error) {
  446. }
  447. return false;
  448. }
  449. export function isIEOrEdge() {
  450. if (isDetectedIEOrEdge) {
  451. return ieOrEdge;
  452. }
  453. isDetectedIEOrEdge = true;
  454. try {
  455. const ua = internalWindow.navigator.userAgent;
  456. if (ua.indexOf('MSIE ') !== -1 || ua.indexOf('Trident/') !== -1 || ua.indexOf('Edge/') !== -1) {
  457. ieOrEdge = true;
  458. }
  459. } catch (error) {
  460. }
  461. return ieOrEdge;
  462. }