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);
|
||
|
}
|
||
|
}
|