Skip to content
Closed
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
2 changes: 1 addition & 1 deletion packages/zone.js/lib/browser/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Zone.__load_patch('EventTarget', (global: any, Zone: ZoneType, api: _ZonePrivate
// patch XMLHttpRequestEventTarget's addEventListener/removeEventListener
const XMLHttpRequestEventTarget = (global as any)['XMLHttpRequestEventTarget'];
if (XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype) {
api.patchEventTarget(global, api, [XMLHttpRequestEventTarget.prototype]);
api.patchEventTarget(global, [XMLHttpRequestEventTarget.prototype]);
}
});

Expand Down
2 changes: 1 addition & 1 deletion packages/zone.js/lib/browser/event-target-legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export function eventTargetLegacyPatch(_global: any, api: _ZonePrivate) {
}
// vh is validateHandler to check event handler
// is valid or not(for security check)
api.patchEventTarget(_global, api, apiTypes, {
api.patchEventTarget(_global, apiTypes, {
vh: checkIEAndCrossContext,
transferEventName: (eventName: string) => {
const pointerEventName = pointerEventsMap[eventName];
Expand Down
2 changes: 1 addition & 1 deletion packages/zone.js/lib/browser/event-target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function eventTargetPatch(_global: any, api: _ZonePrivate) {
if (!EVENT_TARGET || !EVENT_TARGET.prototype) {
return;
}
api.patchEventTarget(_global, api, [EVENT_TARGET && EVENT_TARGET.prototype]);
api.patchEventTarget(_global, [EVENT_TARGET && EVENT_TARGET.prototype]);

return true;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/zone.js/lib/browser/shadydom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Zone.__load_patch('shadydom', (global: any, Zone: ZoneType, api: _ZonePrivate) =
if (proto && proto.hasOwnProperty('addEventListener')) {
proto[Zone.__symbol__('addEventListener')] = null;
proto[Zone.__symbol__('removeEventListener')] = null;
api.patchEventTarget(global, api, [proto]);
api.patchEventTarget(global, [proto]);
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ Zone.__load_patch('RTCPeerConnection', (global: any, Zone: ZoneType, api: _ZoneP
RTCPeerConnection.prototype[addSymbol] = null;
RTCPeerConnection.prototype[removeSymbol] = null;

api.patchEventTarget(global, api, [RTCPeerConnection.prototype], {useG: false});
api.patchEventTarget(global, [RTCPeerConnection.prototype], {useG: false});
});
2 changes: 1 addition & 1 deletion packages/zone.js/lib/browser/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function apply(api: _ZonePrivate, _global: any) {
// On Safari window.EventTarget doesn't exist so need to patch WS add/removeEventListener
// On older Chrome, no need since EventTarget was already patched
if (!(<any>_global).EventTarget) {
api.patchEventTarget(_global, api, [WS.prototype]);
api.patchEventTarget(_global, [WS.prototype]);
}
(<any>_global).WebSocket = function(x: any, y: any) {
const socket = arguments.length > 1 ? new WS(x, y) : new WS(x);
Expand Down
64 changes: 35 additions & 29 deletions packages/zone.js/lib/common/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export interface PatchEventTargetOptions {
}

export function patchEventTarget(
_global: any, api: _ZonePrivate, apis: any[], patchOptions?: PatchEventTargetOptions) {
_global: any, apis: any[], patchOptions?: PatchEventTargetOptions) {
const ADD_EVENT_LISTENER = (patchOptions && patchOptions.add) || ADD_EVENT_LISTENER_STR;
const REMOVE_EVENT_LISTENER = (patchOptions && patchOptions.rm) || REMOVE_EVENT_LISTENER_STR;

Expand Down Expand Up @@ -114,15 +114,7 @@ export function patchEventTarget(
task.originalDelegate = delegate;
}
// invoke static task.invoke
// need to try/catch error here, otherwise, the error in one event listener
// will break the executions of the other event listeners. Also error will
// not remove the event listener when `once` options is true.
let error;
try {
task.invoke(task, target, [event]);
} catch (err) {
error = err;
}
task.invoke(task, target, [event]);
const options = task.options;
if (options && typeof options === 'object' && options.once) {
// if options.once is true, after invoke once remove listener here
Expand All @@ -131,10 +123,10 @@ export function patchEventTarget(
const delegate = task.originalDelegate ? task.originalDelegate : task.callback;
target[REMOVE_EVENT_LISTENER].call(target, event.type, delegate, options);
}
return error;
};

function globalCallback(context: unknown, event: Event, isCapture: boolean) {
// global shared zoneAwareCallback to handle all event callback with capture = false
const globalZoneAwareCallback = function(this: unknown, event: Event) {
// https://github.com/angular/zone.js/issues/911, in IE, sometimes
// event will be undefined, so we need to use window.event
event = event || _global.event;
Expand All @@ -143,8 +135,8 @@ export function patchEventTarget(
}
// event.target is needed for Samsung TV and SourceBuffer
// || global is needed https://github.com/angular/zone.js/issues/190
const target: any = context || event.target || _global;
const tasks = target[zoneSymbolEventNames[event.type][isCapture ? TRUE_STR : FALSE_STR]];
const target: any = this || event.target || _global;
const tasks = target[zoneSymbolEventNames[event.type][FALSE_STR]];
if (tasks) {
// invoke all tasks which attached to current target with given event.type and capture = false
// for performance concern, if task.length === 1, just invoke
Expand All @@ -155,32 +147,46 @@ export function patchEventTarget(
// copy the tasks array before invoke, to avoid
// the callback will remove itself or other listener
const copyTasks = tasks.slice();
const errors = [];
for (let i = 0; i < copyTasks.length; i++) {
if (event && (event as any)[IMMEDIATE_PROPAGATION_SYMBOL] === true) {
break;
}
const err = invokeTask(copyTasks[i], target, event);
err && errors.push(err);
}
for (let i = 0; i < errors.length; i++) {
const err = errors[i];
api.nativeScheduleMicroTask(() => {
throw err;
});
invokeTask(copyTasks[i], target, event);
}
}
}
}

// global shared zoneAwareCallback to handle all event callback with capture = false
const globalZoneAwareCallback = function(this: unknown, event: Event) {
return globalCallback(this, event, false);
};

// global shared zoneAwareCallback to handle all event callback with capture = true
const globalZoneAwareCaptureCallback = function(this: unknown, event: Event) {
return globalCallback(this, event, true);
// https://github.com/angular/zone.js/issues/911, in IE, sometimes
// event will be undefined, so we need to use window.event
event = event || _global.event;
if (!event) {
return;
}
// event.target is needed for Samsung TV and SourceBuffer
// || global is needed https://github.com/angular/zone.js/issues/190
const target: any = this || event.target || _global;
const tasks = target[zoneSymbolEventNames[event.type][TRUE_STR]];
if (tasks) {
// invoke all tasks which attached to current target with given event.type and capture = false
// for performance concern, if task.length === 1, just invoke
if (tasks.length === 1) {
invokeTask(tasks[0], target, event);
} else {
// https://github.com/angular/zone.js/issues/836
// copy the tasks array before invoke, to avoid
// the callback will remove itself or other listener
const copyTasks = tasks.slice();
for (let i = 0; i < copyTasks.length; i++) {
if (event && (event as any)[IMMEDIATE_PROPAGATION_SYMBOL] === true) {
break;
}
invokeTask(copyTasks[i], target, event);
}
}
}
};

function patchEventTargetMethods(obj: any, patchOptions?: PatchEventTargetOptions) {
Expand Down
2 changes: 1 addition & 1 deletion packages/zone.js/lib/extra/socket-io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Zone.__load_patch('socketio', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
(Zone as any)[Zone.__symbol__('socketio')] = function patchSocketIO(io: any) {
// patch io.Socket.prototype event listener related method
api.patchEventTarget(global, api, [io.Socket.prototype], {
api.patchEventTarget(global, [io.Socket.prototype], {
useG: false,
chkDup: false,
rt: true,
Expand Down
4 changes: 2 additions & 2 deletions packages/zone.js/lib/node/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {patchEventTarget} from '../common/events';

Zone.__load_patch('EventEmitter', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
Zone.__load_patch('EventEmitter', (global: any) => {
// For EventEmitter
const EE_ADD_LISTENER = 'addListener';
const EE_PREPEND_LISTENER = 'prependListener';
Expand All @@ -34,7 +34,7 @@ Zone.__load_patch('EventEmitter', (global: any, Zone: ZoneType, api: _ZonePrivat
};

function patchEventEmitterMethods(obj: any) {
const result = patchEventTarget(global, api, [obj], {
const result = patchEventTarget(global, [obj], {
useG: false,
add: EE_ADD_LISTENER,
rm: EE_REMOVE_LISTENER,
Expand Down
42 changes: 18 additions & 24 deletions packages/zone.js/lib/zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ interface _ZonePrivate {
onUnhandledError: (error: Error) => void;
microtaskDrainDone: () => void;
showUncaughtError: () => boolean;
patchEventTarget: (global: any, api: _ZonePrivate, apis: any[], options?: any) => boolean[];
patchEventTarget: (global: any, apis: any[], options?: any) => boolean[];
patchOnProperties: (obj: any, properties: string[]|null, prototype?: any) => void;
patchThen: (ctro: Function) => void;
patchMethod:
Expand All @@ -359,7 +359,6 @@ interface _ZonePrivate {
filterProperties: (target: any, onProperties: string[], ignoreProperties: any[]) => string[];
attachOriginToPatched: (target: any, origin: any) => void;
_redefineProperty: (target: any, callback: string, desc: any) => void;
nativeScheduleMicroTask: (func: Function) => void;
patchCallbacks:
(api: _ZonePrivate, target: any, targetName: string, method: string,
callbacks: string[]) => void;
Expand Down Expand Up @@ -1344,31 +1343,27 @@ const Zone: ZoneType = (function(global: any) {
let _isDrainingMicrotaskQueue: boolean = false;
let nativeMicroTaskQueuePromise: any;

function nativeScheduleMicroTask(func: Function) {
if (!nativeMicroTaskQueuePromise) {
if (global[symbolPromise]) {
nativeMicroTaskQueuePromise = global[symbolPromise].resolve(0);
}
}
if (nativeMicroTaskQueuePromise) {
let nativeThen = nativeMicroTaskQueuePromise[symbolThen];
if (!nativeThen) {
// native Promise is not patchable, we need to use `then` directly
// issue 1078
nativeThen = nativeMicroTaskQueuePromise['then'];
}
nativeThen.call(nativeMicroTaskQueuePromise, func);
} else {
global[symbolSetTimeout](func, 0);
}
}

function scheduleMicroTask(task?: MicroTask) {
// if we are not running in any task, and there has not been anything scheduled
// we must bootstrap the initial task creation by manually scheduling the drain
if (_numberOfNestedTaskFrames === 0 && _microTaskQueue.length === 0) {
// We are not running in Task, so we need to kickstart the microtask queue.
nativeScheduleMicroTask(drainMicroTaskQueue);
if (!nativeMicroTaskQueuePromise) {
if (global[symbolPromise]) {
nativeMicroTaskQueuePromise = global[symbolPromise].resolve(0);
}
}
if (nativeMicroTaskQueuePromise) {
let nativeThen = nativeMicroTaskQueuePromise[symbolThen];
if (!nativeThen) {
// native Promise is not patchable, we need to use `then` directly
// issue 1078
nativeThen = nativeMicroTaskQueuePromise['then'];
}
nativeThen.call(nativeMicroTaskQueuePromise, drainMicroTaskQueue);
} else {
global[symbolSetTimeout](drainMicroTaskQueue, 0);
}
}
task && _microTaskQueue.push(task);
}
Expand Down Expand Up @@ -1433,8 +1428,7 @@ const Zone: ZoneType = (function(global: any) {
filterProperties: () => [],
attachOriginToPatched: () => noop,
_redefineProperty: () => noop,
patchCallbacks: () => noop,
nativeScheduleMicroTask: nativeScheduleMicroTask
patchCallbacks: () => noop
};
let _currentZoneFrame: _ZoneFrame = {parent: null, zone: new Zone(null, null)};
let _currentTask: Task|null = null;
Expand Down
71 changes: 0 additions & 71 deletions packages/zone.js/test/browser/XMLHttpRequest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,77 +103,6 @@ describe('XMLHttpRequest', function() {
req!.send();
});

it('should run onload listeners before internal readystatechange', function(done) {
const logs: string[] = [];
const xhrZone = Zone.current.fork({
name: 'xhr',
onInvokeTask: (delegate, curr, target, task, applyThis, applyArgs) => {
logs.push('invokeTask ' + task.source);
return delegate.invokeTask(target, task, applyThis, applyArgs);
}
});

xhrZone.run(function() {
const req = new XMLHttpRequest();
req.onload = function() {
logs.push('onload');
(window as any)[Zone.__symbol__('setTimeout')](() => {
expect(logs).toEqual([
'invokeTask XMLHttpRequest.addEventListener:load', 'onload',
'invokeTask XMLHttpRequest.send'
])
done();
});
};
req.open('get', '/', true);
req.send();
});
});

it('should invoke xhr task even onload listener throw error', function(done) {
const logs: string[] = [];
const xhrZone = Zone.current.fork({
name: 'xhr',
onInvokeTask: (delegate, curr, target, task, applyThis, applyArgs) => {
logs.push('invokeTask ' + task.source);
return delegate.invokeTask(target, task, applyThis, applyArgs);
},
onHasTask: (delegate, curr, target, hasTaskState) => {
if (hasTaskState.change === 'macroTask') {
logs.push('hasTask ' + hasTaskState.macroTask);
}
return delegate.hasTask(target, hasTaskState);
}
});

xhrZone.run(function() {
const req = new XMLHttpRequest();
req.onload = function() {
logs.push('onload');
throw new Error('test');
};
const unhandledRejection = (e: PromiseRejectionEvent) => {
logs.push(e.reason.message);
};
window.addEventListener('unhandledrejection', unhandledRejection);
req.addEventListener('load', () => {
logs.push('onload1');
(window as any)[Zone.__symbol__('setTimeout')](() => {
expect(logs).toEqual([
'hasTask true', 'invokeTask XMLHttpRequest.addEventListener:load', 'onload',
'invokeTask XMLHttpRequest.addEventListener:load', 'onload1',
'invokeTask XMLHttpRequest.send', 'hasTask false',
'invokeTask Window.addEventListener:unhandledrejection', 'test'
]);
window.removeEventListener('unhandledrejection', unhandledRejection);
done();
});
});
req.open('get', '/', true);
req.send();
});
});

it('should return null when access ontimeout first time without error', function() {
let req: XMLHttpRequest = new XMLHttpRequest();
expect(req.ontimeout).toBe(null);
Expand Down
Loading