-
Notifications
You must be signed in to change notification settings - Fork 25.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(zone.js): add AsyncStackTaggingZoneSpec implementation (#46693)
Chrome has an experimental API to improve the debug experience of the async tasks. The details can be found here https://bugs.chromium.org/p/chromium/issues/detail?id=332624#c29 This commit add the `async stack tagging` support in the `zone.js`. User can `import 'zone.js/plugins/async-stack-tagging';` to enable this feature. PR Close #46693
- Loading branch information
1 parent
b6a950c
commit 848a009
Showing
13 changed files
with
237 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
packages/zone.js/plugins/async-stack-tagging.min/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
packages/zone.js/test/zone-spec/async-tagging-console.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
})); | ||
}); | ||
}); |