Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions MODULE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ Below is the full list of current support modules.
|handleUnhandledPromiseRejection|NodeJS handle unhandledPromiseRejection from ZoneAwarePromise|__Zone_disable_handleUnhandledPromiseRejection = true|
|crypto|NodeJS patch crypto function as macroTask|__Zone_disable_crypto = true|

- on_property

you can also disable specified on_property by setting `__Zone_ignore_on_properties`, for example,
if you want to disable `window.onmessage` and `HTMLElement.prototype.onclick` from zone.js patching,
you can do like this.

```
<script>
__Zone_ignore_on_properties = [
{
target: window,
ignoreProperties: ['message']
}, {
target: HTMLElement.prototype,
ignoreProperties: ['click']
}
];
</script>
<script src="../dist/zone.js"></script>
```

- Angular(2+)

Angular use zone.js to manage async operations and decide when to perform change detection, so in Angular,
Expand Down
1 change: 1 addition & 0 deletions karma-dist.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
module.exports = function (config) {
require('./karma-base.conf.js')(config);
config.files.push('build/test/wtf_mock.js');
config.files.push('build/test/test_fake_polyfill.js');
config.files.push('build/test/custom_error.js');
config.files.push('dist/zone.js');
config.files.push('dist/async-test.js');
Expand Down
75 changes: 54 additions & 21 deletions lib/browser/property-descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,53 +230,86 @@ export const eventNames = globalEventHandlersEventNames.concat(
webglEventNames, formEventNames, detailEventNames, documentEventNames, windowEventNames,
htmlElementEventNames, ieElementEventNames);

export interface IgnoreProperty {
target: any;
ignoreProperties: string[];
}

function filterProperties(
target: any, onProperties: string[], ignoreProperties: IgnoreProperty[]): string[] {
if (!ignoreProperties) {
return onProperties;
}

const tip: IgnoreProperty[] = ignoreProperties.filter(ip => ip.target === target);
if (!tip || tip.length === 0) {
return onProperties;
}

const targetIgnoreProperties: string[] = tip[0].ignoreProperties;
return onProperties.filter(op => targetIgnoreProperties.indexOf(op) === -1);
}

export function patchFilteredProperties(
target: any, onProperties: string[], ignoreProperties: IgnoreProperty[], prototype?: any) {
const filteredProperties: string[] = filterProperties(target, onProperties, ignoreProperties);
patchOnProperties(target, filteredProperties, prototype);
}

export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) {
if (isNode && !isMix) {
return;
}

const supportsWebSocket = typeof WebSocket !== 'undefined';
if (canPatchViaPropertyDescriptor()) {
const ignoreProperties: IgnoreProperty[] = _global.__Zone_ignore_on_properties;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we turn this into our own module so that it is consistent with the rest of the system?

Copy link
Collaborator Author

@JiaLiPassion JiaLiPassion Aug 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhevery , currently propertyDescriptorPatch is only be called from here, it is already in our module system.

Zone.__load_patch('on_property', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
  propertyDescriptorPatch(api, global);
  propertyPatch();
  registerElementPatch(global);
});

and the _global is also from our own module parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what is the point of having this check?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh, now I understand. ... :-)

// for browsers that we can patch the descriptor: Chrome & Firefox
if (isBrowser) {
// in IE/Edge, onProp not exist in window object, but in WindowPrototype
// so we need to pass WindowPrototype to check onProp exist or not
patchOnProperties(window, eventNames.concat(['messageerror']), Object.getPrototypeOf(window));
patchOnProperties(Document.prototype, eventNames);
patchFilteredProperties(
window, eventNames.concat(['messageerror']), ignoreProperties,
Object.getPrototypeOf(window));
patchFilteredProperties(Document.prototype, eventNames, ignoreProperties);

if (typeof(<any>window)['SVGElement'] !== 'undefined') {
patchOnProperties((<any>window)['SVGElement'].prototype, eventNames);
patchFilteredProperties(
(<any>window)['SVGElement'].prototype, eventNames, ignoreProperties);
}
patchOnProperties(Element.prototype, eventNames);
patchOnProperties(HTMLElement.prototype, eventNames);
patchOnProperties(HTMLMediaElement.prototype, mediaElementEventNames);
patchOnProperties(HTMLFrameSetElement.prototype, windowEventNames.concat(frameSetEventNames));
patchOnProperties(HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames));
patchOnProperties(HTMLFrameElement.prototype, frameEventNames);
patchOnProperties(HTMLIFrameElement.prototype, frameEventNames);
patchFilteredProperties(Element.prototype, eventNames, ignoreProperties);
patchFilteredProperties(HTMLElement.prototype, eventNames, ignoreProperties);
patchFilteredProperties(HTMLMediaElement.prototype, mediaElementEventNames, ignoreProperties);
patchFilteredProperties(
HTMLFrameSetElement.prototype, windowEventNames.concat(frameSetEventNames),
ignoreProperties);
patchFilteredProperties(
HTMLBodyElement.prototype, windowEventNames.concat(frameSetEventNames), ignoreProperties);
patchFilteredProperties(HTMLFrameElement.prototype, frameEventNames, ignoreProperties);
patchFilteredProperties(HTMLIFrameElement.prototype, frameEventNames, ignoreProperties);

const HTMLMarqueeElement = (window as any)['HTMLMarqueeElement'];
if (HTMLMarqueeElement) {
patchOnProperties(HTMLMarqueeElement.prototype, marqueeEventNames);
patchFilteredProperties(HTMLMarqueeElement.prototype, marqueeEventNames, ignoreProperties);
}
}
patchOnProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames);
patchFilteredProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames, ignoreProperties);
const XMLHttpRequestEventTarget = _global['XMLHttpRequestEventTarget'];
if (XMLHttpRequestEventTarget) {
patchOnProperties(
patchFilteredProperties(
XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype,
XMLHttpRequestEventNames);
XMLHttpRequestEventNames, ignoreProperties);
}
if (typeof IDBIndex !== 'undefined') {
patchOnProperties(IDBIndex.prototype, IDBIndexEventNames);
patchOnProperties(IDBRequest.prototype, IDBIndexEventNames);
patchOnProperties(IDBOpenDBRequest.prototype, IDBIndexEventNames);
patchOnProperties(IDBDatabase.prototype, IDBIndexEventNames);
patchOnProperties(IDBTransaction.prototype, IDBIndexEventNames);
patchOnProperties(IDBCursor.prototype, IDBIndexEventNames);
patchFilteredProperties(IDBIndex.prototype, IDBIndexEventNames, ignoreProperties);
patchFilteredProperties(IDBRequest.prototype, IDBIndexEventNames, ignoreProperties);
patchFilteredProperties(IDBOpenDBRequest.prototype, IDBIndexEventNames, ignoreProperties);
patchFilteredProperties(IDBDatabase.prototype, IDBIndexEventNames, ignoreProperties);
patchFilteredProperties(IDBTransaction.prototype, IDBIndexEventNames, ignoreProperties);
patchFilteredProperties(IDBCursor.prototype, IDBIndexEventNames, ignoreProperties);
}
if (supportsWebSocket) {
patchOnProperties(WebSocket.prototype, websocketEventNames);
patchFilteredProperties(WebSocket.prototype, websocketEventNames, ignoreProperties);
}
} else {
// Safari, Android browsers (Jelly Bean)
Expand Down
26 changes: 23 additions & 3 deletions test/browser/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {patchFilteredProperties} from '../../lib/browser/property-descriptor';
import {isBrowser, isIEOrEdge, isMix, zoneSymbol} from '../../lib/common/utils';
import {ifEnvSupports, ifEnvSupportsWithDone} from '../test-util';

Expand Down Expand Up @@ -129,9 +130,6 @@ describe('Zone', function() {
for (let prop in target) {
if (prop.substr(0, 2) === 'on' && prop.length > 2) {
target[prop] = noop;
if (!target[Zone.__symbol__('ON_PROPERTY' + prop.substr(2))]) {
console.log('prop', prop);
}
expect(target[Zone.__symbol__('ON_PROPERTY' + prop.substr(2))]).toBeTruthy();
target[prop] = null;
expect(!target[Zone.__symbol__('ON_PROPERTY' + prop.substr(2))]).toBeTruthy();
Expand Down Expand Up @@ -175,6 +173,28 @@ describe('Zone', function() {
checkIsOnPropertiesPatched(new XMLHttpRequest());
});

it('should not patch ignored on properties', function() {
const TestTarget: any = (window as any)['TestTarget'];
patchFilteredProperties(
TestTarget.prototype, ['prop1', 'prop2'], global['__Zone_ignore_on_properties']);
const testTarget = new TestTarget();
Zone.current.fork({name: 'test'}).run(() => {
testTarget.onprop1 = function() {
// onprop1 should not be patched
expect(Zone.current.name).toEqual('test1');
};
testTarget.onprop2 = function() {
// onprop2 should be patched
expect(Zone.current.name).toEqual('test');
};
});

Zone.current.fork({name: 'test1'}).run(() => {
testTarget.dispatchEvent('prop1');
testTarget.dispatchEvent('prop2');
});
});

it('window onclick should be in zone',
ifEnvSupports(canPatchOnProperty(window, 'onmousedown'), function() {
zone.run(function() {
Expand Down
38 changes: 38 additions & 0 deletions test/test_fake_polyfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,42 @@
};

global.cordova = fakeCordova;

const TestTarget = global.TestTarget = function() {};

Object.defineProperties(TestTarget.prototype, {
'onprop1': {configurable: true, writable: true},
'onprop2': {configurable: true, writable: true},
'addEventListener': {
configurable: true,
writable: true,
value: function(eventName: string, callback: Function) {
if (!this.events) {
this.events = {};
}
this.events.eventName = {zone: Zone.current, callback: callback};
}
},
'removeEventListener': {
configurable: true,
writable: true,
value: function(eventName: string, callback: Function) {
if (!this.events) {
return;
}
this.events.eventName = null;
}
},
'dispatchEvent': {
configurable: true,
writable: true,
value: function(eventName: string) {
const zoneCallback = this.events && this.events.eventName;
zoneCallback && zoneCallback.zone.run(zoneCallback.callback, this, [{type: eventName}]);
}
}
});

global['__Zone_ignore_on_properties'] =
[{target: TestTarget.prototype, ignoreProperties: ['prop1']}];
})(typeof window === 'object' && window || typeof self === 'object' && self || global);