2016-06-23 09:47:54 -07:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2016-06-23 09:47:54 -07:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2017-03-01 10:28:52 -08:00
|
|
|
import {EventEmitter} from '../event_emitter';
|
2019-05-17 20:50:02 +09:00
|
|
|
import {global} from '../util/global';
|
2020-11-19 15:19:08 +01:00
|
|
|
import {noop} from '../util/noop';
|
2019-05-17 20:50:02 +09:00
|
|
|
import {getNativeRequestAnimationFrame} from '../util/raf';
|
|
|
|
|
2016-06-08 16:38:52 -07:00
|
|
|
|
2015-04-17 18:06:24 +02:00
|
|
|
/**
|
2015-09-16 08:31:43 -07:00
|
|
|
* An injectable service for executing work inside or outside of the Angular zone.
|
2015-04-17 18:06:24 +02:00
|
|
|
*
|
2015-09-16 08:31:43 -07:00
|
|
|
* The most common use of this service is to optimize performance when starting a work consisting of
|
|
|
|
* one or more asynchronous tasks that don't require UI updates or error handling to be handled by
|
2017-04-25 02:15:33 +02:00
|
|
|
* Angular. Such tasks can be kicked off via {@link #runOutsideAngular} and if needed, these tasks
|
|
|
|
* can reenter the Angular zone via {@link #run}.
|
2015-04-17 18:06:24 +02:00
|
|
|
*
|
2015-09-16 08:31:43 -07:00
|
|
|
* <!-- TODO: add/fix links to:
|
|
|
|
* - docs explaining zones and the use of zones in Angular and change-detection
|
|
|
|
* - link to runOutsideAngular/run (throughout this file!)
|
|
|
|
* -->
|
|
|
|
*
|
2018-05-18 16:13:00 +01:00
|
|
|
* @usageNotes
|
2016-10-06 15:23:37 -07:00
|
|
|
* ### Example
|
2017-02-04 22:24:10 +00:00
|
|
|
*
|
2015-09-16 08:31:43 -07:00
|
|
|
* ```
|
2016-10-06 15:23:37 -07:00
|
|
|
* import {Component, NgZone} from '@angular/core';
|
2016-04-28 17:50:03 -07:00
|
|
|
* import {NgIf} from '@angular/common';
|
2015-09-16 08:31:43 -07:00
|
|
|
*
|
|
|
|
* @Component({
|
2017-09-20 15:17:38 +02:00
|
|
|
* selector: 'ng-zone-demo',
|
2015-09-16 08:31:43 -07:00
|
|
|
* template: `
|
|
|
|
* <h2>Demo: NgZone</h2>
|
|
|
|
*
|
|
|
|
* <p>Progress: {{progress}}%</p>
|
2015-11-23 16:02:19 -08:00
|
|
|
* <p *ngIf="progress >= 100">Done processing {{label}} of Angular zone!</p>
|
2015-09-16 08:31:43 -07:00
|
|
|
*
|
|
|
|
* <button (click)="processWithinAngularZone()">Process within Angular zone</button>
|
|
|
|
* <button (click)="processOutsideOfAngularZone()">Process outside of Angular zone</button>
|
|
|
|
* `,
|
|
|
|
* })
|
|
|
|
* export class NgZoneDemo {
|
|
|
|
* progress: number = 0;
|
|
|
|
* label: string;
|
|
|
|
*
|
|
|
|
* constructor(private _ngZone: NgZone) {}
|
|
|
|
*
|
|
|
|
* // Loop inside the Angular zone
|
|
|
|
* // so the UI DOES refresh after each setTimeout cycle
|
|
|
|
* processWithinAngularZone() {
|
|
|
|
* this.label = 'inside';
|
|
|
|
* this.progress = 0;
|
|
|
|
* this._increaseProgress(() => console.log('Inside Done!'));
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* // Loop outside of the Angular zone
|
|
|
|
* // so the UI DOES NOT refresh after each setTimeout cycle
|
|
|
|
* processOutsideOfAngularZone() {
|
|
|
|
* this.label = 'outside';
|
|
|
|
* this.progress = 0;
|
|
|
|
* this._ngZone.runOutsideAngular(() => {
|
|
|
|
* this._increaseProgress(() => {
|
2017-09-20 15:17:38 +02:00
|
|
|
* // reenter the Angular zone and display done
|
|
|
|
* this._ngZone.run(() => { console.log('Outside Done!'); });
|
|
|
|
* });
|
|
|
|
* });
|
2015-09-16 08:31:43 -07:00
|
|
|
* }
|
|
|
|
*
|
|
|
|
* _increaseProgress(doneCallback: () => void) {
|
|
|
|
* this.progress += 1;
|
|
|
|
* console.log(`Current progress: ${this.progress}%`);
|
|
|
|
*
|
|
|
|
* if (this.progress < 100) {
|
2017-09-20 15:17:38 +02:00
|
|
|
* window.setTimeout(() => this._increaseProgress(doneCallback), 10);
|
2015-09-16 08:31:43 -07:00
|
|
|
* } else {
|
|
|
|
* doneCallback();
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
* ```
|
2017-02-04 22:24:10 +00:00
|
|
|
*
|
2018-10-19 12:12:20 +01:00
|
|
|
* @publicApi
|
2015-04-17 18:06:24 +02:00
|
|
|
*/
|
2015-10-08 13:33:22 -07:00
|
|
|
export class NgZone {
|
2019-10-17 08:48:37 -07:00
|
|
|
readonly hasPendingMacrotasks: boolean = false;
|
2019-05-17 20:50:02 +09:00
|
|
|
readonly hasPendingMicrotasks: boolean = false;
|
2014-12-05 18:30:45 -08:00
|
|
|
|
2017-07-01 10:29:56 -07:00
|
|
|
/**
|
|
|
|
* Whether there are no outstanding microtasks or macrotasks.
|
|
|
|
*/
|
|
|
|
readonly isStable: boolean = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies when code enters Angular Zone. This gets fired first on VM Turn.
|
|
|
|
*/
|
|
|
|
readonly onUnstable: EventEmitter<any> = new EventEmitter(false);
|
|
|
|
|
|
|
|
/**
|
2017-06-01 14:45:49 -07:00
|
|
|
* Notifies when there is no more microtasks enqueued in the current VM Turn.
|
2017-07-01 10:29:56 -07:00
|
|
|
* This is a hint for Angular to do change detection, which may enqueue more microtasks.
|
|
|
|
* For this reason this event can fire multiple times per VM Turn.
|
|
|
|
*/
|
|
|
|
readonly onMicrotaskEmpty: EventEmitter<any> = new EventEmitter(false);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies when the last `onMicrotaskEmpty` has run and there are no more microtasks, which
|
|
|
|
* implies we are about to relinquish VM turn.
|
|
|
|
* This event gets called just once.
|
|
|
|
*/
|
|
|
|
readonly onStable: EventEmitter<any> = new EventEmitter(false);
|
2014-12-05 18:30:45 -08:00
|
|
|
|
2017-07-01 10:29:56 -07:00
|
|
|
/**
|
|
|
|
* Notifies that an error has been delivered.
|
|
|
|
*/
|
|
|
|
readonly onError: EventEmitter<any> = new EventEmitter(false);
|
2015-07-24 12:46:12 -07:00
|
|
|
|
2019-05-17 20:50:02 +09:00
|
|
|
|
2020-10-23 20:45:51 +09:00
|
|
|
constructor({
|
|
|
|
enableLongStackTrace = false,
|
|
|
|
shouldCoalesceEventChangeDetection = false,
|
|
|
|
shouldCoalesceRunChangeDetection = false
|
|
|
|
}) {
|
2016-10-06 15:23:37 -07:00
|
|
|
if (typeof Zone == 'undefined') {
|
2017-11-03 11:16:42 +13:00
|
|
|
throw new Error(`In this configuration Angular requires Zone.js`);
|
2016-10-06 15:23:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
Zone.assertZonePatched();
|
2017-07-01 10:29:56 -07:00
|
|
|
const self = this as any as NgZonePrivate;
|
|
|
|
self._nesting = 0;
|
2016-10-06 15:23:37 -07:00
|
|
|
|
2017-07-01 10:29:56 -07:00
|
|
|
self._outer = self._inner = Zone.current;
|
2016-10-06 15:23:37 -07:00
|
|
|
|
2017-04-27 11:44:14 -07:00
|
|
|
if ((Zone as any)['TaskTrackingZoneSpec']) {
|
|
|
|
self._inner = self._inner.fork(new ((Zone as any)['TaskTrackingZoneSpec'] as any));
|
|
|
|
}
|
|
|
|
|
2016-10-06 15:23:37 -07:00
|
|
|
if (enableLongStackTrace && (Zone as any)['longStackTraceZoneSpec']) {
|
2017-07-01 10:29:56 -07:00
|
|
|
self._inner = self._inner.fork((Zone as any)['longStackTraceZoneSpec']);
|
2016-10-06 15:23:37 -07:00
|
|
|
}
|
2020-10-23 20:45:51 +09:00
|
|
|
// if shouldCoalesceRunChangeDetection is true, all tasks including event tasks will be
|
|
|
|
// coalesced, so shouldCoalesceEventChangeDetection option is not necessary and can be skipped.
|
|
|
|
self.shouldCoalesceEventChangeDetection =
|
|
|
|
!shouldCoalesceRunChangeDetection && shouldCoalesceEventChangeDetection;
|
|
|
|
self.shouldCoalesceRunChangeDetection = shouldCoalesceRunChangeDetection;
|
2019-05-17 20:50:02 +09:00
|
|
|
self.lastRequestAnimationFrameId = -1;
|
|
|
|
self.nativeRequestAnimationFrame = getNativeRequestAnimationFrame().nativeRequestAnimationFrame;
|
2017-07-01 10:29:56 -07:00
|
|
|
forkInnerZoneWithAngularBehavior(self);
|
2015-07-10 15:12:21 -07:00
|
|
|
}
|
|
|
|
|
2020-04-13 16:40:21 -07:00
|
|
|
static isInAngularZone(): boolean {
|
|
|
|
return Zone.current.get('isAngularZone') === true;
|
|
|
|
}
|
2016-10-06 15:23:37 -07:00
|
|
|
|
|
|
|
static assertInAngularZone(): void {
|
|
|
|
if (!NgZone.isInAngularZone()) {
|
|
|
|
throw new Error('Expected to be in Angular Zone, but it is not!');
|
|
|
|
}
|
|
|
|
}
|
2017-07-01 10:29:56 -07:00
|
|
|
|
2016-10-06 15:23:37 -07:00
|
|
|
static assertNotInAngularZone(): void {
|
|
|
|
if (NgZone.isInAngularZone()) {
|
|
|
|
throw new Error('Expected to not be in Angular Zone, but it is!');
|
2016-02-25 14:24:17 -08:00
|
|
|
}
|
2016-10-06 15:23:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Executes the `fn` function synchronously within the Angular zone and returns value returned by
|
|
|
|
* the function.
|
|
|
|
*
|
|
|
|
* Running functions via `run` allows you to reenter Angular zone from a task that was executed
|
2017-04-25 02:15:33 +02:00
|
|
|
* outside of the Angular zone (typically started via {@link #runOutsideAngular}).
|
2016-10-06 15:23:37 -07:00
|
|
|
*
|
|
|
|
* Any future tasks or microtasks scheduled from within this function will continue executing from
|
|
|
|
* within the Angular zone.
|
|
|
|
*
|
|
|
|
* If a synchronous error happens it will be rethrown and not reported via `onError`.
|
|
|
|
*/
|
2017-07-13 15:36:11 -07:00
|
|
|
run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T {
|
2020-06-15 12:43:57 +09:00
|
|
|
return (this as any as NgZonePrivate)._inner.run(fn, applyThis, applyArgs);
|
2017-07-13 15:36:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Executes the `fn` function synchronously within the Angular zone as a task and returns value
|
|
|
|
* returned by the function.
|
|
|
|
*
|
|
|
|
* Running functions via `run` allows you to reenter Angular zone from a task that was executed
|
|
|
|
* outside of the Angular zone (typically started via {@link #runOutsideAngular}).
|
|
|
|
*
|
|
|
|
* Any future tasks or microtasks scheduled from within this function will continue executing from
|
|
|
|
* within the Angular zone.
|
|
|
|
*
|
|
|
|
* If a synchronous error happens it will be rethrown and not reported via `onError`.
|
|
|
|
*/
|
|
|
|
runTask<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[], name?: string): T {
|
|
|
|
const zone = (this as any as NgZonePrivate)._inner;
|
|
|
|
const task = zone.scheduleEventTask('NgZoneEvent: ' + name, fn, EMPTY_PAYLOAD, noop, noop);
|
|
|
|
try {
|
2020-06-15 12:43:57 +09:00
|
|
|
return zone.runTask(task, applyThis, applyArgs);
|
2017-07-13 15:36:11 -07:00
|
|
|
} finally {
|
|
|
|
zone.cancelTask(task);
|
|
|
|
}
|
|
|
|
}
|
2016-10-06 15:23:37 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Same as `run`, except that synchronous errors are caught and forwarded via `onError` and not
|
|
|
|
* rethrown.
|
|
|
|
*/
|
2017-07-13 15:36:11 -07:00
|
|
|
runGuarded<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T {
|
2020-06-15 12:43:57 +09:00
|
|
|
return (this as any as NgZonePrivate)._inner.runGuarded(fn, applyThis, applyArgs);
|
2017-07-13 15:36:11 -07:00
|
|
|
}
|
2016-10-06 15:23:37 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Executes the `fn` function synchronously in Angular's parent zone and returns value returned by
|
|
|
|
* the function.
|
|
|
|
*
|
2017-04-25 02:15:33 +02:00
|
|
|
* Running functions via {@link #runOutsideAngular} allows you to escape Angular's zone and do
|
|
|
|
* work that
|
2016-10-06 15:23:37 -07:00
|
|
|
* doesn't trigger Angular change-detection or is subject to Angular's error handling.
|
|
|
|
*
|
|
|
|
* Any future tasks or microtasks scheduled from within this function will continue executing from
|
|
|
|
* outside of the Angular zone.
|
|
|
|
*
|
2017-04-25 02:15:33 +02:00
|
|
|
* Use {@link #run} to reenter the Angular zone and do work that updates the application model.
|
2016-10-06 15:23:37 -07:00
|
|
|
*/
|
2017-07-13 15:36:11 -07:00
|
|
|
runOutsideAngular<T>(fn: (...args: any[]) => T): T {
|
2020-06-15 12:43:57 +09:00
|
|
|
return (this as any as NgZonePrivate)._outer.run(fn);
|
2017-07-13 15:36:11 -07:00
|
|
|
}
|
2017-07-01 10:29:56 -07:00
|
|
|
}
|
2015-10-19 14:41:15 -07:00
|
|
|
|
2017-07-13 15:36:11 -07:00
|
|
|
const EMPTY_PAYLOAD = {};
|
2019-10-17 08:48:37 -07:00
|
|
|
|
2017-07-01 10:29:56 -07:00
|
|
|
interface NgZonePrivate extends NgZone {
|
|
|
|
_outer: Zone;
|
|
|
|
_inner: Zone;
|
|
|
|
_nesting: number;
|
2019-05-17 20:50:02 +09:00
|
|
|
_hasPendingMicrotasks: boolean;
|
2015-10-19 14:41:15 -07:00
|
|
|
|
2019-10-17 08:48:37 -07:00
|
|
|
hasPendingMacrotasks: boolean;
|
2019-05-17 20:50:02 +09:00
|
|
|
hasPendingMicrotasks: boolean;
|
|
|
|
lastRequestAnimationFrameId: number;
|
2017-07-01 10:29:56 -07:00
|
|
|
isStable: boolean;
|
2020-10-23 20:45:51 +09:00
|
|
|
/**
|
|
|
|
* Optionally specify coalescing event change detections or not.
|
|
|
|
* Consider the following case.
|
|
|
|
*
|
|
|
|
* <div (click)="doSomething()">
|
|
|
|
* <button (click)="doSomethingElse()"></button>
|
|
|
|
* </div>
|
|
|
|
*
|
|
|
|
* When button is clicked, because of the event bubbling, both
|
|
|
|
* event handlers will be called and 2 change detections will be
|
|
|
|
* triggered. We can coalesce such kind of events to trigger
|
|
|
|
* change detection only once.
|
|
|
|
*
|
|
|
|
* By default, this option will be false. So the events will not be
|
|
|
|
* coalesced and the change detection will be triggered multiple times.
|
|
|
|
* And if this option be set to true, the change detection will be
|
|
|
|
* triggered async by scheduling it in an animation frame. So in the case above,
|
|
|
|
* the change detection will only be trigged once.
|
|
|
|
*/
|
2019-05-17 20:50:02 +09:00
|
|
|
shouldCoalesceEventChangeDetection: boolean;
|
2020-10-23 20:45:51 +09:00
|
|
|
/**
|
|
|
|
* Optionally specify if `NgZone#run()` method invocations should be coalesced
|
|
|
|
* into a single change detection.
|
|
|
|
*
|
|
|
|
* Consider the following case.
|
|
|
|
*
|
|
|
|
* for (let i = 0; i < 10; i ++) {
|
|
|
|
* ngZone.run(() => {
|
|
|
|
* // do something
|
|
|
|
* });
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* This case triggers the change detection multiple times.
|
|
|
|
* With ngZoneRunCoalescing options, all change detections in an event loops trigger only once.
|
|
|
|
* In addition, the change detection executes in requestAnimation.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
shouldCoalesceRunChangeDetection: boolean;
|
|
|
|
|
2019-05-17 20:50:02 +09:00
|
|
|
nativeRequestAnimationFrame: (callback: FrameRequestCallback) => number;
|
fix(core): should fake a top event task when coalescing events to prevent draining microTaskQueue too early. (#36841)
Close #36839.
This is a known issue of zone.js,
```
(window as any)[(Zone as any).__symbol__('setTimeout')](() => {
let log = '';
button.addEventListener('click', () => {
Zone.current.scheduleMicroTask('test', () => log += 'microtask;');
log += 'click;';
});
button.click();
expect(log).toEqual('click;microtask;');
done();
});
```
Since in this case, we use native `setTimeout` which is not a ZoneTask,
so zone.js consider the button click handler as the top Task then drain the
microTaskQueue after the click at once, which is not correct(too early).
This case was an edge case and not reported by the users, until we have the
new option ngZoneEventCoalescing, since the event coalescing will happen
in native requestAnimationFrame, so it will not be a ZoneTask, and zone.js will
consider any Task happen in the change detection stage as the top task, and if
there are any microTasks(such as Promise.then) happen in the process, it may be
drained earlier than it should be, so to prevent this situation, we need to schedule
a fake event task and run the change detection check in this fake event task,
so the Task happen in the change detection stage will not be
considered as top ZoneTask.
PR Close #36841
2020-04-30 10:51:01 +09:00
|
|
|
|
2020-10-23 20:45:51 +09:00
|
|
|
// Cache a "fake" top eventTask so you don't need to schedule a new task every
|
|
|
|
// time you run a `checkStable`.
|
fix(core): should fake a top event task when coalescing events to prevent draining microTaskQueue too early. (#36841)
Close #36839.
This is a known issue of zone.js,
```
(window as any)[(Zone as any).__symbol__('setTimeout')](() => {
let log = '';
button.addEventListener('click', () => {
Zone.current.scheduleMicroTask('test', () => log += 'microtask;');
log += 'click;';
});
button.click();
expect(log).toEqual('click;microtask;');
done();
});
```
Since in this case, we use native `setTimeout` which is not a ZoneTask,
so zone.js consider the button click handler as the top Task then drain the
microTaskQueue after the click at once, which is not correct(too early).
This case was an edge case and not reported by the users, until we have the
new option ngZoneEventCoalescing, since the event coalescing will happen
in native requestAnimationFrame, so it will not be a ZoneTask, and zone.js will
consider any Task happen in the change detection stage as the top task, and if
there are any microTasks(such as Promise.then) happen in the process, it may be
drained earlier than it should be, so to prevent this situation, we need to schedule
a fake event task and run the change detection check in this fake event task,
so the Task happen in the change detection stage will not be
considered as top ZoneTask.
PR Close #36841
2020-04-30 10:51:01 +09:00
|
|
|
fakeTopEventTask: Task;
|
2017-07-01 10:29:56 -07:00
|
|
|
}
|
2015-10-19 14:41:15 -07:00
|
|
|
|
2017-07-01 10:29:56 -07:00
|
|
|
function checkStable(zone: NgZonePrivate) {
|
|
|
|
if (zone._nesting == 0 && !zone.hasPendingMicrotasks && !zone.isStable) {
|
|
|
|
try {
|
|
|
|
zone._nesting++;
|
|
|
|
zone.onMicrotaskEmpty.emit(null);
|
|
|
|
} finally {
|
|
|
|
zone._nesting--;
|
|
|
|
if (!zone.hasPendingMicrotasks) {
|
|
|
|
try {
|
|
|
|
zone.runOutsideAngular(() => zone.onStable.emit(null));
|
|
|
|
} finally {
|
|
|
|
zone.isStable = true;
|
2016-10-06 15:23:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-01 10:29:56 -07:00
|
|
|
}
|
2014-12-05 18:30:45 -08:00
|
|
|
|
2019-05-17 20:50:02 +09:00
|
|
|
function delayChangeDetectionForEvents(zone: NgZonePrivate) {
|
|
|
|
if (zone.lastRequestAnimationFrameId !== -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
zone.lastRequestAnimationFrameId = zone.nativeRequestAnimationFrame.call(global, () => {
|
fix(core): should fake a top event task when coalescing events to prevent draining microTaskQueue too early. (#36841)
Close #36839.
This is a known issue of zone.js,
```
(window as any)[(Zone as any).__symbol__('setTimeout')](() => {
let log = '';
button.addEventListener('click', () => {
Zone.current.scheduleMicroTask('test', () => log += 'microtask;');
log += 'click;';
});
button.click();
expect(log).toEqual('click;microtask;');
done();
});
```
Since in this case, we use native `setTimeout` which is not a ZoneTask,
so zone.js consider the button click handler as the top Task then drain the
microTaskQueue after the click at once, which is not correct(too early).
This case was an edge case and not reported by the users, until we have the
new option ngZoneEventCoalescing, since the event coalescing will happen
in native requestAnimationFrame, so it will not be a ZoneTask, and zone.js will
consider any Task happen in the change detection stage as the top task, and if
there are any microTasks(such as Promise.then) happen in the process, it may be
drained earlier than it should be, so to prevent this situation, we need to schedule
a fake event task and run the change detection check in this fake event task,
so the Task happen in the change detection stage will not be
considered as top ZoneTask.
PR Close #36841
2020-04-30 10:51:01 +09:00
|
|
|
// This is a work around for https://github.com/angular/angular/issues/36839.
|
|
|
|
// The core issue is that when event coalescing is enabled it is possible for microtasks
|
|
|
|
// to get flushed too early (As is the case with `Promise.then`) between the
|
|
|
|
// coalescing eventTasks.
|
|
|
|
//
|
|
|
|
// To workaround this we schedule a "fake" eventTask before we process the
|
|
|
|
// coalescing eventTasks. The benefit of this is that the "fake" container eventTask
|
|
|
|
// will prevent the microtasks queue from getting drained in between the coalescing
|
|
|
|
// eventTask execution.
|
|
|
|
if (!zone.fakeTopEventTask) {
|
|
|
|
zone.fakeTopEventTask = Zone.root.scheduleEventTask('fakeTopEventTask', () => {
|
|
|
|
zone.lastRequestAnimationFrameId = -1;
|
|
|
|
updateMicroTaskStatus(zone);
|
|
|
|
checkStable(zone);
|
|
|
|
}, undefined, () => {}, () => {});
|
|
|
|
}
|
|
|
|
zone.fakeTopEventTask.invoke();
|
2019-05-17 20:50:02 +09:00
|
|
|
});
|
|
|
|
updateMicroTaskStatus(zone);
|
|
|
|
}
|
|
|
|
|
2017-07-01 10:29:56 -07:00
|
|
|
function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {
|
2020-04-13 16:40:21 -07:00
|
|
|
const delayChangeDetectionForEventsDelegate = () => {
|
|
|
|
delayChangeDetectionForEvents(zone);
|
|
|
|
};
|
2017-07-01 10:29:56 -07:00
|
|
|
zone._inner = zone._inner.fork({
|
|
|
|
name: 'angular',
|
2020-10-23 20:45:51 +09:00
|
|
|
properties: <any>{'isAngularZone': true},
|
2020-04-13 16:40:21 -07:00
|
|
|
onInvokeTask:
|
|
|
|
(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any,
|
|
|
|
applyArgs: any): any => {
|
|
|
|
try {
|
|
|
|
onEnter(zone);
|
|
|
|
return delegate.invokeTask(target, task, applyThis, applyArgs);
|
|
|
|
} finally {
|
2020-10-23 20:45:51 +09:00
|
|
|
if ((zone.shouldCoalesceEventChangeDetection && task.type === 'eventTask') ||
|
|
|
|
zone.shouldCoalesceRunChangeDetection) {
|
|
|
|
delayChangeDetectionForEventsDelegate();
|
2020-04-13 16:40:21 -07:00
|
|
|
}
|
|
|
|
onLeave(zone);
|
|
|
|
}
|
|
|
|
},
|
2016-10-06 15:23:37 -07:00
|
|
|
|
2020-04-13 16:40:21 -07:00
|
|
|
onInvoke:
|
|
|
|
(delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function, applyThis: any,
|
|
|
|
applyArgs?: any[], source?: string): any => {
|
|
|
|
try {
|
|
|
|
onEnter(zone);
|
|
|
|
return delegate.invoke(target, callback, applyThis, applyArgs, source);
|
|
|
|
} finally {
|
2020-10-23 20:45:51 +09:00
|
|
|
if (zone.shouldCoalesceRunChangeDetection) {
|
|
|
|
delayChangeDetectionForEventsDelegate();
|
|
|
|
}
|
2020-04-13 16:40:21 -07:00
|
|
|
onLeave(zone);
|
|
|
|
}
|
|
|
|
},
|
2017-07-01 10:29:56 -07:00
|
|
|
|
|
|
|
onHasTask:
|
|
|
|
(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {
|
|
|
|
delegate.hasTask(target, hasTaskState);
|
|
|
|
if (current === target) {
|
|
|
|
// We are only interested in hasTask events which originate from our zone
|
|
|
|
// (A child hasTask event is not interesting to us)
|
|
|
|
if (hasTaskState.change == 'microTask') {
|
2019-05-17 20:50:02 +09:00
|
|
|
zone._hasPendingMicrotasks = hasTaskState.microTask;
|
|
|
|
updateMicroTaskStatus(zone);
|
2017-07-01 10:29:56 -07:00
|
|
|
checkStable(zone);
|
|
|
|
} else if (hasTaskState.change == 'macroTask') {
|
|
|
|
zone.hasPendingMacrotasks = hasTaskState.macroTask;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2016-10-06 15:23:37 -07:00
|
|
|
|
2017-07-01 10:29:56 -07:00
|
|
|
onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
|
|
|
|
delegate.handleError(target, error);
|
2017-07-20 22:34:19 -07:00
|
|
|
zone.runOutsideAngular(() => zone.onError.emit(error));
|
2017-07-01 10:29:56 -07:00
|
|
|
return false;
|
2016-10-06 15:23:37 -07:00
|
|
|
}
|
2017-07-01 10:29:56 -07:00
|
|
|
});
|
|
|
|
}
|
2016-10-06 15:23:37 -07:00
|
|
|
|
2019-05-17 20:50:02 +09:00
|
|
|
function updateMicroTaskStatus(zone: NgZonePrivate) {
|
|
|
|
if (zone._hasPendingMicrotasks ||
|
2020-10-23 20:45:51 +09:00
|
|
|
((zone.shouldCoalesceEventChangeDetection || zone.shouldCoalesceRunChangeDetection) &&
|
|
|
|
zone.lastRequestAnimationFrameId !== -1)) {
|
2019-05-17 20:50:02 +09:00
|
|
|
zone.hasPendingMicrotasks = true;
|
|
|
|
} else {
|
|
|
|
zone.hasPendingMicrotasks = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-01 10:29:56 -07:00
|
|
|
function onEnter(zone: NgZonePrivate) {
|
|
|
|
zone._nesting++;
|
|
|
|
if (zone.isStable) {
|
|
|
|
zone.isStable = false;
|
|
|
|
zone.onUnstable.emit(null);
|
2016-10-06 15:23:37 -07:00
|
|
|
}
|
2017-07-01 10:29:56 -07:00
|
|
|
}
|
2016-10-06 15:23:37 -07:00
|
|
|
|
2017-07-01 10:29:56 -07:00
|
|
|
function onLeave(zone: NgZonePrivate) {
|
|
|
|
zone._nesting--;
|
|
|
|
checkStable(zone);
|
2015-04-17 18:06:24 +02:00
|
|
|
}
|
2017-06-01 14:45:49 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides a noop implementation of `NgZone` which does nothing. This zone requires explicit calls
|
|
|
|
* to framework to perform rendering.
|
|
|
|
*/
|
|
|
|
export class NoopNgZone implements NgZone {
|
|
|
|
readonly hasPendingMicrotasks: boolean = false;
|
|
|
|
readonly hasPendingMacrotasks: boolean = false;
|
|
|
|
readonly isStable: boolean = true;
|
|
|
|
readonly onUnstable: EventEmitter<any> = new EventEmitter();
|
|
|
|
readonly onMicrotaskEmpty: EventEmitter<any> = new EventEmitter();
|
|
|
|
readonly onStable: EventEmitter<any> = new EventEmitter();
|
|
|
|
readonly onError: EventEmitter<any> = new EventEmitter();
|
|
|
|
|
2020-06-15 12:43:57 +09:00
|
|
|
run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any): T {
|
2019-08-09 16:42:31 +09:00
|
|
|
return fn.apply(applyThis, applyArgs);
|
|
|
|
}
|
2017-06-01 14:45:49 -07:00
|
|
|
|
2020-06-15 12:43:57 +09:00
|
|
|
runGuarded<T>(fn: (...args: any[]) => any, applyThis?: any, applyArgs?: any): T {
|
2019-08-09 16:42:31 +09:00
|
|
|
return fn.apply(applyThis, applyArgs);
|
|
|
|
}
|
2017-06-01 14:45:49 -07:00
|
|
|
|
2020-06-15 12:43:57 +09:00
|
|
|
runOutsideAngular<T>(fn: (...args: any[]) => T): T {
|
2020-04-13 16:40:21 -07:00
|
|
|
return fn();
|
|
|
|
}
|
2017-06-01 14:45:49 -07:00
|
|
|
|
2020-06-15 12:43:57 +09:00
|
|
|
runTask<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any, name?: string): T {
|
2019-08-09 16:42:31 +09:00
|
|
|
return fn.apply(applyThis, applyArgs);
|
|
|
|
}
|
2017-06-01 14:45:49 -07:00
|
|
|
}
|