property-descriptor-legacy.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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 * as webSocketPatch from './websocket';
  13. export function propertyDescriptorLegacyPatch(api: _ZonePrivate, _global: any) {
  14. const {isNode, isMix} = api.getGlobalObjects()!;
  15. if (isNode && !isMix) {
  16. return;
  17. }
  18. if (!canPatchViaPropertyDescriptor(api, _global)) {
  19. const supportsWebSocket = typeof WebSocket !== 'undefined';
  20. // Safari, Android browsers (Jelly Bean)
  21. patchViaCapturingAllTheEvents(api);
  22. api.patchClass('XMLHttpRequest');
  23. if (supportsWebSocket) {
  24. webSocketPatch.apply(api, _global);
  25. }
  26. (Zone as any)[api.symbol('patchEvents')] = true;
  27. }
  28. }
  29. function canPatchViaPropertyDescriptor(api: _ZonePrivate, _global: any) {
  30. const {isBrowser, isMix} = api.getGlobalObjects()!;
  31. if ((isBrowser || isMix) &&
  32. !api.ObjectGetOwnPropertyDescriptor(HTMLElement.prototype, 'onclick') &&
  33. typeof Element !== 'undefined') {
  34. // WebKit https://bugs.webkit.org/show_bug.cgi?id=134364
  35. // IDL interface attributes are not configurable
  36. const desc = api.ObjectGetOwnPropertyDescriptor(Element.prototype, 'onclick');
  37. if (desc && !desc.configurable) return false;
  38. // try to use onclick to detect whether we can patch via propertyDescriptor
  39. // because XMLHttpRequest is not available in service worker
  40. if (desc) {
  41. api.ObjectDefineProperty(Element.prototype, 'onclick', {
  42. enumerable: true,
  43. configurable: true,
  44. get: function() {
  45. return true;
  46. }
  47. });
  48. const div = document.createElement('div');
  49. const result = !!div.onclick;
  50. api.ObjectDefineProperty(Element.prototype, 'onclick', desc);
  51. return result;
  52. }
  53. }
  54. const XMLHttpRequest = _global['XMLHttpRequest'];
  55. if (!XMLHttpRequest) {
  56. // XMLHttpRequest is not available in service worker
  57. return false;
  58. }
  59. const ON_READY_STATE_CHANGE = 'onreadystatechange';
  60. const XMLHttpRequestPrototype = XMLHttpRequest.prototype;
  61. const xhrDesc =
  62. api.ObjectGetOwnPropertyDescriptor(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE);
  63. // add enumerable and configurable here because in opera
  64. // by default XMLHttpRequest.prototype.onreadystatechange is undefined
  65. // without adding enumerable and configurable will cause onreadystatechange
  66. // non-configurable
  67. // and if XMLHttpRequest.prototype.onreadystatechange is undefined,
  68. // we should set a real desc instead a fake one
  69. if (xhrDesc) {
  70. api.ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, {
  71. enumerable: true,
  72. configurable: true,
  73. get: function() {
  74. return true;
  75. }
  76. });
  77. const req = new XMLHttpRequest();
  78. const result = !!req.onreadystatechange;
  79. // restore original desc
  80. api.ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, xhrDesc || {});
  81. return result;
  82. } else {
  83. const SYMBOL_FAKE_ONREADYSTATECHANGE = api.symbol('fake');
  84. api.ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, {
  85. enumerable: true,
  86. configurable: true,
  87. get: function() {
  88. return this[SYMBOL_FAKE_ONREADYSTATECHANGE];
  89. },
  90. set: function(value) {
  91. this[SYMBOL_FAKE_ONREADYSTATECHANGE] = value;
  92. }
  93. });
  94. const req = new XMLHttpRequest();
  95. const detectFunc = () => {};
  96. req.onreadystatechange = detectFunc;
  97. const result = (req as any)[SYMBOL_FAKE_ONREADYSTATECHANGE] === detectFunc;
  98. req.onreadystatechange = null as any;
  99. return result;
  100. }
  101. }
  102. // Whenever any eventListener fires, we check the eventListener target and all parents
  103. // for `onwhatever` properties and replace them with zone-bound functions
  104. // - Chrome (for now)
  105. function patchViaCapturingAllTheEvents(api: _ZonePrivate) {
  106. const {eventNames} = api.getGlobalObjects()!;
  107. const unboundKey = api.symbol('unbound');
  108. for (let i = 0; i < eventNames.length; i++) {
  109. const property = eventNames[i];
  110. const onproperty = 'on' + property;
  111. self.addEventListener(property, function(event) {
  112. let elt: any = <Node>event.target, bound, source;
  113. if (elt) {
  114. source = elt.constructor['name'] + '.' + onproperty;
  115. } else {
  116. source = 'unknown.' + onproperty;
  117. }
  118. while (elt) {
  119. if (elt[onproperty] && !elt[onproperty][unboundKey]) {
  120. bound = api.wrapWithCurrentZone(elt[onproperty], source);
  121. bound[unboundKey] = elt[onproperty];
  122. elt[onproperty] = bound;
  123. }
  124. elt = elt.parentElement;
  125. }
  126. }, true);
  127. }
  128. }