125 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			125 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google Inc. All Rights Reserved.
 | |
|  *
 | |
|  * Use of this source code is governed by an MIT-style license that can be
 | |
|  * found in the LICENSE file at https://angular.io/license
 | |
|  */
 | |
| /**
 | |
|  * @fileoverview
 | |
|  * @suppress {globalThis}
 | |
|  */
 | |
| 
 | |
| import * as webSocketPatch from './websocket';
 | |
| 
 | |
| export function propertyDescriptorLegacyPatch(api: _ZonePrivate, _global: any) {
 | |
|   const {isNode, isMix} = api.getGlobalObjects() !;
 | |
|   if (isNode && !isMix) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!canPatchViaPropertyDescriptor(api, _global)) {
 | |
|     const supportsWebSocket = typeof WebSocket !== 'undefined';
 | |
|     // Safari, Android browsers (Jelly Bean)
 | |
|     patchViaCapturingAllTheEvents(api);
 | |
|     api.patchClass('XMLHttpRequest');
 | |
|     if (supportsWebSocket) {
 | |
|       webSocketPatch.apply(api, _global);
 | |
|     }
 | |
|     (Zone as any)[api.symbol('patchEvents')] = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function canPatchViaPropertyDescriptor(api: _ZonePrivate, _global: any) {
 | |
|   const {isBrowser, isMix} = api.getGlobalObjects() !;
 | |
|   if ((isBrowser || isMix) &&
 | |
|       !api.ObjectGetOwnPropertyDescriptor(HTMLElement.prototype, 'onclick') &&
 | |
|       typeof Element !== 'undefined') {
 | |
|     // WebKit https://bugs.webkit.org/show_bug.cgi?id=134364
 | |
|     // IDL interface attributes are not configurable
 | |
|     const desc = api.ObjectGetOwnPropertyDescriptor(Element.prototype, 'onclick');
 | |
|     if (desc && !desc.configurable) return false;
 | |
|     // try to use onclick to detect whether we can patch via propertyDescriptor
 | |
|     // because XMLHttpRequest is not available in service worker
 | |
|     if (desc) {
 | |
|       api.ObjectDefineProperty(
 | |
|           Element.prototype, 'onclick',
 | |
|           {enumerable: true, configurable: true, get: function() { return true; }});
 | |
|       const div = document.createElement('div');
 | |
|       const result = !!div.onclick;
 | |
|       api.ObjectDefineProperty(Element.prototype, 'onclick', desc);
 | |
|       return result;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const XMLHttpRequest = _global['XMLHttpRequest'];
 | |
|   if (!XMLHttpRequest) {
 | |
|     // XMLHttpRequest is not available in service worker
 | |
|     return false;
 | |
|   }
 | |
|   const ON_READY_STATE_CHANGE = 'onreadystatechange';
 | |
|   const XMLHttpRequestPrototype = XMLHttpRequest.prototype;
 | |
| 
 | |
|   const xhrDesc =
 | |
|       api.ObjectGetOwnPropertyDescriptor(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE);
 | |
| 
 | |
|   // add enumerable and configurable here because in opera
 | |
|   // by default XMLHttpRequest.prototype.onreadystatechange is undefined
 | |
|   // without adding enumerable and configurable will cause onreadystatechange
 | |
|   // non-configurable
 | |
|   // and if XMLHttpRequest.prototype.onreadystatechange is undefined,
 | |
|   // we should set a real desc instead a fake one
 | |
|   if (xhrDesc) {
 | |
|     api.ObjectDefineProperty(
 | |
|         XMLHttpRequestPrototype, ON_READY_STATE_CHANGE,
 | |
|         {enumerable: true, configurable: true, get: function() { return true; }});
 | |
|     const req = new XMLHttpRequest();
 | |
|     const result = !!req.onreadystatechange;
 | |
|     // restore original desc
 | |
|     api.ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, xhrDesc || {});
 | |
|     return result;
 | |
|   } else {
 | |
|     const SYMBOL_FAKE_ONREADYSTATECHANGE = api.symbol('fake');
 | |
|     api.ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, {
 | |
|       enumerable: true,
 | |
|       configurable: true,
 | |
|       get: function() { return this[SYMBOL_FAKE_ONREADYSTATECHANGE]; },
 | |
|       set: function(value) { this[SYMBOL_FAKE_ONREADYSTATECHANGE] = value; }
 | |
|     });
 | |
|     const req = new XMLHttpRequest();
 | |
|     const detectFunc = () => {};
 | |
|     req.onreadystatechange = detectFunc;
 | |
|     const result = (req as any)[SYMBOL_FAKE_ONREADYSTATECHANGE] === detectFunc;
 | |
|     req.onreadystatechange = null as any;
 | |
|     return result;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Whenever any eventListener fires, we check the eventListener target and all parents
 | |
| // for `onwhatever` properties and replace them with zone-bound functions
 | |
| // - Chrome (for now)
 | |
| function patchViaCapturingAllTheEvents(api: _ZonePrivate) {
 | |
|   const {eventNames} = api.getGlobalObjects() !;
 | |
|   const unboundKey = api.symbol('unbound');
 | |
|   for (let i = 0; i < eventNames.length; i++) {
 | |
|     const property = eventNames[i];
 | |
|     const onproperty = 'on' + property;
 | |
|     self.addEventListener(property, function(event) {
 | |
|       let elt: any = <Node>event.target, bound, source;
 | |
|       if (elt) {
 | |
|         source = elt.constructor['name'] + '.' + onproperty;
 | |
|       } else {
 | |
|         source = 'unknown.' + onproperty;
 | |
|       }
 | |
|       while (elt) {
 | |
|         if (elt[onproperty] && !elt[onproperty][unboundKey]) {
 | |
|           bound = api.wrapWithCurrentZone(elt[onproperty], source);
 | |
|           bound[unboundKey] = elt[onproperty];
 | |
|           elt[onproperty] = bound;
 | |
|         }
 | |
|         elt = elt.parentElement;
 | |
|       }
 | |
|     }, true);
 | |
|   }
 | |
| }
 |