Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Async Stack Tagging API support #46693

Closed
wants to merge 2 commits into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion goldens/size-tracking/aio-payloads.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"aio-local": {
"uncompressed": {
"runtime": 4325,
"main": 459842,
"main": 460353,
"polyfills": 33922,
"styles": 73640,
"light-theme": 78276,
Expand Down
8 changes: 4 additions & 4 deletions goldens/size-tracking/integration-payloads.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"cli-hello-world": {
"uncompressed": {
"runtime": 1083,
"main": 125325,
"main": 125830,
"polyfills": 33824
}
},
Expand All @@ -19,14 +19,14 @@
"cli-hello-world-ivy-compat": {
"uncompressed": {
"runtime": 1102,
"main": 132311,
"main": 132816,
"polyfills": 33957
}
},
"cli-hello-world-ivy-i18n": {
"uncompressed": {
"runtime": 926,
"main": 124982,
"main": 125487,
"polyfills": 35252
}
},
Expand Down Expand Up @@ -55,7 +55,7 @@
"standalone-bootstrap": {
"uncompressed": {
"runtime": 1090,
"main": 83013,
"main": 83515,
"polyfills": 33945
}
},
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/zone/ng_zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ export class NgZone {

self._outer = self._inner = Zone.current;

if ((Zone as any)['AsyncStackTaggingZoneSpec']) {
const AsyncStackTaggingZoneSpec = (Zone as any)['AsyncStackTaggingZoneSpec'];
self._inner = self._inner.fork(new AsyncStackTaggingZoneSpec('Angular'));
}

if ((Zone as any)['TaskTrackingZoneSpec']) {
self._inner = self._inner.fork(new ((Zone as any)['TaskTrackingZoneSpec'] as any));
}
Expand Down
3 changes: 3 additions & 0 deletions packages/zone.js/bundles.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ BUNDLES_ENTRY_POINTS = {
"async-test": {
"entrypoint": _DIR + "testing/async-testing",
},
"async-stack-tagging": {
"entrypoint": _DIR + "zone-spec/async-stack-tagging",
},
"fake-async-test": {
"entrypoint": _DIR + "testing/fake-async",
},
Expand Down
2 changes: 2 additions & 0 deletions packages/zone.js/dist/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ js_library(
filegroup(
name = "dist_bundle_group",
srcs = [
":async-stack-tagging.js",
":async-stack-tagging.min.js",
":async-test.js",
":async-test.min.js",
":fake-async-test.js",
Expand Down
1 change: 1 addition & 0 deletions packages/zone.js/dist/tools.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ ES5_BUNDLES = [
"zone-node",
"zone-testing-node-bundle",
"async-test",
"async-stack-tagging",
"fake-async-test",
"long-stack-trace-zone",
"proxy",
Expand Down
67 changes: 67 additions & 0 deletions packages/zone.js/lib/zone-spec/async-stack-tagging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @license
* Copyright Google LLC 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
*/

interface Console {
scheduleAsyncTask(name: string, recurring?: boolean): number;
startAsyncTask(task: number): void;
finishAsyncTask(task: number): void;
cancelAsyncTask(task: number): void;
}

interface Task {
asyncId?: number;
}

class AsyncStackTaggingZoneSpec implements ZoneSpec {
scheduleAsyncTask: Console['scheduleAsyncTask'];
startAsyncTask: Console['startAsyncTask'];
finishAsyncTask: Console['finishAsyncTask'];
cancelAsyncTask: Console['finishAsyncTask'];

constructor(namePrefix: string, consoleAsyncStackTaggingImpl: Console = console) {
this.name = 'asyncStackTagging for ' + namePrefix;
this.scheduleAsyncTask = consoleAsyncStackTaggingImpl?.scheduleAsyncTask ?? (() => {});
this.startAsyncTask = consoleAsyncStackTaggingImpl?.startAsyncTask ?? (() => {});
this.finishAsyncTask = consoleAsyncStackTaggingImpl?.finishAsyncTask ?? (() => {});
this.cancelAsyncTask = consoleAsyncStackTaggingImpl?.cancelAsyncTask ?? (() => {});
}

// ZoneSpec implementation below.

name: string;

onScheduleTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): Task {
task.asyncId = this.scheduleAsyncTask(
task.source || task.type, task.data?.isPeriodic || task.type === 'eventTask');
return delegate.scheduleTask(target, task);
}

onInvokeTask(
delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task, applyThis: any,
applyArgs?: any[]) {
task.asyncId && this.startAsyncTask(task.asyncId);
try {
return delegate.invokeTask(targetZone, task, applyThis, applyArgs);
} finally {
task.asyncId && this.finishAsyncTask(task.asyncId);
if (task.type !== 'eventTask' && !task.data?.isPeriodic) {
task.asyncId = undefined;
}
}
}

onCancelTask(delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task) {
task.asyncId && this.cancelAsyncTask(task.asyncId);
task.asyncId = undefined;
return delegate.cancelTask(targetZone, task);
}
}

// Export the class so that new instances can be created with proper
// constructor params.
(Zone as any)['AsyncStackTaggingZoneSpec'] = AsyncStackTaggingZoneSpec;
2 changes: 2 additions & 0 deletions packages/zone.js/plugins/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package(default_visibility = ["//visibility:public"])
filegroup(
name = "plugin_bundle_group",
srcs = [
"//packages/zone.js/plugins:async-stack-tagging.min/package.json",
"//packages/zone.js/plugins:async-stack-tagging/package.json",
"//packages/zone.js/plugins:async-test.min/package.json",
"//packages/zone.js/plugins:async-test/package.json",
"//packages/zone.js/plugins:fake-async-test.min/package.json",
Expand Down
7 changes: 7 additions & 0 deletions packages/zone.js/plugins/async-stack-tagging.min/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "zone.js/async-stack-tagging.min",
"main": "../../bundles/async-stack-tagging.umd.min.js",
"fesm2015": "../../fesm2015/async-stack-tagging.min.js",
"es2015": "../../fesm2015/async-stack-tagging.min.js",
"module": "../../fesm2015/async-stack-tagging.min.js"
}
7 changes: 7 additions & 0 deletions packages/zone.js/plugins/async-stack-tagging/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "zone.js/async-stack-tagging",
"main": "../../bundles/async-stack-tagging.umd.js",
"fesm2015": "../../fesm2015/async-stack-tagging.js",
"es2015": "../../fesm2015/async-stack-tagging.js",
"module": "../../fesm2015/async-stack-tagging.js"
}
2 changes: 2 additions & 0 deletions packages/zone.js/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ts_library(
exclude = [
"common/Error.spec.ts",
"common/promise-disable-wrap-uncaught-promise-rejection.spec.ts",
"zone-spec/async-tagging-console.spec.ts",
],
),
deps = [
Expand Down Expand Up @@ -264,6 +265,7 @@ test_srcs = glob(
"jasmine-patch.spec.ts",
"common_tests.ts",
"browser_entry_point.ts",
"zone-spec/async-tagging-console.spec.ts",
]

test_deps = [
Expand Down
1 change: 1 addition & 0 deletions packages/zone.js/test/browser-zone-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import '../lib/browser/webapis-media-query';
import '../lib/testing/zone-testing';
import '../lib/zone-spec/task-tracking';
import '../lib/zone-spec/wtf';
import '../lib/zone-spec/async-stack-tagging';
import '../lib/extra/cordova';
import '../lib/testing/promise-testing';
import '../lib/testing/async-testing';
Expand Down
2 changes: 1 addition & 1 deletion packages/zone.js/test/browser_entry_point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import '../lib/common/error-rewrite';

// import 'core-js/features/set';
// import 'core-js/features/map';
// List all tests here:
Expand All @@ -30,3 +29,4 @@ import './jasmine-patch.spec';
import './browser/messageport.spec';
import './extra/cordova.spec';
import './browser/queue-microtask.spec';
import './zone-spec/async-tagging-console.spec';
1 change: 1 addition & 0 deletions packages/zone.js/test/karma_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def karma_test(name, env_srcs, env_deps, env_entry_point, test_srcs, test_deps,
"//packages/zone.js/bundles:zone-patch-resize-observer.umd.js",
"//packages/zone.js/bundles:zone-patch-message-port.umd.js",
"//packages/zone.js/bundles:zone-patch-user-media.umd.js",
"//packages/zone.js/bundles:async-stack-tagging.umd.js",
":" + name + "_rollup.umd",
]

Expand Down
13 changes: 9 additions & 4 deletions packages/zone.js/test/npm_package/npm_package.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ function checkInSubFolder(subFolder: string, testFn: Function) {
}

describe('Zone.js npm_package', () => {
beforeEach(
() => {shx.cd(
path.dirname(require.resolve('angular/packages/zone.js/npm_package/package.json')))});
beforeEach(() => {
shx.cd(path.dirname(require.resolve('angular/packages/zone.js/npm_package/package.json')));
});
describe('misc root files', () => {
describe('README.md', () => {
it('should have a README.md file with basic info', () => {
Expand Down Expand Up @@ -112,10 +112,11 @@ describe('Zone.js npm_package', () => {
});
});


describe('plugins folder check', () => {
it('should contain all plugin folders in ./plugins', () => {
const expected = [
'async-stack-tagging',
'async-stack-tagging.min',
'async-test',
'async-test.min',
'fake-async-test',
Expand Down Expand Up @@ -196,6 +197,8 @@ describe('Zone.js npm_package', () => {
describe('bundles file list', () => {
it('should contain all files', () => {
const expected = [
'async-stack-tagging.js',
'async-stack-tagging.min.js',
'async-test.js',
'async-test.min.js',
'fake-async-test.js',
Expand Down Expand Up @@ -290,6 +293,8 @@ describe('Zone.js npm_package', () => {
it('should contain all original folders in /dist', () => {
const list = shx.ls('./dist').stdout.split('\n').sort().slice(1);
const expected = [
'async-stack-tagging.js',
'async-stack-tagging.min.js',
'async-test.js',
'async-test.min.js',
'fake-async-test.js',
Expand Down
134 changes: 134 additions & 0 deletions packages/zone.js/test/zone-spec/async-tagging-console.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* @license
* Copyright Google LLC 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
*/

import {ifEnvSupports, ifEnvSupportsWithDone} from '../test-util';

describe('AsyncTaggingConsoleTest', () => {
const AsyncStackTaggingZoneSpec = (Zone as any)['AsyncStackTaggingZoneSpec'];

describe('should call console async stack tagging API', () => {
let idx = 1;
const scheduleAsyncTaskSpy = jasmine.createSpy('scheduleAsyncTask').and.callFake(() => {
return idx++;
});
const startAsyncTaskSpy = jasmine.createSpy('startAsyncTask');
const finishAsyncTaskSpy = jasmine.createSpy('finishAsyncTask');
const cancelAsyncTaskSpy = jasmine.createSpy('cancelAsyncTask');
let asyncStackTaggingZone: Zone;

beforeEach(() => {
scheduleAsyncTaskSpy.calls.reset();
startAsyncTaskSpy.calls.reset();
finishAsyncTaskSpy.calls.reset();
cancelAsyncTaskSpy.calls.reset();
asyncStackTaggingZone = Zone.current.fork(new AsyncStackTaggingZoneSpec('test', {
scheduleAsyncTask: scheduleAsyncTaskSpy,
startAsyncTask: startAsyncTaskSpy,
finishAsyncTask: finishAsyncTaskSpy,
cancelAsyncTask: cancelAsyncTaskSpy,
}));
});
it('setTimeout', (done: DoneFn) => {
asyncStackTaggingZone.run(() => {
setTimeout(() => {});
});
setTimeout(() => {
expect(scheduleAsyncTaskSpy).toHaveBeenCalledWith('setTimeout', false);
expect(startAsyncTaskSpy.calls.count()).toBe(1);
expect(finishAsyncTaskSpy.calls.count()).toBe(1);
done();
});
});
it('clearTimeout', (done: DoneFn) => {
asyncStackTaggingZone.run(() => {
const id = setTimeout(() => {});
clearTimeout(id);
});
setTimeout(() => {
expect(scheduleAsyncTaskSpy).toHaveBeenCalledWith('setTimeout', false);
expect(startAsyncTaskSpy).not.toHaveBeenCalled();
expect(finishAsyncTaskSpy).not.toHaveBeenCalled();
expect(cancelAsyncTaskSpy.calls.count()).toBe(1);
done();
});
});
it('setInterval', (done: DoneFn) => {
asyncStackTaggingZone.run(() => {
let count = 0;
const id = setInterval(() => {
count++;
if (count === 2) {
clearInterval(id);
}
}, 10);
});
setTimeout(() => {
expect(scheduleAsyncTaskSpy).toHaveBeenCalledWith('setInterval', true);
expect(startAsyncTaskSpy.calls.count()).toBe(2);
expect(finishAsyncTaskSpy.calls.count()).toBe(1);
expect(cancelAsyncTaskSpy.calls.count()).toBe(1);
done();
}, 50);
});
it('Promise', (done: DoneFn) => {
asyncStackTaggingZone.run(() => {
Promise.resolve(1).then(() => {});
});
setTimeout(() => {
expect(scheduleAsyncTaskSpy).toHaveBeenCalledWith('Promise.then', false);
expect(startAsyncTaskSpy.calls.count()).toBe(1);
expect(finishAsyncTaskSpy.calls.count()).toBe(1);
done();
});
});

it('XMLHttpRequest', ifEnvSupportsWithDone('XMLHttpRequest', (done: DoneFn) => {
asyncStackTaggingZone.run(() => {
const req = new XMLHttpRequest();
req.onload = () => {
Zone.root.run(() => {
setTimeout(() => {
expect(scheduleAsyncTaskSpy.calls.all()[0].args).toEqual([
'XMLHttpRequest.addEventListener:load',
true,
]);
expect(scheduleAsyncTaskSpy.calls.all()[1].args).toEqual([
'XMLHttpRequest.send',
false,
]);
expect(startAsyncTaskSpy.calls.count()).toBe(2);
expect(finishAsyncTaskSpy.calls.count()).toBe(2);
done();
});
});
};
req.open('get', '/', true);
req.send();
});
}));

it('button click', ifEnvSupports('document', () => {
asyncStackTaggingZone.run(() => {
const button = document.createElement('button');
const clickEvent = document.createEvent('Event');
clickEvent.initEvent('click', true, true);
document.body.appendChild(button);
const handler = () => {};
button.addEventListener('click', handler);
button.dispatchEvent(clickEvent);
button.dispatchEvent(clickEvent);
button.removeEventListener('click', handler);
expect(scheduleAsyncTaskSpy)
.toHaveBeenCalledWith('HTMLButtonElement.addEventListener:click', true);
expect(startAsyncTaskSpy.calls.count()).toBe(2);
expect(finishAsyncTaskSpy.calls.count()).toBe(2);
expect(cancelAsyncTaskSpy.calls.count()).toBe(1);
});
}));
});
});