build: move zone.js to angular repo (#30962)

PR Close #30962
This commit is contained in:
JiaLiPassion 2019-06-01 00:56:07 +09:00 committed by Kara Erickson
parent 7b3bcc23af
commit 5eb7426216
271 changed files with 30890 additions and 19 deletions

View File

@ -641,6 +641,17 @@ jobs:
name: "Running Material unit tests"
command: ./scripts/ci/run_angular_material_unit_tests.sh
test_zonejs:
<<: *job_defaults
steps:
- *attach_workspace
- *init_environment
# Install
- run: yarn --cwd packages/zone.js install --frozen-lockfile --non-interactive
# Run zone.js tools tests
- run: yarn --cwd packages/zone.js promisetest
- run: yarn --cwd packages/zone.js promisefinallytest
workflows:
version: 2
default_workflow:
@ -725,6 +736,9 @@ workflows:
- material-unit-tests:
requires:
- build-ivy-npm-packages
- test_zonejs:
requires:
- setup
saucelabs_tests:
jobs:

View File

@ -74,6 +74,7 @@ karma_web_test(
"//packages/core/test:test_lib",
"//packages/forms/test:test_lib",
"//packages/http/test:test_lib",
"//packages/zone.js/test:karma_jasmine_test_ci",
# "//packages/router/test:test_lib",
# //packages/router/test:test_lib fails with:
# IE 11.0.0 (Windows 8.1.0.0) bootstrap should restore the scrolling position FAILED

View File

@ -42,6 +42,7 @@
"@schematics/angular": "^8.0.0-beta.15",
"@types/angular": "^1.6.47",
"@types/base64-js": "1.2.5",
"@types/bluebird": "^3.5.27",
"@types/chai": "^4.1.2",
"@types/chokidar": "^1.7.5",
"@types/convert-source-map": "^1.5.1",
@ -57,6 +58,7 @@
"@types/selenium-webdriver": "3.0.7",
"@types/shelljs": "^0.7.8",
"@types/source-map": "^0.5.1",
"@types/systemjs": "0.19.32",
"@types/yargs": "^11.1.1",
"@webcomponents/custom-elements": "^1.0.4",
"angular": "npm:angular@1.7",
@ -66,12 +68,14 @@
"angular-mocks-1.5": "npm:angular-mocks@1.5",
"angular-mocks-1.6": "npm:angular-mocks@1.6",
"base64-js": "1.2.1",
"bluebird": "^3.5.5",
"brotli": "^1.3.2",
"canonical-path": "1.0.0",
"chai": "^4.1.2",
"chalk": "^2.3.1",
"chokidar": "^2.1.1",
"convert-source-map": "^1.5.1",
"core-js": "^2.4.1",
"dependency-graph": "^0.7.2",
"diff": "^3.5.0",
"domino": "2.1.2",
@ -88,6 +92,7 @@
"minimist": "1.2.0",
"mock-fs": "^4.10.1",
"node-uuid": "1.4.8",
"nodejs-websocket": "^1.7.2",
"protractor": "^5.4.2",
"reflect-metadata": "^0.1.3",
"rollup": "^1.1.0",
@ -121,7 +126,6 @@
"@bazel/buildifier": "^0.25.1",
"@bazel/ibazel": "~0.9.0",
"@types/minimist": "^1.2.0",
"@types/systemjs": "0.19.32",
"browserstacktunnel-wrapper": "2.0.1",
"check-side-effects": "0.0.21",
"clang-format": "1.0.41",
@ -129,14 +133,13 @@
"cldr-data-downloader": "0.3.2",
"cldrjs": "0.5.0",
"conventional-changelog": "^2.0.3",
"core-js": "^2.4.1",
"cors": "2.8.4",
"entities": "1.1.1",
"firebase-tools": "5.1.1",
"firefox-profile": "1.0.3",
"glob": "7.1.2",
"gulp": "3.9.1",
"gulp-clang-format": "1.0.23",
"gulp-clang-format": "1.0.27",
"gulp-connect": "5.0.0",
"gulp-conventional-changelog": "^2.0.3",
"gulp-filter": "^5.1.0",

View File

@ -42,5 +42,6 @@
"examples/**/main.ts",
"platform-server/integrationtest",
"router/test/aot_ngsummary_test",
"zone.js"
]
}

View File

@ -0,0 +1,47 @@
load("@build_bazel_rules_nodejs//:defs.bzl", "npm_package", "rollup_bundle")
load("@npm_bazel_jasmine//:index.bzl", "jasmine_node_test")
load("@npm_bazel_typescript//:defs.bzl", "ts_library")
load("//packages/zone.js:bundles.bzl", "ES2015_BUNDLES", "ES5_BUNDLES", "ES5_GLOBAL_BUNDLES")
exports_files([
"tsconfig.json",
])
genrule(
name = "LICENSE_copy",
srcs = ["//:LICENSE"],
outs = ["LICENSE"],
cmd = "cp $< $@",
)
genrule(
name = "LICENSE_wrapped",
srcs = ["//:LICENSE"],
outs = ["LICENSE.wrapped"],
cmd = "(echo '/**\n @license' && cat $< && echo '*/') > $@",
)
npm_package(
name = "npm_package",
srcs = [
"CHANGELOG.md",
"README.md",
"package.json",
],
visibility = ["//packages/zone.js/test:__pkg__"],
deps = [
":LICENSE.wrapped",
":LICENSE_copy",
"//packages/zone.js/dist:zone_externs",
"//packages/zone.js/lib",
] + [
"//packages/zone.js/dist:" + b + "-dist"
for b in ES5_BUNDLES
] + [
"//packages/zone.js/dist:" + b + "-dist"
for b in ES2015_BUNDLES
] + [
"//packages/zone.js/dist:" + b + "-dist"
for b in ES5_GLOBAL_BUNDLES
] + ["//packages/zone.js/dist:zone_d_ts"],
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
To run tests
------------
Make sure your environment is set up with:
`yarn`
In a separate process, run the WebSockets server:
`yarn ws-server`
Run the browser tests using Karma:
`yarn test`
Run the node.js tests:
`yarn test-node`
Run tslint:
`yarn lint`
Run format with clang-format:
`yarn format`
Run all checks (lint/format/browser test/test-node):
`yarn ci`
Before Commit
------------
Please make sure you pass all following checks before commit
- gulp lint (tslint)
- gulp format:enforce (clang-format)
- gulp promisetest (promise a+ test)
- yarn test (karma browser test)
- gulp test-node (node test)
You can run
`yarn ci`
to do all those checks for you.
You can also add the script into your git pre-commit hook
```
echo -e 'exec npm run ci' > .git/hooks/pre-commit
chmod u+x .git/hooks/pre-commit
```
Webdriver Test
--------------
`zone.js` also supports running webdriver e2e tests.
1. run locally
```
yarn webdriver-start
yarn webdriver-http
yarn webdriver-test
```
2. run locally with sauce connect
```
// export SAUCE_USERNAME and SAUCE_ACCESS_KEY
export SAUCE_USERNAME=XXXX
export SAUCE_ACCESS_KEY=XXX
sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY
yarn webdriver-http
yarn webdriver-sauce-test
```
Releasing
---------
To make a `dry-run`, run the following commands.
```
$ VERSION=<version>
$ git tag 'zone.js-$VERSION'
$ yarn bazel --output_base=$(mktemp -d) run //packages/zone.js:npm_package.pack --workspace_status_command="echo BUILD_SCM_VERSION $VERSION"
```
If everything looks fine, replace `.pack` with `.publish` to push to the npm registry.

139
packages/zone.js/MODULE.md Normal file
View File

@ -0,0 +1,139 @@
# Modules
Starting from zone.js v0.8.9, you can choose which web API modules you want to patch as to reduce overhead introduced by the patching of these modules. For example,
the below samples show how to disable some modules. You just need to define a few global variables
before loading zone.js.
```
<script>
__Zone_disable_Error = true; // Zone will not patch Error
__Zone_disable_on_property = true; // Zone will not patch onProperty such as button.onclick
__Zone_disable_geolocation = true; // Zone will not patch geolocation API
__Zone_disable_toString = true; // Zone will not patch Function.prototype.toString
__Zone_disable_blocking = true; // Zone will not patch alert/prompt/confirm
__Zone_disable_PromiseRejectionEvent = true; // Zone will not patch PromiseRejectionEventHandler
</script>
<script src="../dist/zone.js"></script>
```
Below is the full list of currently supported modules.
- Common
|Module Name|Behavior with zone.js patch|How to disable|
|--|--|--|
|Error|stack frames will have the Zone's name information, (By default, Error patch will not be loaded by zone.js)|__Zone_disable_Error = true|
|toString|Function.toString will be patched to return native version of toString|__Zone_disable_toString = true|
|ZoneAwarePromise|Promise.then will be patched as Zone aware MicroTask|__Zone_disable_ZoneAwarePromise = true|
|bluebird|Bluebird will use Zone.scheduleMicroTask as async scheduler. (By default, bluebird patch will not be loaded by zone.js)|__Zone_disable_bluebird = true|
- Browser
|Module Name|Behavior with zone.js patch|How to disable|
|--|--|--|
|on_property|target.onProp will become zone aware target.addEventListener(prop)|__Zone_disable_on_property = true|
|timers|setTimeout/setInterval/setImmediate will be patched as Zone MacroTask|__Zone_disable_timers = true|
|requestAnimationFrame|requestAnimationFrame will be patched as Zone MacroTask|__Zone_disable_requestAnimationFrame = true|
|blocking|alert/prompt/confirm will be patched as Zone.run|__Zone_disable_blocking = true|
|EventTarget|target.addEventListener will be patched as Zone aware EventTask|__Zone_disable_EventTarget = true|
|IE BrowserTools check|in IE, browser tool will not use zone patched eventListener|__Zone_disable_IE_check = true|
|CrossContext check|in webdriver, enable check event listener is cross context|__Zone_enable_cross_context_check = true|
|XHR|XMLHttpRequest will be patched as Zone aware MacroTask|__Zone_disable_XHR = true|
|geolocation|navigator.geolocation's prototype will be patched as Zone.run|__Zone_disable_geolocation = true|
|PromiseRejectionEvent|PromiseRejectEvent will fire when ZoneAwarePromise has unhandled error|__Zone_disable_PromiseRejectionEvent = true|
|mediaQuery|mediaQuery addListener API will be patched as Zone aware EventTask. (By default, mediaQuery patch will not be loaded by zone.js) |__Zone_disable_mediaQuery = true|
|notification|notification onProperties API will be patched as Zone aware EventTask. (By default, notification patch will not be loaded by zone.js) |__Zone_disable_notification = true|
- NodeJS
|Module Name|Behavior with zone.js patch|How to disable|
|--|--|--|
|node_timers|NodeJS patch timer|__Zone_disable_node_timers = true|
|fs|NodeJS patch fs function as macroTask|__Zone_disable_fs = true|
|EventEmitter|NodeJS patch EventEmitter as Zone aware EventTask|__Zone_disable_EventEmitter = true|
|nextTick|NodeJS patch process.nextTick as microTask|__Zone_disable_nextTick = true|
|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 specific on_properties by setting `__Zone_ignore_on_properties` as follows: 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>
```
- Error
By default, `zone.js/dist/zone-error` will not be loaded for performance concern.
This package will provide following functionality.
1. Error inherit: handle `extend Error` issue.
```
class MyError extends Error {}
const myError = new MyError();
console.log('is MyError instanceof Error', (myError instanceof Error));
```
without `zone-error` patch, the example above will output `false`, with the patch, the reuslt will be `true`.
2. BlacklistZoneStackFrames: remove zone.js stack from `stackTrace`, and add `zone` information. Without this patch, a lot of `zone.js` invocation stack will be shown
in stack frames.
```
at zone.run (polyfill.bundle.js: 3424)
at zoneDelegate.invokeTask (polyfill.bundle.js: 3424)
at zoneDelegate.runTask (polyfill.bundle.js: 3424)
at zone.drainMicroTaskQueue (polyfill.bundle.js: 3424)
at a.b.c (vendor.bundle.js: 12345 <angular>)
at d.e.f (main.bundle.js: 23456)
```
with this patch, those zone frames will be removed,
and the zone information `<angular>/<root>` will be added
```
at a.b.c (vendor.bundle.js: 12345 <angular>)
at d.e.f (main.bundle.js: 23456 <root>)
```
The second feature will slow down the `Error` performance, so `zone.js` provide a flag to let you be able to control the behavior.
The flag is `__Zone_Error_BlacklistedStackFrames_policy`. And the available options is:
1. default: this is the default one, if you load `zone.js/dist/zone-error` without
setting the flag, `default` will be used, and `BlackListStackFrames` will be available
when `new Error()`, you can get a `error.stack` which is `zone stack free`. But this
will slow down `new Error()` a little bit.
2. disable: this will disable `BlackListZoneStackFrame` feature, and if you load
`zone.js/dist/zone-error`, you will only get a `wrapped Error` which can handle
`Error inherit` issue.
3. lazy: this is a feature to let you be able to get `BlackListZoneStackFrame` feature,
but not impact performance. But as a trade off, you can't get the `zone free stack
frames` by access `error.stack`. You can only get it by access `error.zoneAwareStack`.
- Angular(2+)
Angular uses zone.js to manage async operations and decide when to perform change detection. Thus, in Angular,
the following APIs should be patched, otherwise Angular may not work as expected.
1. ZoneAwarePromise
2. timer
3. on_property
4. EventTarget
5. XHR

View File

@ -0,0 +1,229 @@
# Zone.js's support for non standard apis
Zone.js patched most standard APIs such as DOM event listeners, XMLHttpRequest in Browser, EventEmitter and fs API in Node.js so they can be in zone.
But there are still a lot of non standard APIs that are not patched by default, such as MediaQuery, Notification,
WebAudio and so on. We are adding support to those APIs, and our progress is updated here.
## Currently supported non standard Web APIs
* MediaQuery
* Notification
## Currently supported polyfills
* webcomponents
Usage:
```
<script src="webcomponents-lite.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/zone.js/dist/webapis-shadydom.js"></script>
```
## Currently supported non standard node APIs
## Currently supported non standard common APIs
* bluebird promise
Browser Usage:
```
<script src="zone.js"></script>
<script src="bluebird.js"></script>
<script src="zone-bluebird.js"></script>
<script>
Zone[Zone['__symbol__']('bluebird')](Promise);
</script>
```
After those steps, window.Promise will become a ZoneAware Bluebird Promise.
Node Sample Usage:
```
require('zone.js');
const Bluebird = require('bluebird');
require('zone.js/dist/zone-bluebird');
Zone[Zone['__symbol__']('bluebird')](Bluebird);
Zone.current.fork({
name: 'bluebird'
}).run(() => {
Bluebird.resolve(1).then(r => {
console.log('result ', r, 'Zone', Zone.current.name);
});
});
```
In NodeJS environment, you can choose to use Bluebird Promise as global.Promise
or use ZoneAwarePromise as global.Promise.
To run the jasmine test cases of bluebird
```
npm install bluebird
```
then modify test/node_tests.ts
remove the comment of the following line
```
//import './extra/bluebird.spec';
```
## Others
* Cordova
patch `cordova.exec` API
`cordova.exec(success, error, service, action, args);`
`success` and `error` will be patched with `Zone.wrap`.
to load the patch, you should load in the following order.
```
<script src="zone.js"></script>
<script src="cordova.js"></script>
<script src="zone-patch-cordova.js"></script>
```
## Usage
By default, those APIs' support will not be loaded in zone.js or zone-node.js,
so if you want to load those API's support, you should load those files by yourself.
For example, if you want to add MediaQuery patch, you should do like this:
```
<script src="path/zone.js"></script>
<script src="path/webapis-media-query.js"></script>
```
* rxjs
`zone.js` also provide a `rxjs` patch to make sure rxjs Observable/Subscription/Operator run in correct zone.
For details please refer to [pull request 843](https://github.com/angular/zone.js/pull/843). The following sample code describes the idea.
```
const constructorZone = Zone.current.fork({name: 'constructor'});
const subscriptionZone = Zone.current.fork({name: 'subscription'});
const operatorZone = Zone.current.fork({name: 'operator'});
let observable;
let subscriber;
constructorZone.run(() => {
observable = new Observable((_subscriber) => {
subscriber = _subscriber;
console.log('current zone when construct observable:', Zone.current.name); // will output constructor.
return () => {
console.log('current zone when unsubscribe observable:', Zone.current.name); // will output constructor.
}
});
});
subscriptionZone.run(() => {
observable.subscribe(() => {
console.log('current zone when subscription next', Zone.current.name); // will output subscription.
}, () => {
console.log('current zone when subscription error', Zone.current.name); // will output subscription.
}, () => {
console.log('current zone when subscription complete', Zone.current.name); // will output subscription.
});
});
operatorZone.run(() => {
observable.map(() => {
console.log('current zone when map operator', Zone.current.name); // will output operator.
});
});
```
Currently basically everything the `rxjs` API includes
- Observable
- Subscription
- Subscriber
- Operators
- Scheduler
is patched, so each asynchronous call will run in the correct zone.
## Usage.
For example, in an Angular application, you can load this patch in your `app.module.ts`.
```
import 'zone.js/dist/zone-patch-rxjs';
```
* electron
In electron, we patched the following APIs with `zone.js`
1. Browser API
2. NodeJS
3. Electorn Native API
## Usage.
add following line into `polyfill.ts` after loading zone-mix.
```
//import 'zone.js/dist/zone'; // originally added by angular-cli, comment it out
import 'zone.js/dist/zone-mix'; // add zone-mix to patch both Browser and Nodejs
import 'zone.js/dist/zone-patch-electron'; // add zone-patch-electron to patch Electron native API
```
there is a sampel repo [zone-electron](https://github.com/JiaLiPassion/zone-electron).
* socket.io-client
user need to patch `io` themselves just like following code.
```javascript
<script src="socket.io-client/dist/socket.io.js"></script>
<script src="zone.js/dist/zone.js"></script>
<script src="zone.js/dist/zone-patch-socket-io.js"></script>
<script>
// patch io here
Zone[Zone.__symbol__('socketio')](io);
</script>
```
please reference the sample repo [zone-socketio](https://github.com/JiaLiPassion/zone-socketio) about
detail usage.
* jsonp
## Usage.
provide a helper method to patch jsonp. Because jsonp has a lot of implementation, so
user need to provide the information to let json `send` and `callback` in zone.
there is a sampel repo [zone-jsonp](https://github.com/JiaLiPassion/test-zone-js-with-jsonp) here,
sample usage is:
```javascript
import 'zone.js/dist/zone-patch-jsonp';
Zone['__zone_symbol__jsonp']({
jsonp: getJSONP,
sendFuncName: 'send',
successFuncName: 'jsonpSuccessCallback',
failedFuncName: 'jsonpFailedCallback'
});
```
* ResizeObserver
Currently only `Chrome 64` native support this feature.
you can add the following line into `polyfill.ts` after loading `zone.js`.
```
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-patch-resize-observer';
```
there is a sample repo [zone-resize-observer](https://github.com/JiaLiPassion/zone-resize-observer) here

View File

@ -0,0 +1,92 @@
# Zone.js
[![CDNJS](https://img.shields.io/cdnjs/v/zone.js.svg)](https://cdnjs.com/libraries/zone.js)
Implements _Zones_ for JavaScript, inspired by [Dart](https://www.dartlang.org/articles/zones/).
> If you're using zone.js via unpkg (i.e. using `https://unpkg.com/zone.js`)
> and you're using any of the following libraries, make sure you import them first
> * 'newrelic' as it patches global.Promise before zone.js does
> * 'async-listener' as it patches global.setTimeout, global.setInterval before zone.js does
> * 'continuation-local-storage' as it uses async-listener
# NEW Zone.js POST-v0.6.0
See the new API [here](./lib/zone.ts).
Read up on [Zone Primer](https://docs.google.com/document/d/1F5Ug0jcrm031vhSMJEOgp1l-Is-Vf0UCNDY-LsQtAIY).
## What's a Zone?
A Zone is an execution context that persists across async tasks.
You can think of it as [thread-local storage](http://en.wikipedia.org/wiki/Thread-local_storage) for JavaScript VMs.
See this video from ng-conf 2014 for a detailed explanation:
[![screenshot of the zone.js presentation and ng-conf 2014](/presentation.png)](//www.youtube.com/watch?v=3IqtmUscE_U&t=150)
## See also
* [async-listener](https://github.com/othiym23/async-listener) - a similar library for node
* [Async stack traces in Chrome](http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/)
* [strongloop/zone](https://github.com/strongloop/zone) (Deprecated)
* [vizone](https://github.com/gilbox/vizone) - control flow visualizer that uses zone.js
## Standard API support
zone.js patched most standard web APIs (such as DOM events, `XMLHttpRequest`, ...) and nodejs APIs
(`EventEmitter`, `fs`, ...), for more details, please see [STANDARD-APIS.md](STANDARD-APIS.md).
## Nonstandard API support
We are adding support to some nonstandard APIs, such as MediaQuery and
Notification. Please see [NON-STANDARD-APIS.md](NON-STANDARD-APIS.md) for more details.
## Examples
You can find some samples to describe how to use zone.js in [SAMPLE.md](SAMPLE.md).
## Modules
zone.js patches the async APIs described above, but those patches will have some overhead.
Starting from zone.js v0.8.9, you can choose which web API module you want to patch.
For more details, please
see [MODULE.md](MODULE.md).
## Bundles
There are several bundles under `dist` folder.
|Bundle|Summary|
|---|---|
|zone.js|the default bundle, contains the most used APIs such as `setTimeout/Promise/EventTarget...`, also this bundle supports all evergreen and legacy (IE/Legacy Firefox/Legacy Safari) Browsers|
|zone-evergreen.js|the bundle for evergreen browsers, doesn't include the `patch` for `legacy` browsers such as `IE` or old versions of `Firefox/Safari`|
|zone-legacy.js|the patch bundle for legacy browsers, only includes the `patch` for `legacy` browsers such as `IE` or old versions of `Firefox/Safari`. This bundle must be loaded after `zone-evergreen.js`, **`zone.js`=`zone-evergreen.js` + `zone-legacy.js`**|
|zone-testing.js|the bundle for zone testing support, including `jasmine/mocha` support and `async/fakeAsync/sync` test utilities|
|zone-externs.js|the API definitions for `closure compiler`|
And here are the additional optional patches not included in the main zone.js bundles
|Patch|Summary|
|---|---|
|webapis-media-query.js|patch for `MediaQuery APIs`|
|webapis-notification.js|patch for `Notification APIs`|
|webapis-rtc-peer-connection.js|patch for `RTCPeerConnection APIs`|
|webapis-shadydom.js|patch for `Shady DOM APIs`|
|zone-bluebird.js|patch for `Bluebird APIs`|
|zone-error.js|patch for `Error Global Object`, supports remove `Zone StackTrace`|
|zone-patch-canvas.js|patch for `Canvas API`|
|zone-patch-cordova.js|patch for `Cordova API`|
|zone-patch-electron.js|patch for `Electron API`|
|zone-patch-fetch.js|patch for `Fetch API`|
|zone-patch-jsonp.js|utility for `jsonp API`|
|zone-patch-resize-observer.js|patch for `ResizeObserver API`|
|zone-patch-rxjs.js|patch for `rxjs API`|
|zone-patch-rxjs-fake-async.js|patch for `rxjs fakeasync test`|
|zone-patch-socket-io.js|patch for `socket-io`|
|zone-patch-user-media.js|patch for `UserMedia API`|
## Promise A+ test passed
[![Promises/A+ 1.1 compliant](https://promisesaplus.com/assets/logo-small.png)](https://promisesaplus.com/)
## License
MIT

View File

@ -0,0 +1,23 @@
# Sample
### Basic Sample
use `zone.js` and `long-stack-trace-zone.js` to display longStackTrace information in html.
[basic](https://stackblitz.com/edit/zonejs-basic?file=index.js)
### Async Task Counting Sample
use `zone.js` to monitor async tasks and print the count info.
[counting](https://stackblitz.com/edit/zonejs-counting?file=index.js)
### Profiling Sample
use `zone.js` to profiling sort algorithm.
[profiling](https://stackblitz.com/edit/zonejs-profiling?file=index.js)
### Throttle with longStackTrace
use `long-stack-trace-zone` to display full flow of complex async operations such as throttle XHR requests.
[throttle](https://stackblitz.com/edit/zonejs-throttle?file=index.js)

View File

@ -0,0 +1,148 @@
# Zone.js's support for standard apis
Zone.js patched most standard APIs such as DOM event listeners, XMLHttpRequest in Browser, EventEmitter and fs API in Node.js so they can be in zone.
In this document, all patched API are listed.
For non-standard APIs, please see [NON-STANDARD-APIS.md](NON-STANDARD-APIS.md)
## Patch Mechanisms
There are several patch mechanisms
- wrap: makes callbacks run in zones, and makes applications able to receive onInvoke and onIntercept callbacks
- Task: just like in the JavaScript VM, applications can receive onScheduleTask, onInvokeTask, onCancelTask and onHasTask callbacks
1. MacroTask
2. MicroTask
3. EventTask
Some APIs which should be treated as Tasks, but are currently still patched in the wrap way. These will be patched as Tasks soon.
## Browser
Web APIs
| API | Patch Mechanism | Others |
| --- | --- | --- |
| setTimeout/clearTimeout | MacroTask | app can get handlerId, interval, args, isPeriodic(false) through task.data |
| setImmediate/clearImmediate | MacroTask | same with setTimeout |
| setInterval/clearInterval | MacroTask | isPeriodic is true, so setInterval will not trigger onHasTask callback |
| requestAnimationFrame/cancelAnimationFrame | MacroTask | |
| mozRequestAnimationFrame/mozCancelAnimationFrame | MacroTask | |
| webkitRequestAnimationFrame/webkitCancelAnimationFrame | MacroTask | |
| alert | wrap | |
| prompt | wrap | |
| confirm | wrap | |
| Promise | MicroTask | |
| EventTarget | EventTask | see below Event Target for more details |
| HTMLElement on properties | EventTask | see below on properties for more details |
| XMLHttpRequest.send/abort | MacroTask | |
| XMLHttpRequest on properties | EventTask | |
| IDBIndex on properties | EventTask | |
| IDBRequest on properties | EventTask | |
| IDBOpenDBRequest on properties | EventTask | |
| IDBDatabaseRequest on properties | EventTask | |
| IDBTransaction on properties | EventTask | |
| IDBCursor on properties | EventTask | |
| WebSocket on properties | EventTask | |
| MutationObserver | wrap | |
| WebkitMutationObserver | wrap | |
| FileReader | wrap | |
| registerElement | wrap | |
EventTarget
- For browsers supporting EventTarget, Zone.js just patches EventTarget, so everything that inherits
from EventTarget will also be patched.
- For browsers that do not support EventTarget, Zone.js will patch the following APIs in the IDL
that inherit from EventTarget
|||||
|---|---|---|---|
|ApplicationCache|EventSource|FileReader|InputMethodContext|
|MediaController|MessagePort|Node|Performance|
|SVGElementInstance|SharedWorker|TextTrack|TextTrackCue|
|TextTrackList|WebKitNamedFlow|Window|Worker|
|WorkerGlobalScope|XMLHttpRequest|XMLHttpRequestEventTarget|XMLHttpRequestUpload|
|IDBRequest|IDBOpenDBRequest|IDBDatabase|IDBTransaction|
|IDBCursor|DBIndex|WebSocket|
The following 'on' properties, such as onclick, onreadystatechange, are patched in Zone.js as EventTasks
|||||
|---|---|---|---|
|copy|cut|paste|abort|
|blur|focus|canplay|canplaythrough|
|change|click|contextmenu|dblclick|
|drag|dragend|dragenter|dragleave|
|dragover|dragstart|drop|durationchange|
|emptied|ended|input|invalid|
|keydown|keypress|keyup|load|
|loadeddata|loadedmetadata|loadstart|message|
|mousedown|mouseenter|mouseleave|mousemove|
|mouseout|mouseover|mouseup|pause|
|play|playing|progress|ratechange|
|reset|scroll|seeked|seeking|
|select|show|stalled|submit|
|suspend|timeupdate|volumechange|waiting|
|mozfullscreenchange|mozfullscreenerror|mozpointerlockchange|mozpointerlockerror|
|error|webglcontextrestored|webglcontextlost|webglcontextcreationerror|
## NodeJS
| API | Patch Mechanism | Others |
| --- | --- | --- |
| setTimeout/clearTimeout | MacroTask | app can get handlerId, interval, args, isPeriodic(false) through task.data |
| setImmediate/clearImmediate | MacroTask | same with setTimeout |
| setInterval/clearInterval | MacroTask | isPeriodic is true, so setInterval will not trigger onHasTask callback |
| process.nextTick | Microtask | isPeriodic is true, so setInterval will not trigger onHasTask callback |
| Promise | MicroTask | |
| EventEmitter | EventTask | All APIs inherit EventEmitter are patched as EventTask |
| crypto | MacroTask | |
| fs | MacroTask | all async methods are patched |
EventEmitter, addEventListener, prependEventListener and 'on' will be patched once as EventTasks, and removeEventListener and
removeAllListeners will remove those EventTasks
## Electron
Zone.js does not patch the Electron API, although in Electron both browser APIs and node APIs are patched, so
if you want to include Zone.js in Electron, please use dist/zone-mix.js
## ZoneAwareError
ZoneAwareError replaces global Error, and adds zone information to stack trace.
ZoneAwareError also handles 'this' issue.
This type of issue would happen when creating an error without `new`: `this` would be `undefined` in strict mode, and `global` in
non-strict mode. It could cause some very difficult to detect issues.
```javascript
const error = Error();
```
ZoneAwareError makes sure that `this` is ZoneAwareError even without new.
## ZoneAwarePromise
ZoneAwarePromise wraps the global Promise and makes it run in zones as a MicroTask.
It also passes promise A+ tests.
## BlackListEvents
Sometimes we don't want some `event` to be patched by `zone.js`, we can blacklist events
by following settings.
```javascript
// disable on properties
var targets = [window, Document, HTMLBodyElement, HTMLElement];
__Zone_ignore_on_properties = [];
targets.forEach(function (target) {
__Zone_ignore_on_properties.push({
target: target,
ignoreProperties: ['scroll']
});
});
// disable addEventListener
global['__zone_symbol__BLACK_LISTED_EVENTS'] = ['scroll'];
```

View File

@ -0,0 +1,50 @@
"""
Describe all the output bundles in the zone.js npm package
by mapping the bundle name to the source location.
"""
_DIR = "//packages/zone.js/lib:"
ES5_GLOBAL_BUNDLES = {
"zone": _DIR + "browser/rollup-legacy-main",
"zone-mix": _DIR + "mix/rollup-mix",
"zone-node": _DIR + "node/rollup-main",
"zone-testing-node-bundle": _DIR + "node/rollup-test-main",
}
ES5_BUNDLES = {
"async-test": _DIR + "testing/async-testing",
"fake-async-test": _DIR + "testing/fake-async",
"long-stack-trace-zone": _DIR + "zone-spec/long-stack-trace",
"proxy": _DIR + "zone-spec/proxy",
"zone-patch-rxjs-fake-async": _DIR + "rxjs/rxjs-fake-async",
"sync-test": _DIR + "zone-spec/sync-test",
"task-tracking": _DIR + "zone-spec/task-tracking",
"wtf": _DIR + "zone-spec/wtf",
"zone-error": _DIR + "common/error-rewrite",
"zone-legacy": _DIR + "browser/browser-legacy",
"zone-bluebird": _DIR + "extra/bluebird",
"zone-patch-canvas": _DIR + "browser/canvas",
"zone-patch-cordova": _DIR + "extra/cordova",
"zone-patch-electron": _DIR + "extra/electron",
"zone-patch-fetch": _DIR + "common/fetch",
"jasmine-patch": _DIR + "jasmine/jasmine",
"zone-patch-jsonp": _DIR + "extra/jsonp",
"webapis-media-query": _DIR + "browser/webapis-media-query",
"mocha-patch": _DIR + "mocha/mocha",
"webapis-notification": _DIR + "browser/webapis-notification",
"zone-patch-promise-test": _DIR + "testing/promise-testing",
"zone-patch-resize-observer": _DIR + "browser/webapis-resize-observer",
"webapis-rtc-peer-connection": _DIR + "browser/webapis-rtc-peer-connection",
"zone-patch-rxjs": _DIR + "rxjs/rxjs",
"webapis-shadydom": _DIR + "browser/shadydom",
"zone-patch-socket-io": _DIR + "extra/socket-io",
"zone-patch-user-media": _DIR + "browser/webapis-user-media",
"zone-testing": _DIR + "testing/zone-testing",
"zone-testing-bundle": _DIR + "browser/rollup-legacy-test-main",
}
ES2015_BUNDLES = {
"zone-evergreen": _DIR + "browser/rollup-main",
"zone-evergreen-testing-bundle": _DIR + "browser/rollup-test-main",
}

View File

@ -0,0 +1,28 @@
/**
* @license
* Copyright Google Inc. 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
*/
const fs = require('fs');
module.exports = function(config) {
let chkResult = true;
config.targets.forEach(target => {
if (target.checkTarget) {
try {
const stats = fs.statSync(target.path);
if (stats.size > target.limit) {
console.error(
`file ${target.path} size over limit, limit is ${target.limit}, actual is ${stats.size}`);
chkResult = false;
}
} catch (err) {
console.error(`failed to get filesize: ${target.path}`);
chkResult = false;
}
}
});
return chkResult;
};

200
packages/zone.js/dist/BUILD.bazel vendored Normal file
View File

@ -0,0 +1,200 @@
load("@build_bazel_rules_nodejs//:defs.bzl", "rollup_bundle")
load("//packages/zone.js:bundles.bzl", "ES2015_BUNDLES", "ES5_BUNDLES", "ES5_GLOBAL_BUNDLES")
package(default_visibility = ["//packages/zone.js:__subpackages__"])
# copy this file from //lib to //dist
genrule(
name = "zone_externs",
srcs = ["//packages/zone.js/lib:closure/zone_externs.js"],
outs = ["zone_externs.js"],
cmd = "cp $< $@",
)
genrule(
name = "zone_d_ts",
srcs = ["//packages/zone.js/lib"],
outs = ["zone.js.d.ts"],
cmd = "find $(SRCS) -name \"zone.d.ts\" -exec cp {} $(@D)/zone.js.d.ts \;",
)
[
rollup_bundle(
name = b[0].replace("-", "_") + "_rollup",
entry_point = b[1] + ".ts",
globals = {
"electron": "electron",
},
license_banner = "//packages:license-banner.txt",
deps = [
"//packages/zone.js/lib",
],
)
for b in ES5_BUNDLES.items()
]
[
rollup_bundle(
name = b[0].replace("-", "_") + "_rollup",
entry_point = b[1] + ".ts",
global_name = "Zone",
license_banner = "//packages:license-banner.txt",
deps = [
"//packages/zone.js/lib",
],
)
for b in ES5_GLOBAL_BUNDLES.items() + ES2015_BUNDLES.items()
]
# the es5 filegroups
[
filegroup(
name = b[0] + ".es5",
srcs = [":" + b[0].replace("-", "_") + "_rollup"],
output_group = "es5_umd",
)
for b in ES5_BUNDLES.items() + ES5_GLOBAL_BUNDLES.items()
]
# the es5.min filegroups
[
filegroup(
name = b[0] + ".es5.min",
srcs = [":" + b[0].replace("-", "_") + "_rollup"],
output_group = "es5_umd_min",
)
for b in ES5_BUNDLES.items() + ES5_GLOBAL_BUNDLES.items()
]
# the es2015 filegroups
[
filegroup(
name = b[0] + ".umd",
srcs = [":" + b[0].replace("-", "_") + "_rollup"],
output_group = "umd",
)
for b in ES2015_BUNDLES.items()
]
# the es2015.min filegroups
[
filegroup(
name = b[0] + ".umd.min",
srcs = [":" + b[0].replace("-", "_") + "_rollup"],
output_group = "umd_min",
)
for b in ES2015_BUNDLES.items()
]
# Extract and rename each es5 bundle to a .js and .min.js in the dist/ dir
[
genrule(
name = b[0] + "-dist",
srcs = [
b[0] + ".es5",
b[0] + ".es5.min",
],
outs = [
b[0] + ".js",
b[0] + ".min.js",
],
cmd = " && ".join([
"mkdir -p $(@D)",
"cp $(@D)/" + b[0].replace("-", "_") + "_rollup.es5umd.js $(@D)/" + b[0] + ".js",
"cp $(@D)/" + b[0].replace("-", "_") + "_rollup.min.es5umd.js $(@D)/" + b[0] + ".min.js",
]),
)
for b in ES5_BUNDLES.items() + ES5_GLOBAL_BUNDLES.items()
]
# Extract and rename each es5 bundle to a .js and .min.js in the dist/ dir
[
genrule(
name = b[0] + "-dist-dev-test",
srcs = [
b[0] + ".es5",
],
outs = [
b[0] + ".dev.test.js",
],
cmd = " && ".join([
"mkdir -p $(@D)",
"cp $(@D)/" + b[0].replace("-", "_") + "_rollup.es5umd.js $(@D)/" + b[0] + ".dev.test.js",
]),
)
for b in ES5_BUNDLES.items() + ES5_GLOBAL_BUNDLES.items()
]
[
genrule(
name = b + "-dist-dev-test",
srcs = [
b + ".umd",
],
outs = [
b + ".dev.test.js",
],
cmd = " && ".join([
"mkdir -p $(@D)",
"cp $(@D)/" + b.replace("-", "_") + "_rollup.umd.js $(@D)/" + b + ".dev.test.js",
]),
)
for b in ES2015_BUNDLES
]
# Extract and rename each es5 bundle to a .js and .min.js in the dist/ dir
[
genrule(
name = b[0] + "-dist-test",
srcs = [
b[0] + ".es5.min",
],
outs = [
b[0] + ".test.min.js",
],
cmd = " && ".join([
"mkdir -p $(@D)",
"cp $(@D)/" + b[0].replace("-", "_") + "_rollup.min.es5umd.js $(@D)/" + b[0] + ".test.min.js",
]),
)
for b in ES5_BUNDLES.items() + ES5_GLOBAL_BUNDLES.items()
]
# Extract and rename each es2015 bundle to a .js and .min.js in the dist/ dir
[
genrule(
name = b + "-dist",
srcs = [
b + ".umd",
b + ".umd.min",
],
outs = [
b + ".js",
b + ".min.js",
],
cmd = " && ".join([
"mkdir -p $(@D)",
"cp $(@D)/" + b.replace("-", "_") + "_rollup.umd.js $(@D)/" + b + ".js",
"cp $(@D)/" + b.replace("-", "_") + "_rollup.min.umd.js $(@D)/" + b + ".min.js",
]),
)
for b in ES2015_BUNDLES
]
# Extract and rename each es5 bundle to a .js and .min.js in the dist/ dir
[
genrule(
name = b + "-dist-test",
srcs = [
b + ".umd.min",
],
outs = [
b + ".test.min.js",
],
cmd = " && ".join([
"mkdir -p $(@D)",
"cp $(@D)/" + b.replace("-", "_") + "_rollup.min.umd.js $(@D)/" + b + ".test.min.js",
]),
)
for b in ES2015_BUNDLES
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,9 @@
@startuml
scheduling --> unknown: zoneSpec.onScheduleTask\nor task.scheduleFn\nthrow error
running --> scheduled: error in \ntask.callback\nand task is\nperiodical\ntask
running --> notScheduled: error in\ntask.callback\nand\ntask is not\nperiodical
running: zoneSpec.onHandleError
running --> throw: error in\n task.callback\n and \nzoneSpec.onHandleError\n return true
canceling --> unknown: zoneSpec.onCancelTask\n or task.cancelFn\n throw error
unknown --> throw
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,21 @@
@startuml
[*] --> notScheduled: initialize
notScheduled --> scheduling: addEventListener
scheduling: zoneSpec.onScheduleTask
scheduling: zoneSpec.onHasTask
scheduling --> scheduled
scheduled --> running: event\n triggered
running: zoneSpec:onInvokeTask
scheduled --> canceling: removeEventListener
canceling: zoneSpec.onCancelTask
canceling --> notScheduled
canceling: zoneSpec.onHasTask
running --> scheduled: callback\n finished
running: zoneSpec.onHasTask
running --> canceling: removeEventListener
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,14 @@
@startuml
[*] --> notScheduled: initialize
notScheduled --> scheduling: promise.then/\nprocess.nextTick\nand so on
scheduling: zoneSpec.onScheduleTask
scheduling: zoneSpec.onHasTask
scheduling --> scheduled
scheduled --> running: callback
running: zoneSpec:onInvokeTask
running --> notScheduled
running: zoneSpec.onHasTask
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,18 @@
@startuml
[*] --> notScheduled: initialize
notScheduled --> scheduling: setTimeout/\nXMLHttpRequest.send\nand so on
scheduling: zoneSpec.onScheduleTask
scheduling: zoneSpec.onHasTask
scheduling --> scheduled
scheduled --> running: timeout callback\nreadystatechange\ncallback
running: zoneSpec:onInvokeTask
scheduled --> canceling: clearTimeout\n/abort request
canceling: zoneSpec.onCancelTask
canceling --> notScheduled
canceling: zoneSpec.onHasTask
running --> notScheduled
running: zoneSpec.onHasTask
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,18 @@
@startuml
[*] --> notScheduled: initialize
notScheduled --> scheduling: scheduleTask
scheduling: zoneSpec.onScheduleTask
scheduling: zoneSpec.onHasTask
scheduling --> scheduled: override with\n anotherZone
scheduled --> running: timeout callback\nreadystatechange\ncallback
running: anotherZoneSpec:onInvokeTask
scheduled --> canceling: clearTimeout\n/abort request
canceling: anotherZoneSpec.onCancelTask
canceling --> notScheduled
canceling: zneSpec.onHasTask
running --> notScheduled
running: zoneSpec.onHasTask
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,18 @@
@startuml
[*] --> notScheduled: initialize
notScheduled --> scheduling: setInterval
scheduling: zoneSpec.onScheduleTask
scheduling: zoneSpec.onHasTask
scheduling --> scheduled
scheduled --> running: interval\n callback
running: zoneSpec:onInvokeTask
scheduled --> canceling: clearInterval
canceling: zoneSpec.onCancelTask
canceling --> notScheduled
canceling: zoneSpec.onHasTask
running --> scheduled: callback\n finished
running --> canceling: clearInterval
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,20 @@
@startuml
[*] --> notScheduled: initialize
notScheduled --> scheduling: ①current zone\n scheduleTask
notScheduled --> scheduling: ③anotherZone\n scheduleTask
scheduling: anotherZoneSpec.onScheduleTask
scheduling: anotherZoneSpec.onHasTask
scheduling --> notScheduled: ②cancelScheduleRequest
scheduling --> scheduled
scheduled --> running: callback
running: anotherZoneSpec:onInvokeTask
scheduled --> canceling: cancelTask
canceling: anotherZoneSpec.onCancelTask
canceling --> notScheduled
canceling: anotherZoneSpec.onHasTask
running --> notScheduled
running: anotherZoneSpec.onHasTask
@enduml

View File

@ -0,0 +1,80 @@
## Task lifecycle
We handle several kinds of tasks in zone.js,
- MacroTask
- MicroTask
- EventTask
For details, please refer to [here](../dist/zone.js.d.ts)
This document will explain the lifecycle (state-transition) of different types of tasks and also the triggering of various zonespec's callback during that cycle.
The motivation to write this document has come from this [PR](https://github.com/angular/zone.js/pull/629) of @mhevery. This has made the task's state more clear. Also, tasks can now be cancelled and rescheduled in different zone.
### MicroTask
Such as Promise.then, process.nextTick, they are microTasks, the lifecycle(state transition)
looks like this.
![MicroTask](microtask.png "MicroTask")
ZoneSpec's onHasTask callback will be triggered when the first microTask were scheduled or the
last microTask was invoked.
### EventTask
Such as EventTarget's EventListener, EventEmitter's EventListener, their lifecycle(state transition)
looks like this.
![EventTask](eventtask.png "EventTask")
ZoneSpec's onHasTask callback will be triggered when the first eventTask were scheduled or the
last eventTask was cancelled.
EventTask will go back to scheduled state after invoked(running state), and will become notScheduled after cancelTask(such as removeEventListener)
### MacroTask
#### Non Periodical MacroTask
Such as setTimeout/XMLHttpRequest, their lifecycle(state transition)
looks like this.
![non-periodical-macroTask](non-periodical-macrotask.png "non periodical macroTask")
ZoneSpec's onHasTask callback will be triggered when the first macroTask were scheduled or the
last macroTask was invoked or cancelled.
Non periodical macroTask will become notScheduled after being invoked or being cancelled(such as clearTimeout)
#### Periodical MacroTask
Such as setInterval, their lifecycle(state transition)
looks like this.
![periodical-MacroTask](periodical-macrotask.png "periodical MacroTask")
ZoneSpec's onHasTask callback will be triggered when first macroTask was scheduled or last macroTask
was cancelled, it will not triggered after invoke, because it is periodical and become scheduled again.
Periodical macroTask will go back to scheduled state after invoked(running state), and will become notScheduled after cancelTask(such as clearInterval)
### Reschedule Task to a new zone
Sometimes you may want to reschedule task into different zone, the lifecycle looks like
![reschedule-task](reschedule-task.png "reschedule task")
the ZoneTask's cancelScheduleRequest method can be only called in onScheduleTask callback of ZoneSpec,
because it is still under scheduling state.
And after rescheduling, the task will be scheduled to new zone(the otherZoneSpec in the graph),
and will have nothing todo with the original zone.
### Override zone when scheduling
Sometimes you may want to just override the zone when scheduling, the lifecycle looks like
![override-task](override-task.png "override task")
After overriding, the task will be invoked/cancelled in the new zone(the otherZoneSpec in the graph),
but hasTask callback will still be invoked in original zone.
### Error occurs in task lifecycle
![error](error.png "error")

View File

@ -0,0 +1,59 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Zone.js Basic Demo</title>
<link rel="stylesheet" href="css/style.css">
<script src="../dist/zone.js"></script>
<script src="../dist/long-stack-trace-zone.js"></script>
</head>
<body>
<h1>Basic Example</h1>
<button id="b1">Bind Error</button>
<button id="b2">Cause Error</button>
<script>
/*
* This is a simple example of async stack traces with zones
*/
function main () {
b1.addEventListener('click', bindSecondButton);
}
/*
* What if your stack trace could tell you what
* order the user pushed the buttons from the stack trace?
*
* What if you could log this back to the server?
*
* Think of how much more productive your debugging could be!
*/
function bindSecondButton () {
b2.addEventListener('click', throwError);
}
function throwError () {
throw new Error('aw shucks');
}
/*
* Bootstrap the app
*/
//main();
Zone.current.fork(
{
onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) {
console.log(error.stack);
}
}
).fork(Zone.longStackTraceZoneSpec).run(main);
</script>
</body>
</html>

View File

@ -0,0 +1,65 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Zone.js addEventListenerBenchmark</title>
<link rel="stylesheet" href="../css/style.css">
<script src="../../dist/zone.js"></script>
</head>
<body>
<h1>addEventListener Benchmark</h1>
<h2>No Zone</h2>
<button id="b1">Add/Remove same callback</button>
<button id="b2">Add/Remove different callback</button>
<h2>With Zone</h2>
<button id="b3">Add/Remove same callback</button>
<button id="b4">Add/Remove different callback</button>
<div id="rootDiv"></div>
<script>
b1.addEventListener('click', function () { addRemoveCallback(true, false); });
b2.addEventListener('click', function () { addRemoveCallback(false, false); });
b3.addEventListener('click', function () { addRemoveCallback(true, true); });
b4.addEventListener('click', function () { addRemoveCallback(false, true); });
var divs = [];
var callbacks = [];
var size = 100000;
for(var i = 0; i < size; i++) {
var div = document.createElement("div");
var callback = (function(i) { return function() { console.log(i); }; })(i);
rootDiv.appendChild(div);
divs[i] = div;
callbacks[i] = callback;
}
function addRemoveCallback(reuse, useZone) {
var start = performance.now();
var callback = callbacks[0];
for(var i = 0; i < size; i++) {
var div = divs[i];
if (!reuse) callback = callbacks[i];
if (useZone)
div.addEventListener('click', callback);
else
div.__zone_symbol__addEventListener('click', callback);
}
for(var i = 0; i < size; i++) {
var div = divs[i];
if (!reuse) callback = callbacks[i];
if (useZone)
div.removeEventListener('click', callback);
else
div.__zone_symbol__removeEventListener('click', callback);
}
var end = performance.now();
console.log(useZone ? 'use zone': 'native', reuse ? 'reuse' : 'new', end - start, 'ms');
}
</script>
</body>
</html>

View File

@ -0,0 +1,50 @@
/**
* @license
* Copyright Google Inc. 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
*/
const events = require('events');
const EventEmitter = events.EventEmitter;
require('../../dist/zone-node');
const emitters = [];
const callbacks = [];
const size = 100000;
for (let i = 0; i < size; i++) {
const emitter = new EventEmitter();
const callback = (function(i) { return function() { console.log(i); }; })(i);
emitters[i] = emitter;
callbacks[i] = callback;
}
function addRemoveCallback(reuse, useZone) {
const start = new Date();
let callback = callbacks[0];
for (let i = 0; i < size; i++) {
const emitter = emitters[i];
if (!reuse) callback = callbacks[i];
if (useZone)
emitter.on('msg', callback);
else
emitter.__zone_symbol__addListener('msg', callback);
}
for (let i = 0; i < size; i++) {
const emitter = emitters[i];
if (!reuse) callback = callbacks[i];
if (useZone)
emitter.removeListener('msg', callback);
else
emitter.__zone_symbol__removeListener('msg', callback);
}
const end = new Date();
console.log(useZone ? 'use zone' : 'native', reuse ? 'reuse' : 'new');
console.log('Execution time: %dms', end - start);
}
addRemoveCallback(false, false);
addRemoveCallback(false, true);
addRemoveCallback(true, false);
addRemoveCallback(true, true);

View File

@ -0,0 +1,95 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Counting Pending Tasks</title>
<link rel="stylesheet" href="css/style.css">
<script src="../dist/zone.js"></script>
<script src="js/counting-zone.js"></script>
</head>
<body>
<h1>Counting Pending Tasks</h1>
<p>We want to know about just the events from a single mouse click
while a bunch of other stuff is happening on the page</p>
<p>This is useful in E2E testing. Because you know when there are
no async tasks, you avoid adding timeouts that wait for tasks that
run for an indeterminable amount of time.</p>
<button id="b1">Start</button>
<p id="output"></p>
<script>
/*
* Zone that counts pending async tasks
*/
const outputElem = document.getElementById('output');
const countingZoneSpec = Zone['countingZoneSpec'];
const myCountingZone = Zone.current.fork(countingZoneSpec).fork({
onScheduleTask(parent, current, target, task) {
parent.scheduleTask(target, task);
console.log('Scheduled ' + task.source + ' => ' + task.data.handleId);
outputElem.innerText = countingZoneSpec.counter();
},
onInvokeTask(parent, current, target, task) {
console.log('Invoking ' + task.source + ' => ' + task.data.handleId);
parent.invokeTask(target, task);
outputElem.innerText = countingZoneSpec.counter();
},
onHasTask(parent, current, target, hasTask) {
if (hasTask.macroTask) {
console.log("There are outstanding MacroTasks.");
} else {
console.log("All MacroTasks have been completed.");
}
}
});
/*
* We want to profile just the actions that are a result of this button, so with
* a single line of code, we can run `main` in the countingZone
*/
b1.addEventListener('click', function () {
myCountingZone.run(main);
});
/*
* Spawns a bunch of setTimeouts which in turn spawn more setTimeouts!
* This is a boring way to simulate HTTP requests and other async actions.
*/
function main () {
for (var i = 0; i < 10; i++) {
recur(i, 800);
}
}
function recur (x, t) {
if (x > 0) {
setTimeout(function () {
for (var i = x; i < 8; i++) {
recur(x - 1, Math.random()*t);
}
}, t);
}
}
/*
* There may be other async actions going on in the background.
* Because this is not in the zone, our profiling ignores it.
* Nice.
*/
function noop () {
setTimeout(noop, 10*Math.random());
}
noop();
</script>
</body>
</html>

View File

@ -0,0 +1,8 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}

View File

@ -0,0 +1,21 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Zone.js Examples</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<h1>Examples</h1>
<ol>
<li><a href="basic.html">Tracing user actions with long stack traces</a></li>
<li><a href="counting.html">Counting Tasks</a></li>
<li><a href="profiling.html">Profiling Across Tasks</a></li>
<li><a href="throttle.html">Throttle</a></li>
<li><a href="web-socket.html">WebSocket</a></li>
</ol>
</body>
</html>

View File

@ -0,0 +1,33 @@
/*
* See example/counting.html
*/
Zone['countingZoneSpec'] = {
name: 'counterZone',
// setTimeout
onScheduleTask: function(delegate, current, target, task) {
this.data.count += 1;
delegate.scheduleTask(target, task);
},
// fires when...
// - clearTimeout
// - setTimeout finishes
onInvokeTask: function(delegate, current, target, task, applyThis, applyArgs) {
delegate.invokeTask(target, task, applyThis, applyArgs);
this.data.count -= 1;
},
onHasTask: function(delegate, current, target, hasTask) {
if (this.data.count === 0 && !this.data.flushed) {
this.data.flushed = true;
target.run(this.onFlush);
}
},
counter: function() { return this.data.count; },
data: {count: 0, flushed: false},
onFlush: function() {}
};

View File

@ -0,0 +1,126 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Zones Profiling</title>
<link rel="stylesheet" href="css/style.css">
<script>
__Zone_disable_Error = true;
__Zone_disable_on_property = true;
__Zone_disable_geolocation = true;
__Zone_disable_toString = true;
__Zone_disable_blocking = true;
__Zone_disable_PromiseRejectionEvent = true;
</script>
<script src="../dist/zone.js"></script>
<script src="../dist/long-stack-trace-zone.js"></script>
</head>
<body>
<h1>Profiling with Zones</h1>
<button id="b1">Start Profiling</button>
<script>
/*
* Let's say we want to know the CPU cost from some action
* that includes async tasks. We can do this with zones!
*/
/*
* For this demo, we're going to sort an array using an async
* algorithm when a button is pressed.
*/
function sortAndPrintArray (unsortedArray) {
profilingZoneSpec.reset();
asyncBogosort(unsortedArray, function (sortedArray) {
console.log(sortedArray);
console.log('sorting took ' + profilingZoneSpec.time() + ' of CPU time');
});
}
/*
* This is a really efficient algorithm.
*
* First, check if the array is sorted.
* - If it is, call the wrapCallback
* - If it isn't, randomize the array and recur
*
* This implementation is async because JavaScript
*/
function asyncBogosort (arr, cb) {
setTimeout(function () {
if (isSorted(arr)) {
cb(arr);
} else {
var newArr = arr.slice(0);
newArr.sort(function () {
return Math.random() - 0.5;
});
asyncBogosort(newArr, cb);
}
}, 0);
}
function isSorted (things) {
for (var i = 1; i < things.length; i += 1) {
if (things[i] < things[i - 1]) {
return false;
}
}
return true;
}
/*
* Bind button
*/
function main () {
var unsortedArray = [3,4,1,2,7];
b1.addEventListener('click', function () {
sortAndPrintArray(unsortedArray);
});
}
/*
* This zone starts a timer at the start of each taskEnv,
* and stops it at the end. It accumulates the total run
* time internally, exposing it via `zone.time()`
*
* Note that this is the time the CPU is spending doing
* bogosort, as opposed to the time from the start
* of the algorithm until it's completion.
*/
var profilingZoneSpec = (function () {
var time = 0,
// use the high-res timer if available
timer = performance ?
performance.now.bind(performance) :
Date.now.bind(Date);
return {
onInvokeTask: function (delegate, current, target, task, applyThis, applyArgs) {
this.start = timer();
delegate.invokeTask(target, task, applyThis, applyArgs);
time += timer() - this.start;
},
time: function () {
return Math.floor(time*100) / 100 + 'ms';
},
reset: function () {
time = 0;
}
};
}());
/*
* Bootstrap the app
*/
Zone.current.fork(profilingZoneSpec).run(main);
</script>
</body>
</html>

View File

@ -0,0 +1,91 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Zones throttle</title>
<link rel="stylesheet" href="css/style.css">
<script src="../dist/zone.js"></script>
<script src="../dist/long-stack-trace-zone.js"></script>
</head>
<body>
<h1>Throttle Example</h1>
<button id="b2">Error</button>
<script>
/*
* In this example, we have a button that makes some request
* to our server. We want to only make the request once per
* 1s, so we use a `throttle` helper. If the request fails,
* it's difficult to see where the failure originated from.
*
* Thankfully, we can use zones to preserve the stack even
* across async tasks to get a better picture of the control
* flow of the app.
*/
var throttleMakeRequest;
/*
* Start the app
* Bind listeners
*/
function bootstrap () {
b2.addEventListener('click', throttleMakeRequest);
}
/*
* Makes a request to the server
* Will only make the request once every 1000ms
*/
throttleMakeRequest = throttle(function () {
makeRequestToServer(handleServerResponse);
}, 1000);
/*
* Pretend this makes a request to a server
*/
function makeRequestToServer (cb) {
setTimeout(cb, 100);
}
function handleServerResponse (err, data) {
throw new Error('Oops');
}
/*
* Returns a fn that can only be called once
* per `time` milliseconds
*/
function throttle (fn, time) {
var id = null;
if (typeof time !== 'number') {
time = 0;
}
return function () {
if (!id) {
id = setTimeout(function () {
id = null;
fn();
}, time);
}
};
}
/*
* Start the application
*
* If we start the application with `zone.run`, we
* get long stack traces in the console!
*/
//bootstrap();
Zone.current.fork(Zone.longStackTraceZoneSpec).run(bootstrap);
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>WebSockets with Zones</title>
<link rel="stylesheet" href="css/style.css">
<script src="../dist/zone.js"></script>
</head>
<body>
<p>
Ensure that you started <code>node test/ws-server.js</code> before loading
this page. Then check console output.
</p>
<script>
var ws = new WebSocket('ws://localhost:8001');
ws.onopen = function() {
Zone.current.fork({properties: {secretPayload: 'bah!'}, name: 'secrete-zone'}).run(function() {
ws.onmessage = function(eventListener) {
if (Zone.current.get('secretPayload') === 'bah!') {
console.log("The current zone (id: %s) has secretPayload. Zones are working!",
Zone.current.name);
} else {
console.error('Secret payload not found where expected! Zones are not working! :-(', Zone.current.name);
}
};
console.log('Setting secret payload in the current zone (id: %s)', Zone.current.name);
});
ws.send('hello!');
};
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
{
"targets": [
{
"path": "dist/zone-evergreen.min.js",
"checkTarget": true,
"limit": 43000
},
{
"path": "dist/zone.min.js",
"checkTarget": true,
"limit": 45000
}
]
}

View File

@ -0,0 +1,51 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
config.set({
basePath: '',
client: {errorpolicy: config.errorpolicy},
files: [
'node_modules/systemjs/dist/system-polyfills.js', 'node_modules/systemjs/dist/system.src.js',
'node_modules/whatwg-fetch/fetch.js',
{pattern: 'node_modules/rxjs/**/**/*.js', included: false, watched: false},
{pattern: 'node_modules/rxjs/**/**/*.js.map', included: false, watched: false},
{pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false},
{pattern: 'node_modules/es6-promise/**/*.js', included: false, watched: false},
{pattern: 'node_modules/core-js/**/*.js', included: false, watched: false},
{pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false},
{pattern: 'test/assets/**/*.*', watched: true, served: true, included: false},
{pattern: 'build/**/*.js.map', watched: true, served: true, included: false},
{pattern: 'build/**/*.js', watched: true, served: true, included: false}
],
plugins: [
require('karma-chrome-launcher'), require('karma-firefox-launcher'),
require('karma-sourcemap-loader')
],
preprocessors: {'**/*.js': ['sourcemap']},
exclude: ['test/microtasks.spec.ts'],
reporters: ['progress'],
// port: 9876,
colors: true,
logLevel: config.LOG_INFO,
browsers: ['Chrome'],
captureTimeout: 60000,
retryLimit: 4,
autoWatch: true,
singleRun: false
});
};

View File

@ -0,0 +1,9 @@
module.exports = function(config) {
require('./karma-build.conf.js')(config);
config.plugins.push(require('karma-jasmine'));
config.plugins.push(require('karma-phantomjs-launcher'));
config.frameworks.push('jasmine');
config.browsers.splice(0, 1, ['PhantomJS']);
};

View File

@ -0,0 +1,7 @@
module.exports = function(config) {
require('./karma-build.conf.js')(config);
config.plugins.push(require('karma-jasmine'));
config.frameworks.push('jasmine');
};

View File

@ -0,0 +1,11 @@
module.exports = function(config) {
require('./karma-build-jasmine.conf.js')(config);
for (let i = 0; i < config.files.length; i++) {
if (config.files[i] === 'node_modules/core-js-bundle/index.js') {
config.files.splice(i, 1);
break;
}
}
config.client.entrypoint = 'browser_es2015_entry_point';
};

View File

@ -0,0 +1,11 @@
module.exports = function(config) {
require('./karma-build.conf.js')(config);
config.plugins.push(require('karma-mocha'));
config.frameworks.push('mocha');
config.client.mocha = {
timeout: 5000 // copied timeout for Jasmine in WebSocket.spec (otherwise Mochas default timeout
// at 2 sec is to low for the tests)
};
};

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
require('./karma-dist-mocha.conf.js')(config);
require('./sauce.conf')(config);
};

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
require('./karma-dist-mocha.conf.js')(config);
require('./sauce-selenium3.conf')(config, ['SL_IE9']);
};

View File

@ -0,0 +1,18 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
require('./karma-base.conf.js')(config);
config.files.push('node_modules/core-js-bundle/index.js');
config.files.push('build/test/browser-env-setup.js');
config.files.push('build/test/wtf_mock.js');
config.files.push('build/test/test_fake_polyfill.js');
config.files.push('build/lib/zone.js');
config.files.push('build/lib/common/promise.js');
config.files.push('build/test/main.js');
};

View File

@ -0,0 +1,7 @@
module.exports = function(config) {
require('./karma-dist.conf.js')(config);
config.plugins.push(require('karma-jasmine'));
config.frameworks.push('jasmine');
};

View File

@ -0,0 +1,23 @@
module.exports = function(config) {
require('./karma-dist.conf.js')(config);
for (let i = 0; i < config.files.length; i++) {
if (config.files[i] === 'dist/zone-testing.js') {
config.files.splice(i, 1);
break;
}
}
config.files.push('dist/long-stack-trace-zone.js');
config.files.push('dist/proxy.js');
config.files.push('dist/sync-test.js');
config.files.push('dist/async-test.js');
config.files.push('dist/fake-async-test.js');
config.files.push('dist/zone-patch-promise-test.js');
config.plugins.push(require('karma-mocha'));
config.frameworks.push('mocha');
config.client.mocha = {
timeout: 5000 // copied timeout for Jasmine in WebSocket.spec (otherwise Mochas default timeout
// at 2 sec is to low for the tests)
};
};

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
require('./karma-dist-jasmine.conf.js')(config);
require('./sauce.conf')(config, ['SL_IOS9']);
};

View File

@ -0,0 +1,28 @@
module.exports = function(config) {
require('./karma-dist-jasmine.conf.js')(config);
require('./sauce.es2015.conf')(config);
const files = config.files;
config.files = [];
for (let i = 0; i < files.length; i++) {
if (files[i] !== 'node_modules/core-js-bundle/index.js' || files[i] === 'build/test/main.js') {
config.files.push(files[i]);
}
}
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({pattern: 'dist/zone-evergreen.js', type: 'module'});
config.files.push('dist/zone-patch-canvas.js');
config.files.push('dist/zone-patch-fetch.js');
config.files.push('dist/webapis-media-query.js');
config.files.push('dist/webapis-notification.js');
config.files.push('dist/zone-patch-user-media.js');
config.files.push('dist/zone-patch-resize-observer.js');
config.files.push('dist/task-tracking.js');
config.files.push('dist/wtf.js');
config.files.push('dist/zone-testing.js');
config.files.push('build/test/test-env-setup-jasmine.js');
config.files.push('build/lib/common/error-rewrite.js');
config.files.push('build/test/browser/custom-element.spec.js');
};

View File

@ -0,0 +1,16 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
require('./karma-dist-jasmine.conf.js')(config);
require('./sauce.conf')(config, [
'SL_IOS9', 'SL_CHROME', 'SL_FIREFOX_54', 'SL_SAFARI8', 'SL_SAFARI9', 'SL_SAFARI10', 'SL_IOS8',
'SL_IOS9', 'SL_IOS10', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_MSEDGE15', 'SL_ANDROID4.4',
'SL_ANDROID5.1'
])
};

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
require('./karma-dist-jasmine.conf.js')(config);
require('./sauce-selenium3.conf')(config);
};

View File

@ -0,0 +1,27 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
require('./karma-base.conf.js')(config);
config.files.push('node_modules/core-js-bundle/index.js');
config.files.push('build/test/browser-env-setup.js');
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/zone-patch-fetch.js');
config.files.push('dist/zone-patch-canvas.js');
config.files.push('dist/webapis-media-query.js');
config.files.push('dist/webapis-notification.js');
config.files.push('dist/zone-patch-user-media.js');
config.files.push('dist/zone-patch-resize-observer.js');
config.files.push('dist/task-tracking.js');
config.files.push('dist/wtf.js');
config.files.push('dist/zone-testing.js');
config.files.push('build/test/main.js');
};

View File

@ -0,0 +1,7 @@
module.exports = function(config) {
require('./karma-evergreen-dist.conf.js')(config);
config.plugins.push(require('karma-jasmine'));
config.frameworks.push('jasmine');
};

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
require('./karma-evergreen-dist-jasmine.conf.js')(config);
require('./sauce-evergreen.conf')(config);
};

View File

@ -0,0 +1,35 @@
/**
* @license
* Copyright Google Inc. 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
*/
module.exports = function(config) {
require('./karma-base.conf.js')(config);
const files = config.files;
config.files = [];
for (let i = 0; i < files.length; i++) {
if (files[i] !== 'node_modules/core-js-bundle/index.js') {
config.files.push(files[i]);
}
}
config.files.push('build/test/browser-env-setup.js');
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({pattern: 'dist/zone-evergreen.js', type: 'module'});
config.files.push('dist/zone-patch-canvas.js');
config.files.push('dist/zone-patch-fetch.js');
config.files.push('dist/webapis-media-query.js');
config.files.push('dist/webapis-notification.js');
config.files.push('dist/zone-patch-user-media.js');
config.files.push('dist/zone-patch-resize-observer.js');
config.files.push('dist/task-tracking.js');
config.files.push('dist/wtf.js');
config.files.push('dist/zone-testing.js');
config.files.push({pattern: 'build/test/browser/custom-element.spec.js', type: 'module'});
config.files.push('build/test/main.js');
};

View File

@ -0,0 +1,18 @@
load("@npm_bazel_typescript//:defs.bzl", "ts_library")
package(default_visibility = ["//packages/zone.js:__pkg__"])
exports_files(glob([
"**/*",
]))
ts_library(
name = "lib",
srcs = glob(["**/*.ts"]),
visibility = ["//packages/zone.js:__subpackages__"],
deps = [
"@npm//@types/jasmine",
"@npm//@types/node",
"@npm//rxjs",
],
)

View File

@ -0,0 +1,52 @@
/**
* @license
* Copyright Google Inc. 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 {globalSources, patchEventPrototype, patchEventTarget, zoneSymbolEventNames} from '../common/events';
import {ADD_EVENT_LISTENER_STR, ArraySlice, FALSE_STR, ObjectCreate, ObjectDefineProperty, ObjectGetOwnPropertyDescriptor, REMOVE_EVENT_LISTENER_STR, TRUE_STR, ZONE_SYMBOL_PREFIX, attachOriginToPatched, bindArguments, isBrowser, isIEOrEdge, isMix, isNode, patchClass, patchMacroTask, patchMethod, patchOnProperties, wrapWithCurrentZone} from '../common/utils';
import {patchCallbacks} from './browser-util';
import {_redefineProperty} from './define-property';
import {eventNames, filterProperties} from './property-descriptor';
Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
api.patchOnProperties = patchOnProperties;
api.patchMethod = patchMethod;
api.bindArguments = bindArguments;
api.patchMacroTask = patchMacroTask;
// In earlier version of zone.js (<0.9.0), we use env name `__zone_symbol__BLACK_LISTED_EVENTS` to
// define which events will not be patched by `Zone.js`.
// In newer version (>=0.9.0), we change the env name to `__zone_symbol__UNPATCHED_EVENTS` to keep
// the name consistent with angular repo.
// The `__zone_symbol__BLACK_LISTED_EVENTS` is deprecated, but it is still be supported for
// backwards compatibility.
const SYMBOL_BLACK_LISTED_EVENTS = Zone.__symbol__('BLACK_LISTED_EVENTS');
const SYMBOL_UNPATCHED_EVENTS = Zone.__symbol__('UNPATCHED_EVENTS');
if (global[SYMBOL_UNPATCHED_EVENTS]) {
global[SYMBOL_BLACK_LISTED_EVENTS] = global[SYMBOL_UNPATCHED_EVENTS];
}
if (global[SYMBOL_BLACK_LISTED_EVENTS]) {
(Zone as any)[SYMBOL_BLACK_LISTED_EVENTS] = (Zone as any)[SYMBOL_UNPATCHED_EVENTS] =
global[SYMBOL_BLACK_LISTED_EVENTS];
}
api.patchEventPrototype = patchEventPrototype;
api.patchEventTarget = patchEventTarget;
api.isIEOrEdge = isIEOrEdge;
api.ObjectDefineProperty = ObjectDefineProperty;
api.ObjectGetOwnPropertyDescriptor = ObjectGetOwnPropertyDescriptor;
api.ObjectCreate = ObjectCreate;
api.ArraySlice = ArraySlice;
api.patchClass = patchClass;
api.wrapWithCurrentZone = wrapWithCurrentZone;
api.filterProperties = filterProperties;
api.attachOriginToPatched = attachOriginToPatched;
api._redefineProperty = _redefineProperty;
api.patchCallbacks = patchCallbacks;
api.getGlobalObjects = () =>
({globalSources, zoneSymbolEventNames, eventNames, isBrowser, isMix, isNode, TRUE_STR,
FALSE_STR, ZONE_SYMBOL_PREFIX, ADD_EVENT_LISTENER_STR, REMOVE_EVENT_LISTENER_STR});
});

View File

@ -0,0 +1,31 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @fileoverview
* @suppress {missingRequire}
*/
import {eventTargetLegacyPatch} from './event-target-legacy';
import {propertyDescriptorLegacyPatch} from './property-descriptor-legacy';
import {registerElementPatch} from './register-element';
(function(_global: any) {
_global[Zone.__symbol__('legacyPatch')] = function() {
const Zone = _global['Zone'];
Zone.__load_patch('registerElement', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
registerElementPatch(global, api);
});
Zone.__load_patch('EventTargetLegacy', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
eventTargetLegacyPatch(global, api);
propertyDescriptorLegacyPatch(api, global);
});
};
})(typeof window !== 'undefined' ?
window :
typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {});

View File

@ -0,0 +1,38 @@
/**
* @license
* Copyright Google Inc. 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
*/
export function patchCallbacks(
api: _ZonePrivate, target: any, targetName: string, method: string, callbacks: string[]) {
const symbol = Zone.__symbol__(method);
if (target[symbol]) {
return;
}
const nativeDelegate = target[symbol] = target[method];
target[method] = function(name: any, opts: any, options?: any) {
if (opts && opts.prototype) {
callbacks.forEach(function(callback) {
const source = `${targetName}.${method}::` + callback;
const prototype = opts.prototype;
if (prototype.hasOwnProperty(callback)) {
const descriptor = api.ObjectGetOwnPropertyDescriptor(prototype, callback);
if (descriptor && descriptor.value) {
descriptor.value = api.wrapWithCurrentZone(descriptor.value, source);
api._redefineProperty(opts.prototype, callback, descriptor);
} else if (prototype[callback]) {
prototype[callback] = api.wrapWithCurrentZone(prototype[callback], source);
}
} else if (prototype[callback]) {
prototype[callback] = api.wrapWithCurrentZone(prototype[callback], source);
}
});
}
return nativeDelegate.call(target, name, opts, options);
};
api.attachOriginToPatched(target[method], nativeDelegate);
}

View File

@ -0,0 +1,280 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @fileoverview
* @suppress {missingRequire}
*/
import {findEventTasks} from '../common/events';
import {patchTimer} from '../common/timers';
import {ZONE_SYMBOL_ADD_EVENT_LISTENER, ZONE_SYMBOL_REMOVE_EVENT_LISTENER, patchClass, patchMethod, patchPrototype, scheduleMacroTaskWithCurrentZone, zoneSymbol} from '../common/utils';
import {patchCustomElements} from './custom-elements';
import {propertyPatch} from './define-property';
import {eventTargetPatch, patchEvent} from './event-target';
import {propertyDescriptorPatch} from './property-descriptor';
Zone.__load_patch('legacy', (global: any) => {
const legacyPatch = global[Zone.__symbol__('legacyPatch')];
if (legacyPatch) {
legacyPatch();
}
});
Zone.__load_patch('timers', (global: any) => {
const set = 'set';
const clear = 'clear';
patchTimer(global, set, clear, 'Timeout');
patchTimer(global, set, clear, 'Interval');
patchTimer(global, set, clear, 'Immediate');
});
Zone.__load_patch('requestAnimationFrame', (global: any) => {
patchTimer(global, 'request', 'cancel', 'AnimationFrame');
patchTimer(global, 'mozRequest', 'mozCancel', 'AnimationFrame');
patchTimer(global, 'webkitRequest', 'webkitCancel', 'AnimationFrame');
});
Zone.__load_patch('blocking', (global: any, Zone: ZoneType) => {
const blockingMethods = ['alert', 'prompt', 'confirm'];
for (let i = 0; i < blockingMethods.length; i++) {
const name = blockingMethods[i];
patchMethod(global, name, (delegate, symbol, name) => {
return function(s: any, args: any[]) {
return Zone.current.run(delegate, global, args, name);
};
});
}
});
Zone.__load_patch('EventTarget', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
patchEvent(global, api);
eventTargetPatch(global, api);
// patch XMLHttpRequestEventTarget's addEventListener/removeEventListener
const XMLHttpRequestEventTarget = (global as any)['XMLHttpRequestEventTarget'];
if (XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype) {
api.patchEventTarget(global, [XMLHttpRequestEventTarget.prototype]);
}
patchClass('MutationObserver');
patchClass('WebKitMutationObserver');
patchClass('IntersectionObserver');
patchClass('FileReader');
});
Zone.__load_patch('on_property', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
propertyDescriptorPatch(api, global);
propertyPatch();
});
Zone.__load_patch('customElements', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
patchCustomElements(global, api);
});
Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
// Treat XMLHttpRequest as a macrotask.
patchXHR(global);
const XHR_TASK = zoneSymbol('xhrTask');
const XHR_SYNC = zoneSymbol('xhrSync');
const XHR_LISTENER = zoneSymbol('xhrListener');
const XHR_SCHEDULED = zoneSymbol('xhrScheduled');
const XHR_URL = zoneSymbol('xhrURL');
const XHR_ERROR_BEFORE_SCHEDULED = zoneSymbol('xhrErrorBeforeScheduled');
interface XHROptions extends TaskData {
target: any;
url: string;
args: any[];
aborted: boolean;
}
function patchXHR(window: any) {
const XMLHttpRequest = window['XMLHttpRequest'];
if (!XMLHttpRequest) {
// XMLHttpRequest is not available in service worker
return;
}
const XMLHttpRequestPrototype: any = XMLHttpRequest.prototype;
function findPendingTask(target: any) { return target[XHR_TASK]; }
let oriAddListener = XMLHttpRequestPrototype[ZONE_SYMBOL_ADD_EVENT_LISTENER];
let oriRemoveListener = XMLHttpRequestPrototype[ZONE_SYMBOL_REMOVE_EVENT_LISTENER];
if (!oriAddListener) {
const XMLHttpRequestEventTarget = window['XMLHttpRequestEventTarget'];
if (XMLHttpRequestEventTarget) {
const XMLHttpRequestEventTargetPrototype = XMLHttpRequestEventTarget.prototype;
oriAddListener = XMLHttpRequestEventTargetPrototype[ZONE_SYMBOL_ADD_EVENT_LISTENER];
oriRemoveListener = XMLHttpRequestEventTargetPrototype[ZONE_SYMBOL_REMOVE_EVENT_LISTENER];
}
}
const READY_STATE_CHANGE = 'readystatechange';
const SCHEDULED = 'scheduled';
function scheduleTask(task: Task) {
const data = <XHROptions>task.data;
const target = data.target;
target[XHR_SCHEDULED] = false;
target[XHR_ERROR_BEFORE_SCHEDULED] = false;
// remove existing event listener
const listener = target[XHR_LISTENER];
if (!oriAddListener) {
oriAddListener = target[ZONE_SYMBOL_ADD_EVENT_LISTENER];
oriRemoveListener = target[ZONE_SYMBOL_REMOVE_EVENT_LISTENER];
}
if (listener) {
oriRemoveListener.call(target, READY_STATE_CHANGE, listener);
}
const newListener = target[XHR_LISTENER] = () => {
if (target.readyState === target.DONE) {
// sometimes on some browsers XMLHttpRequest will fire onreadystatechange with
// readyState=4 multiple times, so we need to check task state here
if (!data.aborted && target[XHR_SCHEDULED] && task.state === SCHEDULED) {
// check whether the xhr has registered onload listener
// if that is the case, the task should invoke after all
// onload listeners finish.
const loadTasks = target[Zone.__symbol__('loadfalse')];
if (loadTasks && loadTasks.length > 0) {
const oriInvoke = task.invoke;
task.invoke = function() {
// need to load the tasks again, because in other
// load listener, they may remove themselves
const loadTasks = target[Zone.__symbol__('loadfalse')];
for (let i = 0; i < loadTasks.length; i++) {
if (loadTasks[i] === task) {
loadTasks.splice(i, 1);
}
}
if (!data.aborted && task.state === SCHEDULED) {
oriInvoke.call(task);
}
};
loadTasks.push(task);
} else {
task.invoke();
}
} else if (!data.aborted && target[XHR_SCHEDULED] === false) {
// error occurs when xhr.send()
target[XHR_ERROR_BEFORE_SCHEDULED] = true;
}
}
};
oriAddListener.call(target, READY_STATE_CHANGE, newListener);
const storedTask: Task = target[XHR_TASK];
if (!storedTask) {
target[XHR_TASK] = task;
}
sendNative !.apply(target, data.args);
target[XHR_SCHEDULED] = true;
return task;
}
function placeholderCallback() {}
function clearTask(task: Task) {
const data = <XHROptions>task.data;
// Note - ideally, we would call data.target.removeEventListener here, but it's too late
// to prevent it from firing. So instead, we store info for the event listener.
data.aborted = true;
return abortNative !.apply(data.target, data.args);
}
const openNative =
patchMethod(XMLHttpRequestPrototype, 'open', () => function(self: any, args: any[]) {
self[XHR_SYNC] = args[2] == false;
self[XHR_URL] = args[1];
return openNative !.apply(self, args);
});
const XMLHTTPREQUEST_SOURCE = 'XMLHttpRequest.send';
const fetchTaskAborting = zoneSymbol('fetchTaskAborting');
const fetchTaskScheduling = zoneSymbol('fetchTaskScheduling');
const sendNative: Function|null =
patchMethod(XMLHttpRequestPrototype, 'send', () => function(self: any, args: any[]) {
if ((Zone.current as any)[fetchTaskScheduling] === true) {
// a fetch is scheduling, so we are using xhr to polyfill fetch
// and because we already schedule macroTask for fetch, we should
// not schedule a macroTask for xhr again
return sendNative !.apply(self, args);
}
if (self[XHR_SYNC]) {
// if the XHR is sync there is no task to schedule, just execute the code.
return sendNative !.apply(self, args);
} else {
const options: XHROptions =
{target: self, url: self[XHR_URL], isPeriodic: false, args: args, aborted: false};
const task = scheduleMacroTaskWithCurrentZone(
XMLHTTPREQUEST_SOURCE, placeholderCallback, options, scheduleTask, clearTask);
if (self && self[XHR_ERROR_BEFORE_SCHEDULED] === true && !options.aborted &&
task.state === SCHEDULED) {
// xhr request throw error when send
// we should invoke task instead of leaving a scheduled
// pending macroTask
task.invoke();
}
}
});
const abortNative =
patchMethod(XMLHttpRequestPrototype, 'abort', () => function(self: any, args: any[]) {
const task: Task = findPendingTask(self);
if (task && typeof task.type == 'string') {
// If the XHR has already completed, do nothing.
// If the XHR has already been aborted, do nothing.
// Fix #569, call abort multiple times before done will cause
// macroTask task count be negative number
if (task.cancelFn == null || (task.data && (<XHROptions>task.data).aborted)) {
return;
}
task.zone.cancelTask(task);
} else if ((Zone.current as any)[fetchTaskAborting] === true) {
// the abort is called from fetch polyfill, we need to call native abort of XHR.
return abortNative !.apply(self, args);
}
// Otherwise, we are trying to abort an XHR which has not yet been sent, so there is no
// task
// to cancel. Do nothing.
});
}
});
Zone.__load_patch('geolocation', (global: any) => {
/// GEO_LOCATION
if (global['navigator'] && global['navigator'].geolocation) {
patchPrototype(global['navigator'].geolocation, ['getCurrentPosition', 'watchPosition']);
}
});
Zone.__load_patch('PromiseRejectionEvent', (global: any, Zone: ZoneType) => {
// handle unhandled promise rejection
function findPromiseRejectionHandler(evtName: string) {
return function(e: any) {
const eventTasks = findEventTasks(global, evtName);
eventTasks.forEach(eventTask => {
// windows has added unhandledrejection event listener
// trigger the event listener
const PromiseRejectionEvent = global['PromiseRejectionEvent'];
if (PromiseRejectionEvent) {
const evt = new PromiseRejectionEvent(evtName, {promise: e.promise, reason: e.rejection});
eventTask.invoke(evt);
}
});
};
}
if (global['PromiseRejectionEvent']) {
(Zone as any)[zoneSymbol('unhandledPromiseRejectionHandler')] =
findPromiseRejectionHandler('unhandledrejection');
(Zone as any)[zoneSymbol('rejectionHandledHandler')] =
findPromiseRejectionHandler('rejectionhandled');
}
});

View File

@ -0,0 +1,16 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('canvas', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
const HTMLCanvasElement = global['HTMLCanvasElement'];
if (typeof HTMLCanvasElement !== 'undefined' && HTMLCanvasElement.prototype &&
HTMLCanvasElement.prototype.toBlob) {
api.patchMacroTask(HTMLCanvasElement.prototype, 'toBlob', (self: any, args: any[]) => {
return {name: 'HTMLCanvasElement.toBlob', target: self, cbIdx: 0, args: args};
});
}
});

View File

@ -0,0 +1,19 @@
/**
* @license
* Copyright Google Inc. 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
*/
export function patchCustomElements(_global: any, api: _ZonePrivate) {
const {isBrowser, isMix} = api.getGlobalObjects() !;
if ((!isBrowser && !isMix) || !_global['customElements'] || !('customElements' in _global)) {
return;
}
const callbacks =
['connectedCallback', 'disconnectedCallback', 'adoptedCallback', 'attributeChangedCallback'];
api.patchCallbacks(api, _global.customElements, 'customElements', 'define', callbacks);
}

View File

@ -0,0 +1,111 @@
/**
* @license
* Copyright Google Inc. 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
*/
/*
* This is necessary for Chrome and Chrome mobile, to enable
* things like redefining `createdCallback` on an element.
*/
const zoneSymbol = Zone.__symbol__;
const _defineProperty = (Object as any)[zoneSymbol('defineProperty')] = Object.defineProperty;
const _getOwnPropertyDescriptor = (Object as any)[zoneSymbol('getOwnPropertyDescriptor')] =
Object.getOwnPropertyDescriptor;
const _create = Object.create;
const unconfigurablesKey = zoneSymbol('unconfigurables');
export function propertyPatch() {
Object.defineProperty = function(obj: any, prop: string, desc: any) {
if (isUnconfigurable(obj, prop)) {
throw new TypeError('Cannot assign to read only property \'' + prop + '\' of ' + obj);
}
const originalConfigurableFlag = desc.configurable;
if (prop !== 'prototype') {
desc = rewriteDescriptor(obj, prop, desc);
}
return _tryDefineProperty(obj, prop, desc, originalConfigurableFlag);
};
Object.defineProperties = function(obj, props) {
Object.keys(props).forEach(function(prop) { Object.defineProperty(obj, prop, props[prop]); });
return obj;
};
Object.create = <any>function(obj: any, proto: any) {
if (typeof proto === 'object' && !Object.isFrozen(proto)) {
Object.keys(proto).forEach(function(prop) {
proto[prop] = rewriteDescriptor(obj, prop, proto[prop]);
});
}
return _create(obj, proto);
};
Object.getOwnPropertyDescriptor = function(obj, prop) {
const desc = _getOwnPropertyDescriptor(obj, prop);
if (desc && isUnconfigurable(obj, prop)) {
desc.configurable = false;
}
return desc;
};
}
export function _redefineProperty(obj: any, prop: string, desc: any) {
const originalConfigurableFlag = desc.configurable;
desc = rewriteDescriptor(obj, prop, desc);
return _tryDefineProperty(obj, prop, desc, originalConfigurableFlag);
}
function isUnconfigurable(obj: any, prop: any) {
return obj && obj[unconfigurablesKey] && obj[unconfigurablesKey][prop];
}
function rewriteDescriptor(obj: any, prop: string, desc: any) {
// issue-927, if the desc is frozen, don't try to change the desc
if (!Object.isFrozen(desc)) {
desc.configurable = true;
}
if (!desc.configurable) {
// issue-927, if the obj is frozen, don't try to set the desc to obj
if (!obj[unconfigurablesKey] && !Object.isFrozen(obj)) {
_defineProperty(obj, unconfigurablesKey, {writable: true, value: {}});
}
if (obj[unconfigurablesKey]) {
obj[unconfigurablesKey][prop] = true;
}
}
return desc;
}
function _tryDefineProperty(obj: any, prop: string, desc: any, originalConfigurableFlag: any) {
try {
return _defineProperty(obj, prop, desc);
} catch (error) {
if (desc.configurable) {
// In case of errors, when the configurable flag was likely set by rewriteDescriptor(), let's
// retry with the original flag value
if (typeof originalConfigurableFlag == 'undefined') {
delete desc.configurable;
} else {
desc.configurable = originalConfigurableFlag;
}
try {
return _defineProperty(obj, prop, desc);
} catch (error) {
let descJson: string|null = null;
try {
descJson = JSON.stringify(desc);
} catch (error) {
descJson = desc.toString();
}
console.log(`Attempting to configure '${prop}' with descriptor '${descJson}' on object '${
obj}' and got error, giving up: ${error}`);
}
} else {
throw error;
}
}
}

View File

@ -0,0 +1,110 @@
/**
* @license
* Copyright Google Inc. 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
*/
export function eventTargetLegacyPatch(_global: any, api: _ZonePrivate) {
const {eventNames, globalSources, zoneSymbolEventNames, TRUE_STR, FALSE_STR, ZONE_SYMBOL_PREFIX} =
api.getGlobalObjects() !;
const WTF_ISSUE_555 =
'Anchor,Area,Audio,BR,Base,BaseFont,Body,Button,Canvas,Content,DList,Directory,Div,Embed,FieldSet,Font,Form,Frame,FrameSet,HR,Head,Heading,Html,IFrame,Image,Input,Keygen,LI,Label,Legend,Link,Map,Marquee,Media,Menu,Meta,Meter,Mod,OList,Object,OptGroup,Option,Output,Paragraph,Pre,Progress,Quote,Script,Select,Source,Span,Style,TableCaption,TableCell,TableCol,Table,TableRow,TableSection,TextArea,Title,Track,UList,Unknown,Video';
const NO_EVENT_TARGET =
'ApplicationCache,EventSource,FileReader,InputMethodContext,MediaController,MessagePort,Node,Performance,SVGElementInstance,SharedWorker,TextTrack,TextTrackCue,TextTrackList,WebKitNamedFlow,Window,Worker,WorkerGlobalScope,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload,IDBRequest,IDBOpenDBRequest,IDBDatabase,IDBTransaction,IDBCursor,DBIndex,WebSocket'
.split(',');
const EVENT_TARGET = 'EventTarget';
let apis: any[] = [];
const isWtf = _global['wtf'];
const WTF_ISSUE_555_ARRAY = WTF_ISSUE_555.split(',');
if (isWtf) {
// Workaround for: https://github.com/google/tracing-framework/issues/555
apis = WTF_ISSUE_555_ARRAY.map((v) => 'HTML' + v + 'Element').concat(NO_EVENT_TARGET);
} else if (_global[EVENT_TARGET]) {
apis.push(EVENT_TARGET);
} else {
// Note: EventTarget is not available in all browsers,
// if it's not available, we instead patch the APIs in the IDL that inherit from EventTarget
apis = NO_EVENT_TARGET;
}
const isDisableIECheck = _global['__Zone_disable_IE_check'] || false;
const isEnableCrossContextCheck = _global['__Zone_enable_cross_context_check'] || false;
const ieOrEdge = api.isIEOrEdge();
const ADD_EVENT_LISTENER_SOURCE = '.addEventListener:';
const FUNCTION_WRAPPER = '[object FunctionWrapper]';
const BROWSER_TOOLS = 'function __BROWSERTOOLS_CONSOLE_SAFEFUNC() { [native code] }';
// predefine all __zone_symbol__ + eventName + true/false string
for (let i = 0; i < eventNames.length; i++) {
const eventName = eventNames[i];
const falseEventName = eventName + FALSE_STR;
const trueEventName = eventName + TRUE_STR;
const symbol = ZONE_SYMBOL_PREFIX + falseEventName;
const symbolCapture = ZONE_SYMBOL_PREFIX + trueEventName;
zoneSymbolEventNames[eventName] = {};
zoneSymbolEventNames[eventName][FALSE_STR] = symbol;
zoneSymbolEventNames[eventName][TRUE_STR] = symbolCapture;
}
// predefine all task.source string
for (let i = 0; i < WTF_ISSUE_555.length; i++) {
const target: any = WTF_ISSUE_555_ARRAY[i];
const targets: any = globalSources[target] = {};
for (let j = 0; j < eventNames.length; j++) {
const eventName = eventNames[j];
targets[eventName] = target + ADD_EVENT_LISTENER_SOURCE + eventName;
}
}
const checkIEAndCrossContext = function(
nativeDelegate: any, delegate: any, target: any, args: any) {
if (!isDisableIECheck && ieOrEdge) {
if (isEnableCrossContextCheck) {
try {
const testString = delegate.toString();
if ((testString === FUNCTION_WRAPPER || testString == BROWSER_TOOLS)) {
nativeDelegate.apply(target, args);
return false;
}
} catch (error) {
nativeDelegate.apply(target, args);
return false;
}
} else {
const testString = delegate.toString();
if ((testString === FUNCTION_WRAPPER || testString == BROWSER_TOOLS)) {
nativeDelegate.apply(target, args);
return false;
}
}
} else if (isEnableCrossContextCheck) {
try {
delegate.toString();
} catch (error) {
nativeDelegate.apply(target, args);
return false;
}
}
return true;
};
const apiTypes: any[] = [];
for (let i = 0; i < apis.length; i++) {
const type = _global[apis[i]];
apiTypes.push(type && type.prototype);
}
// vh is validateHandler to check event handler
// is valid or not(for security check)
api.patchEventTarget(_global, apiTypes, {vh: checkIEAndCrossContext});
(Zone as any)[api.symbol('patchEventTarget')] = !!_global[EVENT_TARGET];
return true;
}
export function patchEvent(global: any, api: _ZonePrivate) {
api.patchEventPrototype(global, api);
}

View File

@ -0,0 +1,39 @@
/**
* @license
* Copyright Google Inc. 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
*/
export function eventTargetPatch(_global: any, api: _ZonePrivate) {
if ((Zone as any)[api.symbol('patchEventTarget')]) {
// EventTarget is already patched.
return;
}
const {eventNames, zoneSymbolEventNames, TRUE_STR, FALSE_STR, ZONE_SYMBOL_PREFIX} =
api.getGlobalObjects() !;
// predefine all __zone_symbol__ + eventName + true/false string
for (let i = 0; i < eventNames.length; i++) {
const eventName = eventNames[i];
const falseEventName = eventName + FALSE_STR;
const trueEventName = eventName + TRUE_STR;
const symbol = ZONE_SYMBOL_PREFIX + falseEventName;
const symbolCapture = ZONE_SYMBOL_PREFIX + trueEventName;
zoneSymbolEventNames[eventName] = {};
zoneSymbolEventNames[eventName][FALSE_STR] = symbol;
zoneSymbolEventNames[eventName][TRUE_STR] = symbolCapture;
}
const EVENT_TARGET = _global['EventTarget'];
if (!EVENT_TARGET || !EVENT_TARGET.prototype) {
return;
}
api.patchEventTarget(_global, [EVENT_TARGET && EVENT_TARGET.prototype]);
return true;
}
export function patchEvent(global: any, api: _ZonePrivate) {
api.patchEventPrototype(global, api);
}

View File

@ -0,0 +1,124 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @fileoverview
* @suppress {globalThis}
*/
import * as webSocketPatch from './websocket';
export function propertyDescriptorLegacyPatch(api: _ZonePrivate, _global: any) {
const {isNode, isMix} = api.getGlobalObjects() !;
if (isNode && !isMix) {
return;
}
if (!canPatchViaPropertyDescriptor(api, _global)) {
const supportsWebSocket = typeof WebSocket !== 'undefined';
// Safari, Android browsers (Jelly Bean)
patchViaCapturingAllTheEvents(api);
api.patchClass('XMLHttpRequest');
if (supportsWebSocket) {
webSocketPatch.apply(api, _global);
}
(Zone as any)[api.symbol('patchEvents')] = true;
}
}
function canPatchViaPropertyDescriptor(api: _ZonePrivate, _global: any) {
const {isBrowser, isMix} = api.getGlobalObjects() !;
if ((isBrowser || isMix) &&
!api.ObjectGetOwnPropertyDescriptor(HTMLElement.prototype, 'onclick') &&
typeof Element !== 'undefined') {
// WebKit https://bugs.webkit.org/show_bug.cgi?id=134364
// IDL interface attributes are not configurable
const desc = api.ObjectGetOwnPropertyDescriptor(Element.prototype, 'onclick');
if (desc && !desc.configurable) return false;
// try to use onclick to detect whether we can patch via propertyDescriptor
// because XMLHttpRequest is not available in service worker
if (desc) {
api.ObjectDefineProperty(
Element.prototype, 'onclick',
{enumerable: true, configurable: true, get: function() { return true; }});
const div = document.createElement('div');
const result = !!div.onclick;
api.ObjectDefineProperty(Element.prototype, 'onclick', desc);
return result;
}
}
const XMLHttpRequest = _global['XMLHttpRequest'];
if (!XMLHttpRequest) {
// XMLHttpRequest is not available in service worker
return false;
}
const ON_READY_STATE_CHANGE = 'onreadystatechange';
const XMLHttpRequestPrototype = XMLHttpRequest.prototype;
const xhrDesc =
api.ObjectGetOwnPropertyDescriptor(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE);
// add enumerable and configurable here because in opera
// by default XMLHttpRequest.prototype.onreadystatechange is undefined
// without adding enumerable and configurable will cause onreadystatechange
// non-configurable
// and if XMLHttpRequest.prototype.onreadystatechange is undefined,
// we should set a real desc instead a fake one
if (xhrDesc) {
api.ObjectDefineProperty(
XMLHttpRequestPrototype, ON_READY_STATE_CHANGE,
{enumerable: true, configurable: true, get: function() { return true; }});
const req = new XMLHttpRequest();
const result = !!req.onreadystatechange;
// restore original desc
api.ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, xhrDesc || {});
return result;
} else {
const SYMBOL_FAKE_ONREADYSTATECHANGE = api.symbol('fake');
api.ObjectDefineProperty(XMLHttpRequestPrototype, ON_READY_STATE_CHANGE, {
enumerable: true,
configurable: true,
get: function() { return this[SYMBOL_FAKE_ONREADYSTATECHANGE]; },
set: function(value) { this[SYMBOL_FAKE_ONREADYSTATECHANGE] = value; }
});
const req = new XMLHttpRequest();
const detectFunc = () => {};
req.onreadystatechange = detectFunc;
const result = (req as any)[SYMBOL_FAKE_ONREADYSTATECHANGE] === detectFunc;
req.onreadystatechange = null as any;
return result;
}
}
// Whenever any eventListener fires, we check the eventListener target and all parents
// for `onwhatever` properties and replace them with zone-bound functions
// - Chrome (for now)
function patchViaCapturingAllTheEvents(api: _ZonePrivate) {
const {eventNames} = api.getGlobalObjects() !;
const unboundKey = api.symbol('unbound');
for (let i = 0; i < eventNames.length; i++) {
const property = eventNames[i];
const onproperty = 'on' + property;
self.addEventListener(property, function(event) {
let elt: any = <Node>event.target, bound, source;
if (elt) {
source = elt.constructor['name'] + '.' + onproperty;
} else {
source = 'unknown.' + onproperty;
}
while (elt) {
if (elt[onproperty] && !elt[onproperty][unboundKey]) {
bound = api.wrapWithCurrentZone(elt[onproperty], source);
bound[unboundKey] = elt[onproperty];
elt[onproperty] = bound;
}
elt = elt.parentElement;
}
}, true);
}
}

View File

@ -0,0 +1,334 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @fileoverview
* @suppress {globalThis}
*/
import {ObjectGetPrototypeOf, isBrowser, isIE, isMix, isNode, patchOnProperties} from '../common/utils';
const globalEventHandlersEventNames = [
'abort',
'animationcancel',
'animationend',
'animationiteration',
'auxclick',
'beforeinput',
'blur',
'cancel',
'canplay',
'canplaythrough',
'change',
'compositionstart',
'compositionupdate',
'compositionend',
'cuechange',
'click',
'close',
'contextmenu',
'curechange',
'dblclick',
'drag',
'dragend',
'dragenter',
'dragexit',
'dragleave',
'dragover',
'drop',
'durationchange',
'emptied',
'ended',
'error',
'focus',
'focusin',
'focusout',
'gotpointercapture',
'input',
'invalid',
'keydown',
'keypress',
'keyup',
'load',
'loadstart',
'loadeddata',
'loadedmetadata',
'lostpointercapture',
'mousedown',
'mouseenter',
'mouseleave',
'mousemove',
'mouseout',
'mouseover',
'mouseup',
'mousewheel',
'orientationchange',
'pause',
'play',
'playing',
'pointercancel',
'pointerdown',
'pointerenter',
'pointerleave',
'pointerlockchange',
'mozpointerlockchange',
'webkitpointerlockerchange',
'pointerlockerror',
'mozpointerlockerror',
'webkitpointerlockerror',
'pointermove',
'pointout',
'pointerover',
'pointerup',
'progress',
'ratechange',
'reset',
'resize',
'scroll',
'seeked',
'seeking',
'select',
'selectionchange',
'selectstart',
'show',
'sort',
'stalled',
'submit',
'suspend',
'timeupdate',
'volumechange',
'touchcancel',
'touchmove',
'touchstart',
'touchend',
'transitioncancel',
'transitionend',
'waiting',
'wheel'
];
const documentEventNames = [
'afterscriptexecute', 'beforescriptexecute', 'DOMContentLoaded', 'freeze', 'fullscreenchange',
'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange', 'fullscreenerror',
'mozfullscreenerror', 'webkitfullscreenerror', 'msfullscreenerror', 'readystatechange',
'visibilitychange', 'resume'
];
const windowEventNames = [
'absolutedeviceorientation',
'afterinput',
'afterprint',
'appinstalled',
'beforeinstallprompt',
'beforeprint',
'beforeunload',
'devicelight',
'devicemotion',
'deviceorientation',
'deviceorientationabsolute',
'deviceproximity',
'hashchange',
'languagechange',
'message',
'mozbeforepaint',
'offline',
'online',
'paint',
'pageshow',
'pagehide',
'popstate',
'rejectionhandled',
'storage',
'unhandledrejection',
'unload',
'userproximity',
'vrdisplyconnected',
'vrdisplaydisconnected',
'vrdisplaypresentchange'
];
const htmlElementEventNames = [
'beforecopy', 'beforecut', 'beforepaste', 'copy', 'cut', 'paste', 'dragstart', 'loadend',
'animationstart', 'search', 'transitionrun', 'transitionstart', 'webkitanimationend',
'webkitanimationiteration', 'webkitanimationstart', 'webkittransitionend'
];
const mediaElementEventNames =
['encrypted', 'waitingforkey', 'msneedkey', 'mozinterruptbegin', 'mozinterruptend'];
const ieElementEventNames = [
'activate',
'afterupdate',
'ariarequest',
'beforeactivate',
'beforedeactivate',
'beforeeditfocus',
'beforeupdate',
'cellchange',
'controlselect',
'dataavailable',
'datasetchanged',
'datasetcomplete',
'errorupdate',
'filterchange',
'layoutcomplete',
'losecapture',
'move',
'moveend',
'movestart',
'propertychange',
'resizeend',
'resizestart',
'rowenter',
'rowexit',
'rowsdelete',
'rowsinserted',
'command',
'compassneedscalibration',
'deactivate',
'help',
'mscontentzoom',
'msmanipulationstatechanged',
'msgesturechange',
'msgesturedoubletap',
'msgestureend',
'msgesturehold',
'msgesturestart',
'msgesturetap',
'msgotpointercapture',
'msinertiastart',
'mslostpointercapture',
'mspointercancel',
'mspointerdown',
'mspointerenter',
'mspointerhover',
'mspointerleave',
'mspointermove',
'mspointerout',
'mspointerover',
'mspointerup',
'pointerout',
'mssitemodejumplistitemremoved',
'msthumbnailclick',
'stop',
'storagecommit'
];
const webglEventNames = ['webglcontextrestored', 'webglcontextlost', 'webglcontextcreationerror'];
const formEventNames = ['autocomplete', 'autocompleteerror'];
const detailEventNames = ['toggle'];
const frameEventNames = ['load'];
const frameSetEventNames = ['blur', 'error', 'focus', 'load', 'resize', 'scroll', 'messageerror'];
const marqueeEventNames = ['bounce', 'finish', 'start'];
const XMLHttpRequestEventNames = [
'loadstart', 'progress', 'abort', 'error', 'load', 'progress', 'timeout', 'loadend',
'readystatechange'
];
const IDBIndexEventNames =
['upgradeneeded', 'complete', 'abort', 'success', 'error', 'blocked', 'versionchange', 'close'];
const websocketEventNames = ['close', 'error', 'open', 'message'];
const workerEventNames = ['error', 'message'];
export const eventNames = globalEventHandlersEventNames.concat(
webglEventNames, formEventNames, detailEventNames, documentEventNames, windowEventNames,
htmlElementEventNames, ieElementEventNames);
export interface IgnoreProperty {
target: any;
ignoreProperties: string[];
}
export function filterProperties(
target: any, onProperties: string[], ignoreProperties: IgnoreProperty[]): string[] {
if (!ignoreProperties || ignoreProperties.length === 0) {
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) {
// check whether target is available, sometimes target will be undefined
// because different browser or some 3rd party plugin.
if (!target) {
return;
}
const filteredProperties: string[] = filterProperties(target, onProperties, ignoreProperties);
patchOnProperties(target, filteredProperties, prototype);
}
export function propertyDescriptorPatch(api: _ZonePrivate, _global: any) {
if (isNode && !isMix) {
return;
}
if ((Zone as any)[api.symbol('patchEvents')]) {
// events are already been patched by legacy patch.
return;
}
const supportsWebSocket = typeof WebSocket !== 'undefined';
const ignoreProperties: IgnoreProperty[] = _global['__Zone_ignore_on_properties'];
// for browsers that we can patch the descriptor: Chrome & Firefox
if (isBrowser) {
const internalWindow: any = window;
const ignoreErrorProperties =
isIE ? [{target: internalWindow, ignoreProperties: ['error']}] : [];
// in IE/Edge, onProp not exist in window object, but in WindowPrototype
// so we need to pass WindowPrototype to check onProp exist or not
patchFilteredProperties(
internalWindow, eventNames.concat(['messageerror']),
ignoreProperties ? ignoreProperties.concat(ignoreErrorProperties) : ignoreProperties,
ObjectGetPrototypeOf(internalWindow));
patchFilteredProperties(Document.prototype, eventNames, ignoreProperties);
if (typeof internalWindow['SVGElement'] !== 'undefined') {
patchFilteredProperties(internalWindow['SVGElement'].prototype, eventNames, ignoreProperties);
}
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 = internalWindow['HTMLMarqueeElement'];
if (HTMLMarqueeElement) {
patchFilteredProperties(HTMLMarqueeElement.prototype, marqueeEventNames, ignoreProperties);
}
const Worker = internalWindow['Worker'];
if (Worker) {
patchFilteredProperties(Worker.prototype, workerEventNames, ignoreProperties);
}
}
const XMLHttpRequest = _global['XMLHttpRequest'];
if (XMLHttpRequest) {
// XMLHttpRequest is not available in ServiceWorker, so we need to check here
patchFilteredProperties(XMLHttpRequest.prototype, XMLHttpRequestEventNames, ignoreProperties);
}
const XMLHttpRequestEventTarget = _global['XMLHttpRequestEventTarget'];
if (XMLHttpRequestEventTarget) {
patchFilteredProperties(
XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype, XMLHttpRequestEventNames,
ignoreProperties);
}
if (typeof IDBIndex !== 'undefined') {
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) {
patchFilteredProperties(WebSocket.prototype, websocketEventNames, ignoreProperties);
}
}

View File

@ -0,0 +1,19 @@
/**
* @license
* Copyright Google Inc. 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
*/
export function registerElementPatch(_global: any, api: _ZonePrivate) {
const {isBrowser, isMix} = api.getGlobalObjects() !;
if ((!isBrowser && !isMix) || !('registerElement' in (<any>_global).document)) {
return;
}
const callbacks =
['createdCallback', 'attachedCallback', 'detachedCallback', 'attributeChangedCallback'];
api.patchCallbacks(api, document, 'Document', 'registerElement', callbacks);
}

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. 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 '../zone';
import '../common/promise';
import '../common/to-string';
import './api-util';

View File

@ -0,0 +1,11 @@
/**
* @license
* Copyright Google Inc. 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 './rollup-common';
import './browser-legacy';
import './browser';

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. 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 './rollup-legacy-main';
// load test related files into bundle
import '../testing/zone-testing';

View File

@ -0,0 +1,10 @@
/**
* @license
* Copyright Google Inc. 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 './rollup-common';
import './browser';

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. 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 './rollup-main';
// load test related files into bundle
import '../testing/zone-testing';

View File

@ -0,0 +1,24 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('shadydom', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
// https://github.com/angular/zone.js/issues/782
// in web components, shadydom will patch addEventListener/removeEventListener of
// Node.prototype and WindowPrototype, this will have conflict with zone.js
// so zone.js need to patch them again.
const windowPrototype = Object.getPrototypeOf(window);
if (windowPrototype && windowPrototype.hasOwnProperty('addEventListener')) {
(windowPrototype as any)[Zone.__symbol__('addEventListener')] = null;
(windowPrototype as any)[Zone.__symbol__('removeEventListener')] = null;
api.patchEventTarget(global, [windowPrototype]);
}
if (Node.prototype.hasOwnProperty('addEventListener')) {
(Node.prototype as any)[Zone.__symbol__('addEventListener')] = null;
(Node.prototype as any)[Zone.__symbol__('removeEventListener')] = null;
api.patchEventTarget(global, [Node.prototype]);
}
});

View File

@ -0,0 +1,65 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('mediaQuery', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
function patchAddListener(proto: any) {
api.patchMethod(proto, 'addListener', (delegate: Function) => (self: any, args: any[]) => {
const callback = args.length > 0 ? args[0] : null;
if (typeof callback === 'function') {
const wrapperedCallback = Zone.current.wrap(callback, 'MediaQuery');
callback[api.symbol('mediaQueryCallback')] = wrapperedCallback;
return delegate.call(self, wrapperedCallback);
} else {
return delegate.apply(self, args);
}
});
}
function patchRemoveListener(proto: any) {
api.patchMethod(proto, 'removeListener', (delegate: Function) => (self: any, args: any[]) => {
const callback = args.length > 0 ? args[0] : null;
if (typeof callback === 'function') {
const wrapperedCallback = callback[api.symbol('mediaQueryCallback')];
if (wrapperedCallback) {
return delegate.call(self, wrapperedCallback);
} else {
return delegate.apply(self, args);
}
} else {
return delegate.apply(self, args);
}
});
}
if (global['MediaQueryList']) {
const proto = global['MediaQueryList'].prototype;
patchAddListener(proto);
patchRemoveListener(proto);
} else if (global['matchMedia']) {
api.patchMethod(global, 'matchMedia', (delegate: Function) => (self: any, args: any[]) => {
const mql = delegate.apply(self, args);
if (mql) {
// try to patch MediaQueryList.prototype
const proto = Object.getPrototypeOf(mql);
if (proto && proto['addListener']) {
// try to patch proto, don't need to worry about patch
// multiple times, because, api.patchEventTarget will check it
patchAddListener(proto);
patchRemoveListener(proto);
patchAddListener(mql);
patchRemoveListener(mql);
} else if (mql['addListener']) {
// proto not exists, or proto has no addListener method
// try to patch mql instance
patchAddListener(mql);
patchRemoveListener(mql);
}
}
return mql;
});
}
});

View File

@ -0,0 +1,18 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('notification', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
const Notification = global['Notification'];
if (!Notification || !Notification.prototype) {
return;
}
const desc = Object.getOwnPropertyDescriptor(Notification.prototype, 'onerror');
if (!desc || !desc.configurable) {
return;
}
api.patchOnProperties(Notification.prototype, null);
});

View File

@ -0,0 +1,91 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('ResizeObserver', (global: any, Zone: any, api: _ZonePrivate) => {
const ResizeObserver = global['ResizeObserver'];
if (!ResizeObserver) {
return;
}
const resizeObserverSymbol = api.symbol('ResizeObserver');
api.patchMethod(global, 'ResizeObserver', (delegate: Function) => (self: any, args: any[]) => {
const callback = args.length > 0 ? args[0] : null;
if (callback) {
args[0] = function(entries: any, observer: any) {
const zones: {[zoneName: string]: any} = {};
const currZone = Zone.current;
for (let entry of entries) {
let zone = entry.target[resizeObserverSymbol];
if (!zone) {
zone = currZone;
}
let zoneEntriesInfo = zones[zone.name];
if (!zoneEntriesInfo) {
zones[zone.name] = zoneEntriesInfo = {entries: [], zone: zone};
}
zoneEntriesInfo.entries.push(entry);
}
Object.keys(zones).forEach(zoneName => {
const zoneEntriesInfo = zones[zoneName];
if (zoneEntriesInfo.zone !== Zone.current) {
zoneEntriesInfo.zone.run(
callback, this, [zoneEntriesInfo.entries, observer], 'ResizeObserver');
} else {
callback.call(this, zoneEntriesInfo.entries, observer);
}
});
};
}
return args.length > 0 ? new ResizeObserver(args[0]) : new ResizeObserver();
});
api.patchMethod(
ResizeObserver.prototype, 'observe', (delegate: Function) => (self: any, args: any[]) => {
const target = args.length > 0 ? args[0] : null;
if (!target) {
return delegate.apply(self, args);
}
let targets = self[resizeObserverSymbol];
if (!targets) {
targets = self[resizeObserverSymbol] = [];
}
targets.push(target);
target[resizeObserverSymbol] = Zone.current;
return delegate.apply(self, args);
});
api.patchMethod(
ResizeObserver.prototype, 'unobserve', (delegate: Function) => (self: any, args: any[]) => {
const target = args.length > 0 ? args[0] : null;
if (!target) {
return delegate.apply(self, args);
}
let targets = self[resizeObserverSymbol];
if (targets) {
for (let i = 0; i < targets.length; i++) {
if (targets[i] === target) {
targets.splice(i, 1);
break;
}
}
}
target[resizeObserverSymbol] = undefined;
return delegate.apply(self, args);
});
api.patchMethod(
ResizeObserver.prototype, 'disconnect', (delegate: Function) => (self: any, args: any[]) => {
const targets = self[resizeObserverSymbol];
if (targets) {
targets.forEach((target: any) => { target[resizeObserverSymbol] = undefined; });
self[resizeObserverSymbol] = undefined;
}
return delegate.apply(self, args);
});
});

View File

@ -0,0 +1,26 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('RTCPeerConnection', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
const RTCPeerConnection = global['RTCPeerConnection'];
if (!RTCPeerConnection) {
return;
}
const addSymbol = api.symbol('addEventListener');
const removeSymbol = api.symbol('removeEventListener');
RTCPeerConnection.prototype.addEventListener = RTCPeerConnection.prototype[addSymbol];
RTCPeerConnection.prototype.removeEventListener = RTCPeerConnection.prototype[removeSymbol];
// RTCPeerConnection extends EventTarget, so we must clear the symbol
// to allow patch RTCPeerConnection.prototype.addEventListener again
RTCPeerConnection.prototype[addSymbol] = null;
RTCPeerConnection.prototype[removeSymbol] = null;
api.patchEventTarget(global, [RTCPeerConnection.prototype], {useG: false});
});

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('getUserMedia', (global: any, Zone: any, api: _ZonePrivate) => {
function wrapFunctionArgs(func: Function, source?: string): Function {
return function() {
const args = Array.prototype.slice.call(arguments);
const wrappedArgs = api.bindArguments(args, source ? source : (func as any).name);
return func.apply(this, wrappedArgs);
};
}
let navigator = global['navigator'];
if (navigator && navigator.getUserMedia) {
navigator.getUserMedia = wrapFunctionArgs(navigator.getUserMedia);
}
});

View File

@ -0,0 +1,59 @@
/**
* @license
* Copyright Google Inc. 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
*/
// we have to patch the instance since the proto is non-configurable
export function apply(api: _ZonePrivate, _global: any) {
const {ADD_EVENT_LISTENER_STR, REMOVE_EVENT_LISTENER_STR} = api.getGlobalObjects() !;
const WS = (<any>_global).WebSocket;
// 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, [WS.prototype]);
}
(<any>_global).WebSocket = function(x: any, y: any) {
const socket = arguments.length > 1 ? new WS(x, y) : new WS(x);
let proxySocket: any;
let proxySocketProto: any;
// Safari 7.0 has non-configurable own 'onmessage' and friends properties on the socket instance
const onmessageDesc = api.ObjectGetOwnPropertyDescriptor(socket, 'onmessage');
if (onmessageDesc && onmessageDesc.configurable === false) {
proxySocket = api.ObjectCreate(socket);
// socket have own property descriptor 'onopen', 'onmessage', 'onclose', 'onerror'
// but proxySocket not, so we will keep socket as prototype and pass it to
// patchOnProperties method
proxySocketProto = socket;
[ADD_EVENT_LISTENER_STR, REMOVE_EVENT_LISTENER_STR, 'send', 'close'].forEach(function(
propName) {
proxySocket[propName] = function() {
const args = api.ArraySlice.call(arguments);
if (propName === ADD_EVENT_LISTENER_STR || propName === REMOVE_EVENT_LISTENER_STR) {
const eventName = args.length > 0 ? args[0] : undefined;
if (eventName) {
const propertySymbol = Zone.__symbol__('ON_PROPERTY' + eventName);
socket[propertySymbol] = proxySocket[propertySymbol];
}
}
return socket[propName].apply(socket, args);
};
});
} else {
// we can patch the real socket
proxySocket = socket;
}
api.patchOnProperties(proxySocket, ['close', 'error', 'message', 'open'], proxySocketProto);
return proxySocket;
};
const globalWebSocket = _global['WebSocket'];
for (const prop in WS) {
globalWebSocket[prop] = WS[prop];
}
}

View File

@ -0,0 +1,445 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @fileoverview Externs for zone.js
* @see https://github.com/angular/zone.js
* @externs
*/
/**
* @interface
*/
var Zone = function() {};
/**
* @type {!Zone} The parent Zone.
*/
Zone.prototype.parent;
/**
* @type {!string} The Zone name (useful for debugging)
*/
Zone.prototype.name;
Zone.assertZonePatched = function() {};
/**
* @type {!Zone} Returns the current [Zone]. Returns the current zone. The only way to change
* the current zone is by invoking a run() method, which will update the current zone for the
* duration of the run method callback.
*/
Zone.current;
/**
* @type {Task} The task associated with the current execution.
*/
Zone.currentTask;
/**
* @type {!Zone} Return the root zone.
*/
Zone.root;
/**
* Returns a value associated with the `key`.
*
* If the current zone does not have a key, the request is delegated to the parent zone. Use
* [ZoneSpec.properties] to configure the set of properties associated with the current zone.
*
* @param {!string} key The key to retrieve.
* @returns {?} The value for the key, or `undefined` if not found.
*/
Zone.prototype.get = function(key) {};
/**
* Returns a Zone which defines a `key`.
*
* Recursively search the parent Zone until a Zone which has a property `key` is found.
*
* @param {!string} key The key to use for identification of the returned zone.
* @returns {?Zone} The Zone which defines the `key`, `null` if not found.
*/
Zone.prototype.getZoneWith = function(key) {};
/**
* Used to create a child zone.
*
* @param {!ZoneSpec} zoneSpec A set of rules which the child zone should follow.
* @returns {!Zone} A new child zone.
*/
Zone.prototype.fork = function(zoneSpec) {};
/**
* Wraps a callback function in a new function which will properly restore the current zone upon
* invocation.
*
* The wrapped function will properly forward `this` as well as `arguments` to the `callback`.
*
* Before the function is wrapped the zone can intercept the `callback` by declaring
* [ZoneSpec.onIntercept].
*
* @param {!Function} callback the function which will be wrapped in the zone.
* @param {!string=} source A unique debug location of the API being wrapped.
* @returns {!Function} A function which will invoke the `callback` through [Zone.runGuarded].
*/
Zone.prototype.wrap = function(callback, source) {};
/**
* Invokes a function in a given zone.
*
* The invocation of `callback` can be intercepted be declaring [ZoneSpec.onInvoke].
*
* @param {!Function} callback The function to invoke.
* @param {?Object=} applyThis
* @param {?Array=} applyArgs
* @param {?string=} source A unique debug location of the API being invoked.
* @returns {*} Value from the `callback` function.
*/
Zone.prototype.run = function(callback, applyThis, applyArgs, source) {};
/**
* Invokes a function in a given zone and catches any exceptions.
*
* Any exceptions thrown will be forwarded to [Zone.HandleError].
*
* The invocation of `callback` can be intercepted be declaring [ZoneSpec.onInvoke]. The
* handling of exceptions can intercepted by declaring [ZoneSpec.handleError].
*
* @param {!Function} callback The function to invoke.
* @param {?Object=} applyThis
* @param {?Array=} applyArgs
* @param {?string=} source A unique debug location of the API being invoked.
* @returns {*} Value from the `callback` function.
*/
Zone.prototype.runGuarded = function(callback, applyThis, applyArgs, source) {};
/**
* Execute the Task by restoring the [Zone.currentTask] in the Task's zone.
*
* @param {!Task} task
* @param {?Object=} applyThis
* @param {?Array=} applyArgs
* @returns {*}
*/
Zone.prototype.runTask = function(task, applyThis, applyArgs) {};
/**
* @param {string} source
* @param {!Function} callback
* @param {?TaskData=} data
* @param {?function(!Task)=} customSchedule
* @return {!MicroTask} microTask
*/
Zone.prototype.scheduleMicroTask = function(source, callback, data, customSchedule) {};
/**
* @param {string} source
* @param {!Function} callback
* @param {?TaskData=} data
* @param {?function(!Task)=} customSchedule
* @param {?function(!Task)=} customCancel
* @return {!MacroTask} macroTask
*/
Zone.prototype.scheduleMacroTask = function(source, callback, data, customSchedule, customCancel) {
};
/**
* @param {string} source
* @param {!Function} callback
* @param {?TaskData=} data
* @param {?function(!Task)=} customSchedule
* @param {?function(!Task)=} customCancel
* @return {!EventTask} eventTask
*/
Zone.prototype.scheduleEventTask = function(source, callback, data, customSchedule, customCancel) {
};
/**
* @param {!Task} task
* @return {!Task} task
*/
Zone.prototype.scheduleTask = function(task) {};
/**
* @param {!Task} task
* @return {!Task} task
*/
Zone.prototype.cancelTask = function(task) {};
/**
* @record
*/
var ZoneSpec = function() {};
/**
* @type {!string} The name of the zone. Usefull when debugging Zones.
*/
ZoneSpec.prototype.name;
/**
* @type {Object<string, Object>|undefined} A set of properties to be associated with Zone. Use
* [Zone.get] to retrieve them.
*/
ZoneSpec.prototype.properties;
/**
* Allows the interception of zone forking.
*
* When the zone is being forked, the request is forwarded to this method for interception.
*
* @type {
* undefined|?function(ZoneDelegate, Zone, Zone, ZoneSpec): Zone
* }
*/
ZoneSpec.prototype.onFork;
/**
* Allows the interception of the wrapping of the callback.
*
* When the zone is being forked, the request is forwarded to this method for interception.
*
* @type {
* undefined|?function(ZoneDelegate, Zone, Zone, Function, string): Function
* }
*/
ZoneSpec.prototype.onIntercept;
/**
* Allows interception of the callback invocation.
*
* @type {
* undefined|?function(ZoneDelegate, Zone, Zone, Function, Object, Array, string): *
* }
*/
ZoneSpec.prototype.onInvoke;
/**
* Allows interception of the error handling.
*
* @type {
* undefined|?function(ZoneDelegate, Zone, Zone, Object): boolean
* }
*/
ZoneSpec.prototype.onHandleError;
/**
* Allows interception of task scheduling.
*
* @type {
* undefined|?function(ZoneDelegate, Zone, Zone, Task): Task
* }
*/
ZoneSpec.prototype.onScheduleTask;
/**
* Allows interception of task invoke.
*
* @type {
* undefined|?function(ZoneDelegate, Zone, Zone, Task, Object, Array): *
* }
*/
ZoneSpec.prototype.onInvokeTask;
/**
* Allows interception of task cancelation.
*
* @type {
* undefined|?function(ZoneDelegate, Zone, Zone, Task): *
* }
*/
ZoneSpec.prototype.onCancelTask;
/**
* Notifies of changes to the task queue empty status.
*
* @type {
* undefined|?function(ZoneDelegate, Zone, Zone, HasTaskState)
* }
*/
ZoneSpec.prototype.onHasTask;
/**
* @interface
*/
var ZoneDelegate = function() {};
/**
* @type {!Zone} zone
*/
ZoneDelegate.prototype.zone;
/**
* @param {!Zone} targetZone the [Zone] which originally received the request.
* @param {!ZoneSpec} zoneSpec the argument passed into the `fork` method.
* @returns {!Zone} the new forked zone
*/
ZoneDelegate.prototype.fork = function(targetZone, zoneSpec) {};
/**
* @param {!Zone} targetZone the [Zone] which originally received the request.
* @param {!Function} callback the callback function passed into `wrap` function
* @param {string=} source the argument passed into the `wrap` method.
* @returns {!Function}
*/
ZoneDelegate.prototype.intercept = function(targetZone, callback, source) {};
/**
* @param {Zone} targetZone the [Zone] which originally received the request.
* @param {!Function} callback the callback which will be invoked.
* @param {?Object=} applyThis the argument passed into the `run` method.
* @param {?Array=} applyArgs the argument passed into the `run` method.
* @param {?string=} source the argument passed into the `run` method.
* @returns {*}
*/
ZoneDelegate.prototype.invoke = function(targetZone, callback, applyThis, applyArgs, source) {};
/**
* @param {!Zone} targetZone the [Zone] which originally received the request.
* @param {!Object} error the argument passed into the `handleError` method.
* @returns {boolean}
*/
ZoneDelegate.prototype.handleError = function(targetZone, error) {};
/**
* @param {!Zone} targetZone the [Zone] which originally received the request.
* @param {!Task} task the argument passed into the `scheduleTask` method.
* @returns {!Task} task
*/
ZoneDelegate.prototype.scheduleTask = function(targetZone, task) {};
/**
* @param {!Zone} targetZone The [Zone] which originally received the request.
* @param {!Task} task The argument passed into the `scheduleTask` method.
* @param {?Object=} applyThis The argument passed into the `run` method.
* @param {?Array=} applyArgs The argument passed into the `run` method.
* @returns {*}
*/
ZoneDelegate.prototype.invokeTask = function(targetZone, task, applyThis, applyArgs) {};
/**
* @param {!Zone} targetZone The [Zone] which originally received the request.
* @param {!Task} task The argument passed into the `cancelTask` method.
* @returns {*}
*/
ZoneDelegate.prototype.cancelTask = function(targetZone, task) {};
/**
* @param {!Zone} targetZone The [Zone] which originally received the request.
* @param {!HasTaskState} hasTaskState
*/
ZoneDelegate.prototype.hasTask = function(targetZone, hasTaskState) {};
/**
* @interface
*/
var HasTaskState = function() {};
/**
* @type {boolean}
*/
HasTaskState.prototype.microTask;
/**
* @type {boolean}
*/
HasTaskState.prototype.macroTask;
/**
* @type {boolean}
*/
HasTaskState.prototype.eventTask;
/**
* @type {TaskType}
*/
HasTaskState.prototype.change;
/**
* @interface
*/
var TaskType = function() {};
/**
* @interface
*/
var TaskState = function() {};
/**
* @interface
*/
var TaskData = function() {};
/**
* @type {boolean|undefined}
*/
TaskData.prototype.isPeriodic;
/**
* @type {number|undefined}
*/
TaskData.prototype.delay;
/**
* @type {number|undefined}
*/
TaskData.prototype.handleId;
/**
* @interface
*/
var Task = function() {};
/**
* @type {TaskType}
*/
Task.prototype.type;
/**
* @type {TaskState}
*/
Task.prototype.state;
/**
* @type {string}
*/
Task.prototype.source;
/**
* @type {Function}
*/
Task.prototype.invoke;
/**
* @type {Function}
*/
Task.prototype.callback;
/**
* @type {TaskData}
*/
Task.prototype.data;
/**
* @param {!Task} task
*/
Task.prototype.scheduleFn = function(task) {};
/**
* @param {!Task} task
*/
Task.prototype.cancelFn = function(task) {};
/**
* @type {Zone}
*/
Task.prototype.zone;
/**
* @type {number}
*/
Task.prototype.runCount;
Task.prototype.cancelSchduleRequest = function() {};
/**
* @interface
* @extends {Task}
*/
var MicroTask = function() {};
/**
* @interface
* @extends {Task}
*/
var MacroTask = function() {};
/**
* @interface
* @extends {Task}
*/
var EventTask = function() {};
/**
* @type {?string}
*/
Error.prototype.zoneAwareStack;
/**
* @type {?string}
*/
Error.prototype.originalStack;

View File

@ -0,0 +1,378 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @fileoverview
* @suppress {globalThis,undefinedVars}
*/
/**
* Extend the Error with additional fields for rewritten stack frames
*/
interface Error {
/**
* Stack trace where extra frames have been removed and zone names added.
*/
zoneAwareStack?: string;
/**
* Original stack trace with no modifications
*/
originalStack?: string;
}
Zone.__load_patch('Error', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
/*
* This code patches Error so that:
* - It ignores un-needed stack frames.
* - It Shows the associated Zone for reach frame.
*/
const enum FrameType {
/// Skip this frame when printing out stack
blackList,
/// This frame marks zone transition
transition
}
const blacklistedStackFramesSymbol = api.symbol('blacklistedStackFrames');
const NativeError = global[api.symbol('Error')] = global['Error'];
// Store the frames which should be removed from the stack frames
const blackListedStackFrames: {[frame: string]: FrameType} = {};
// We must find the frame where Error was created, otherwise we assume we don't understand stack
let zoneAwareFrame1: string;
let zoneAwareFrame2: string;
let zoneAwareFrame1WithoutNew: string;
let zoneAwareFrame2WithoutNew: string;
let zoneAwareFrame3WithoutNew: string;
global['Error'] = ZoneAwareError;
const stackRewrite = 'stackRewrite';
type BlackListedStackFramesPolicy = 'default' | 'disable' | 'lazy';
const blackListedStackFramesPolicy: BlackListedStackFramesPolicy =
global['__Zone_Error_BlacklistedStackFrames_policy'] || 'default';
interface ZoneFrameName {
zoneName: string;
parent?: ZoneFrameName;
}
function buildZoneFrameNames(zoneFrame: _ZoneFrame) {
let zoneFrameName: ZoneFrameName = {zoneName: zoneFrame.zone.name};
let result = zoneFrameName;
while (zoneFrame.parent) {
zoneFrame = zoneFrame.parent;
const parentZoneFrameName = {zoneName: zoneFrame.zone.name};
zoneFrameName.parent = parentZoneFrameName;
zoneFrameName = parentZoneFrameName;
}
return result;
}
function buildZoneAwareStackFrames(
originalStack: string, zoneFrame: _ZoneFrame | ZoneFrameName | null, isZoneFrame = true) {
let frames: string[] = originalStack.split('\n');
let i = 0;
// Find the first frame
while (!(frames[i] === zoneAwareFrame1 || frames[i] === zoneAwareFrame2 ||
frames[i] === zoneAwareFrame1WithoutNew || frames[i] === zoneAwareFrame2WithoutNew ||
frames[i] === zoneAwareFrame3WithoutNew) &&
i < frames.length) {
i++;
}
for (; i < frames.length && zoneFrame; i++) {
let frame = frames[i];
if (frame.trim()) {
switch (blackListedStackFrames[frame]) {
case FrameType.blackList:
frames.splice(i, 1);
i--;
break;
case FrameType.transition:
if (zoneFrame.parent) {
// This is the special frame where zone changed. Print and process it accordingly
zoneFrame = zoneFrame.parent;
} else {
zoneFrame = null;
}
frames.splice(i, 1);
i--;
break;
default:
frames[i] += isZoneFrame ? ` [${(zoneFrame as _ZoneFrame).zone.name}]` :
` [${(zoneFrame as ZoneFrameName).zoneName}]`;
}
}
}
return frames.join('\n');
}
/**
* This is ZoneAwareError which processes the stack frame and cleans up extra frames as well as
* adds zone information to it.
*/
function ZoneAwareError(): Error {
// We always have to return native error otherwise the browser console will not work.
let error: Error = NativeError.apply(this, arguments);
// Save original stack trace
const originalStack = (error as any)['originalStack'] = error.stack;
// Process the stack trace and rewrite the frames.
if ((ZoneAwareError as any)[stackRewrite] && originalStack) {
let zoneFrame = api.currentZoneFrame();
if (blackListedStackFramesPolicy === 'lazy') {
// don't handle stack trace now
(error as any)[api.symbol('zoneFrameNames')] = buildZoneFrameNames(zoneFrame);
} else if (blackListedStackFramesPolicy === 'default') {
try {
error.stack = error.zoneAwareStack = buildZoneAwareStackFrames(originalStack, zoneFrame);
} catch (e) {
// ignore as some browsers don't allow overriding of stack
}
}
}
if (this instanceof NativeError && this.constructor != NativeError) {
// We got called with a `new` operator AND we are subclass of ZoneAwareError
// in that case we have to copy all of our properties to `this`.
Object.keys(error).concat('stack', 'message').forEach((key) => {
const value = (error as any)[key];
if (value !== undefined) {
try {
this[key] = value;
} catch (e) {
// ignore the assignment in case it is a setter and it throws.
}
}
});
return this;
}
return error;
}
// Copy the prototype so that instanceof operator works as expected
ZoneAwareError.prototype = NativeError.prototype;
(ZoneAwareError as any)[blacklistedStackFramesSymbol] = blackListedStackFrames;
(ZoneAwareError as any)[stackRewrite] = false;
const zoneAwareStackSymbol = api.symbol('zoneAwareStack');
// try to define zoneAwareStack property when blackListed
// policy is delay
if (blackListedStackFramesPolicy === 'lazy') {
Object.defineProperty(ZoneAwareError.prototype, 'zoneAwareStack', {
configurable: true,
enumerable: true,
get: function() {
if (!this[zoneAwareStackSymbol]) {
this[zoneAwareStackSymbol] = buildZoneAwareStackFrames(
this.originalStack, this[api.symbol('zoneFrameNames')], false);
}
return this[zoneAwareStackSymbol];
},
set: function(newStack: string) {
this.originalStack = newStack;
this[zoneAwareStackSymbol] = buildZoneAwareStackFrames(
this.originalStack, this[api.symbol('zoneFrameNames')], false);
}
});
}
// those properties need special handling
const specialPropertyNames = ['stackTraceLimit', 'captureStackTrace', 'prepareStackTrace'];
// those properties of NativeError should be set to ZoneAwareError
const nativeErrorProperties = Object.keys(NativeError);
if (nativeErrorProperties) {
nativeErrorProperties.forEach(prop => {
if (specialPropertyNames.filter(sp => sp === prop).length === 0) {
Object.defineProperty(ZoneAwareError, prop, {
get: function() { return NativeError[prop]; },
set: function(value) { NativeError[prop] = value; }
});
}
});
}
if (NativeError.hasOwnProperty('stackTraceLimit')) {
// Extend default stack limit as we will be removing few frames.
NativeError.stackTraceLimit = Math.max(NativeError.stackTraceLimit, 15);
// make sure that ZoneAwareError has the same property which forwards to NativeError.
Object.defineProperty(ZoneAwareError, 'stackTraceLimit', {
get: function() { return NativeError.stackTraceLimit; },
set: function(value) { return NativeError.stackTraceLimit = value; }
});
}
if (NativeError.hasOwnProperty('captureStackTrace')) {
Object.defineProperty(ZoneAwareError, 'captureStackTrace', {
// add named function here because we need to remove this
// stack frame when prepareStackTrace below
value: function zoneCaptureStackTrace(targetObject: Object, constructorOpt?: Function) {
NativeError.captureStackTrace(targetObject, constructorOpt);
}
});
}
const ZONE_CAPTURESTACKTRACE = 'zoneCaptureStackTrace';
Object.defineProperty(ZoneAwareError, 'prepareStackTrace', {
get: function() { return NativeError.prepareStackTrace; },
set: function(value) {
if (!value || typeof value !== 'function') {
return NativeError.prepareStackTrace = value;
}
return NativeError.prepareStackTrace = function(
error: Error, structuredStackTrace: {getFunctionName: Function}[]) {
// remove additional stack information from ZoneAwareError.captureStackTrace
if (structuredStackTrace) {
for (let i = 0; i < structuredStackTrace.length; i++) {
const st = structuredStackTrace[i];
// remove the first function which name is zoneCaptureStackTrace
if (st.getFunctionName() === ZONE_CAPTURESTACKTRACE) {
structuredStackTrace.splice(i, 1);
break;
}
}
}
return value.call(this, error, structuredStackTrace);
};
}
});
if (blackListedStackFramesPolicy === 'disable') {
// don't need to run detectZone to populate
// blacklisted stack frames
return;
}
// Now we need to populate the `blacklistedStackFrames` as well as find the
// run/runGuarded/runTask frames. This is done by creating a detect zone and then threading
// the execution through all of the above methods so that we can look at the stack trace and
// find the frames of interest.
let detectZone: Zone = Zone.current.fork({
name: 'detect',
onHandleError: function(
parentZD: ZoneDelegate, current: Zone, target: Zone, error: any): boolean {
if (error.originalStack && Error === ZoneAwareError) {
let frames = error.originalStack.split(/\n/);
let runFrame = false, runGuardedFrame = false, runTaskFrame = false;
while (frames.length) {
let frame = frames.shift();
// On safari it is possible to have stack frame with no line number.
// This check makes sure that we don't filter frames on name only (must have
// line number or exact equals to `ZoneAwareError`)
if (/:\d+:\d+/.test(frame) || frame === 'ZoneAwareError') {
// Get rid of the path so that we don't accidentally find function name in path.
// In chrome the separator is `(` and `@` in FF and safari
// Chrome: at Zone.run (zone.js:100)
// Chrome: at Zone.run (http://localhost:9876/base/build/lib/zone.js:100:24)
// FireFox: Zone.prototype.run@http://localhost:9876/base/build/lib/zone.js:101:24
// Safari: run@http://localhost:9876/base/build/lib/zone.js:101:24
let fnName: string = frame.split('(')[0].split('@')[0];
let frameType = FrameType.transition;
if (fnName.indexOf('ZoneAwareError') !== -1) {
if (fnName.indexOf('new ZoneAwareError') !== -1) {
zoneAwareFrame1 = frame;
zoneAwareFrame2 = frame.replace('new ZoneAwareError', 'new Error.ZoneAwareError');
} else {
zoneAwareFrame1WithoutNew = frame;
zoneAwareFrame2WithoutNew = frame.replace('Error.', '');
if (frame.indexOf('Error.ZoneAwareError') === -1) {
zoneAwareFrame3WithoutNew =
frame.replace('ZoneAwareError', 'Error.ZoneAwareError');
}
}
blackListedStackFrames[zoneAwareFrame2] = FrameType.blackList;
}
if (fnName.indexOf('runGuarded') !== -1) {
runGuardedFrame = true;
} else if (fnName.indexOf('runTask') !== -1) {
runTaskFrame = true;
} else if (fnName.indexOf('run') !== -1) {
runFrame = true;
} else {
frameType = FrameType.blackList;
}
blackListedStackFrames[frame] = frameType;
// Once we find all of the frames we can stop looking.
if (runFrame && runGuardedFrame && runTaskFrame) {
(ZoneAwareError as any)[stackRewrite] = true;
break;
}
}
}
}
return false;
}
}) as Zone;
// carefully constructor a stack frame which contains all of the frames of interest which
// need to be detected and blacklisted.
const childDetectZone = detectZone.fork({
name: 'child',
onScheduleTask: function(delegate, curr, target, task) {
return delegate.scheduleTask(target, task);
},
onInvokeTask: function(delegate, curr, target, task, applyThis, applyArgs) {
return delegate.invokeTask(target, task, applyThis, applyArgs);
},
onCancelTask: function(delegate, curr, target, task) {
return delegate.cancelTask(target, task);
},
onInvoke: function(delegate, curr, target, callback, applyThis, applyArgs, source) {
return delegate.invoke(target, callback, applyThis, applyArgs, source);
}
});
// we need to detect all zone related frames, it will
// exceed default stackTraceLimit, so we set it to
// larger number here, and restore it after detect finish.
const originalStackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 100;
// we schedule event/micro/macro task, and invoke them
// when onSchedule, so we can get all stack traces for
// all kinds of tasks with one error thrown.
childDetectZone.run(() => {
childDetectZone.runGuarded(() => {
const fakeTransitionTo = () => {};
childDetectZone.scheduleEventTask(
blacklistedStackFramesSymbol,
() => {
childDetectZone.scheduleMacroTask(
blacklistedStackFramesSymbol,
() => {
childDetectZone.scheduleMicroTask(
blacklistedStackFramesSymbol, () => { throw new Error(); }, undefined,
(t: Task) => {
(t as any)._transitionTo = fakeTransitionTo;
t.invoke();
});
childDetectZone.scheduleMicroTask(
blacklistedStackFramesSymbol, () => { throw Error(); }, undefined,
(t: Task) => {
(t as any)._transitionTo = fakeTransitionTo;
t.invoke();
});
},
undefined,
(t) => {
(t as any)._transitionTo = fakeTransitionTo;
t.invoke();
},
() => {});
},
undefined,
(t) => {
(t as any)._transitionTo = fakeTransitionTo;
t.invoke();
},
() => {});
});
});
Error.stackTraceLimit = originalStackTraceLimit;
});

View File

@ -0,0 +1,679 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @fileoverview
* @suppress {missingRequire}
*/
import {ADD_EVENT_LISTENER_STR, FALSE_STR, ObjectGetPrototypeOf, REMOVE_EVENT_LISTENER_STR, TRUE_STR, ZONE_SYMBOL_PREFIX, attachOriginToPatched, isNode, zoneSymbol} from './utils';
/** @internal **/
interface EventTaskData extends TaskData {
// use global callback or not
readonly useG?: boolean;
}
let passiveSupported = false;
if (typeof window !== 'undefined') {
try {
const options =
Object.defineProperty({}, 'passive', {get: function() { passiveSupported = true; }});
window.addEventListener('test', options, options);
window.removeEventListener('test', options, options);
} catch (err) {
passiveSupported = false;
}
}
// an identifier to tell ZoneTask do not create a new invoke closure
const OPTIMIZED_ZONE_EVENT_TASK_DATA: EventTaskData = {
useG: true
};
export const zoneSymbolEventNames: any = {};
export const globalSources: any = {};
const EVENT_NAME_SYMBOL_REGX = new RegExp('^' + ZONE_SYMBOL_PREFIX + '(\\w+)(true|false)$');
const IMMEDIATE_PROPAGATION_SYMBOL = zoneSymbol('propagationStopped');
export interface PatchEventTargetOptions {
// validateHandler
vh?: (nativeDelegate: any, delegate: any, target: any, args: any) => boolean;
// addEventListener function name
add?: string;
// removeEventListener function name
rm?: string;
// prependEventListener function name
prepend?: string;
// listeners function name
listeners?: string;
// removeAllListeners function name
rmAll?: string;
// useGlobalCallback flag
useG?: boolean;
// check duplicate flag when addEventListener
chkDup?: boolean;
// return target flag when addEventListener
rt?: boolean;
// event compare handler
diff?: (task: any, delegate: any) => boolean;
// support passive or not
supportPassive?: boolean;
// get string from eventName (in nodejs, eventName maybe Symbol)
eventNameToString?: (eventName: any) => string;
}
export function patchEventTarget(
_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;
const LISTENERS_EVENT_LISTENER = (patchOptions && patchOptions.listeners) || 'eventListeners';
const REMOVE_ALL_LISTENERS_EVENT_LISTENER =
(patchOptions && patchOptions.rmAll) || 'removeAllListeners';
const zoneSymbolAddEventListener = zoneSymbol(ADD_EVENT_LISTENER);
const ADD_EVENT_LISTENER_SOURCE = '.' + ADD_EVENT_LISTENER + ':';
const PREPEND_EVENT_LISTENER = 'prependListener';
const PREPEND_EVENT_LISTENER_SOURCE = '.' + PREPEND_EVENT_LISTENER + ':';
const invokeTask = function(task: any, target: any, event: Event) {
// for better performance, check isRemoved which is set
// by removeEventListener
if (task.isRemoved) {
return;
}
const delegate = task.callback;
if (typeof delegate === 'object' && delegate.handleEvent) {
// create the bind version of handleEvent when invoke
task.callback = (event: Event) => delegate.handleEvent(event);
task.originalDelegate = delegate;
}
// invoke static task.invoke
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
// only browser need to do this, nodejs eventEmitter will cal removeListener
// inside EventEmitter.once
const delegate = task.originalDelegate ? task.originalDelegate : task.callback;
target[REMOVE_EVENT_LISTENER].call(target, event.type, delegate, options);
}
};
// global shared zoneAwareCallback to handle all event callback with capture = false
const globalZoneAwareCallback = function(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;
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][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
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);
}
}
}
};
// global shared zoneAwareCallback to handle all event callback with capture = true
const globalZoneAwareCaptureCallback = function(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;
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) {
if (!obj) {
return false;
}
let useGlobalCallback = true;
if (patchOptions && patchOptions.useG !== undefined) {
useGlobalCallback = patchOptions.useG;
}
const validateHandler = patchOptions && patchOptions.vh;
let checkDuplicate = true;
if (patchOptions && patchOptions.chkDup !== undefined) {
checkDuplicate = patchOptions.chkDup;
}
let returnTarget = false;
if (patchOptions && patchOptions.rt !== undefined) {
returnTarget = patchOptions.rt;
}
let proto = obj;
while (proto && !proto.hasOwnProperty(ADD_EVENT_LISTENER)) {
proto = ObjectGetPrototypeOf(proto);
}
if (!proto && obj[ADD_EVENT_LISTENER]) {
// somehow we did not find it, but we can see it. This happens on IE for Window properties.
proto = obj;
}
if (!proto) {
return false;
}
if (proto[zoneSymbolAddEventListener]) {
return false;
}
const eventNameToString = patchOptions && patchOptions.eventNameToString;
// a shared global taskData to pass data for scheduleEventTask
// so we do not need to create a new object just for pass some data
const taskData: any = {};
const nativeAddEventListener = proto[zoneSymbolAddEventListener] = proto[ADD_EVENT_LISTENER];
const nativeRemoveEventListener = proto[zoneSymbol(REMOVE_EVENT_LISTENER)] =
proto[REMOVE_EVENT_LISTENER];
const nativeListeners = proto[zoneSymbol(LISTENERS_EVENT_LISTENER)] =
proto[LISTENERS_EVENT_LISTENER];
const nativeRemoveAllListeners = proto[zoneSymbol(REMOVE_ALL_LISTENERS_EVENT_LISTENER)] =
proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER];
let nativePrependEventListener: any;
if (patchOptions && patchOptions.prepend) {
nativePrependEventListener = proto[zoneSymbol(patchOptions.prepend)] =
proto[patchOptions.prepend];
}
function checkIsPassive(task: Task) {
if (!passiveSupported && typeof taskData.options !== 'boolean' &&
typeof taskData.options !== 'undefined' && taskData.options !== null) {
// options is a non-null non-undefined object
// passive is not supported
// don't pass options as object
// just pass capture as a boolean
(task as any).options = !!taskData.options.capture;
taskData.options = (task as any).options;
}
}
const customScheduleGlobal = function(task: Task) {
// if there is already a task for the eventName + capture,
// just return, because we use the shared globalZoneAwareCallback here.
if (taskData.isExisting) {
return;
}
checkIsPassive(task);
return nativeAddEventListener.call(
taskData.target, taskData.eventName,
taskData.capture ? globalZoneAwareCaptureCallback : globalZoneAwareCallback,
taskData.options);
};
const customCancelGlobal = function(task: any) {
// if task is not marked as isRemoved, this call is directly
// from Zone.prototype.cancelTask, we should remove the task
// from tasksList of target first
if (!task.isRemoved) {
const symbolEventNames = zoneSymbolEventNames[task.eventName];
let symbolEventName;
if (symbolEventNames) {
symbolEventName = symbolEventNames[task.capture ? TRUE_STR : FALSE_STR];
}
const existingTasks = symbolEventName && task.target[symbolEventName];
if (existingTasks) {
for (let i = 0; i < existingTasks.length; i++) {
const existingTask = existingTasks[i];
if (existingTask === task) {
existingTasks.splice(i, 1);
// set isRemoved to data for faster invokeTask check
task.isRemoved = true;
if (existingTasks.length === 0) {
// all tasks for the eventName + capture have gone,
// remove globalZoneAwareCallback and remove the task cache from target
task.allRemoved = true;
task.target[symbolEventName] = null;
}
break;
}
}
}
}
// if all tasks for the eventName + capture have gone,
// we will really remove the global event callback,
// if not, return
if (!task.allRemoved) {
return;
}
return nativeRemoveEventListener.call(
task.target, task.eventName,
task.capture ? globalZoneAwareCaptureCallback : globalZoneAwareCallback, task.options);
};
const customScheduleNonGlobal = function(task: Task) {
checkIsPassive(task);
return nativeAddEventListener.call(
taskData.target, taskData.eventName, task.invoke, taskData.options);
};
const customSchedulePrepend = function(task: Task) {
return nativePrependEventListener.call(
taskData.target, taskData.eventName, task.invoke, taskData.options);
};
const customCancelNonGlobal = function(task: any) {
return nativeRemoveEventListener.call(task.target, task.eventName, task.invoke, task.options);
};
const customSchedule = useGlobalCallback ? customScheduleGlobal : customScheduleNonGlobal;
const customCancel = useGlobalCallback ? customCancelGlobal : customCancelNonGlobal;
const compareTaskCallbackVsDelegate = function(task: any, delegate: any) {
const typeOfDelegate = typeof delegate;
return (typeOfDelegate === 'function' && task.callback === delegate) ||
(typeOfDelegate === 'object' && task.originalDelegate === delegate);
};
const compare =
(patchOptions && patchOptions.diff) ? patchOptions.diff : compareTaskCallbackVsDelegate;
const blackListedEvents: string[] = (Zone as any)[zoneSymbol('BLACK_LISTED_EVENTS')];
const makeAddListener = function(
nativeListener: any, addSource: string, customScheduleFn: any, customCancelFn: any,
returnTarget = false, prepend = false) {
return function() {
const target = this || _global;
const eventName = arguments[0];
let delegate = arguments[1];
if (!delegate) {
return nativeListener.apply(this, arguments);
}
if (isNode && eventName === 'uncaughtException') {
// don't patch uncaughtException of nodejs to prevent endless loop
return nativeListener.apply(this, arguments);
}
// don't create the bind delegate function for handleEvent
// case here to improve addEventListener performance
// we will create the bind delegate when invoke
let isHandleEvent = false;
if (typeof delegate !== 'function') {
if (!delegate.handleEvent) {
return nativeListener.apply(this, arguments);
}
isHandleEvent = true;
}
if (validateHandler && !validateHandler(nativeListener, delegate, target, arguments)) {
return;
}
const options = arguments[2];
if (blackListedEvents) {
// check black list
for (let i = 0; i < blackListedEvents.length; i++) {
if (eventName === blackListedEvents[i]) {
return nativeListener.apply(this, arguments);
}
}
}
let capture;
let once = false;
if (options === undefined) {
capture = false;
} else if (options === true) {
capture = true;
} else if (options === false) {
capture = false;
} else {
capture = options ? !!options.capture : false;
once = options ? !!options.once : false;
}
const zone = Zone.current;
const symbolEventNames = zoneSymbolEventNames[eventName];
let symbolEventName;
if (!symbolEventNames) {
// the code is duplicate, but I just want to get some better performance
const falseEventName =
(eventNameToString ? eventNameToString(eventName) : eventName) + FALSE_STR;
const trueEventName =
(eventNameToString ? eventNameToString(eventName) : eventName) + TRUE_STR;
const symbol = ZONE_SYMBOL_PREFIX + falseEventName;
const symbolCapture = ZONE_SYMBOL_PREFIX + trueEventName;
zoneSymbolEventNames[eventName] = {};
zoneSymbolEventNames[eventName][FALSE_STR] = symbol;
zoneSymbolEventNames[eventName][TRUE_STR] = symbolCapture;
symbolEventName = capture ? symbolCapture : symbol;
} else {
symbolEventName = symbolEventNames[capture ? TRUE_STR : FALSE_STR];
}
let existingTasks = target[symbolEventName];
let isExisting = false;
if (existingTasks) {
// already have task registered
isExisting = true;
if (checkDuplicate) {
for (let i = 0; i < existingTasks.length; i++) {
if (compare(existingTasks[i], delegate)) {
// same callback, same capture, same event name, just return
return;
}
}
}
} else {
existingTasks = target[symbolEventName] = [];
}
let source;
const constructorName = target.constructor['name'];
const targetSource = globalSources[constructorName];
if (targetSource) {
source = targetSource[eventName];
}
if (!source) {
source = constructorName + addSource +
(eventNameToString ? eventNameToString(eventName) : eventName);
}
// do not create a new object as task.data to pass those things
// just use the global shared one
taskData.options = options;
if (once) {
// if addEventListener with once options, we don't pass it to
// native addEventListener, instead we keep the once setting
// and handle ourselves.
taskData.options.once = false;
}
taskData.target = target;
taskData.capture = capture;
taskData.eventName = eventName;
taskData.isExisting = isExisting;
const data = useGlobalCallback ? OPTIMIZED_ZONE_EVENT_TASK_DATA : undefined;
// keep taskData into data to allow onScheduleEventTask to access the task information
if (data) {
(data as any).taskData = taskData;
}
const task: any =
zone.scheduleEventTask(source, delegate, data, customScheduleFn, customCancelFn);
// should clear taskData.target to avoid memory leak
// issue, https://github.com/angular/angular/issues/20442
taskData.target = null;
// need to clear up taskData because it is a global object
if (data) {
(data as any).taskData = null;
}
// have to save those information to task in case
// application may call task.zone.cancelTask() directly
if (once) {
options.once = true;
}
if (!(!passiveSupported && typeof task.options === 'boolean')) {
// if not support passive, and we pass an option object
// to addEventListener, we should save the options to task
task.options = options;
}
task.target = target;
task.capture = capture;
task.eventName = eventName;
if (isHandleEvent) {
// save original delegate for compare to check duplicate
(task as any).originalDelegate = delegate;
}
if (!prepend) {
existingTasks.push(task);
} else {
existingTasks.unshift(task);
}
if (returnTarget) {
return target;
}
};
};
proto[ADD_EVENT_LISTENER] = makeAddListener(
nativeAddEventListener, ADD_EVENT_LISTENER_SOURCE, customSchedule, customCancel,
returnTarget);
if (nativePrependEventListener) {
proto[PREPEND_EVENT_LISTENER] = makeAddListener(
nativePrependEventListener, PREPEND_EVENT_LISTENER_SOURCE, customSchedulePrepend,
customCancel, returnTarget, true);
}
proto[REMOVE_EVENT_LISTENER] = function() {
const target = this || _global;
const eventName = arguments[0];
const options = arguments[2];
let capture;
if (options === undefined) {
capture = false;
} else if (options === true) {
capture = true;
} else if (options === false) {
capture = false;
} else {
capture = options ? !!options.capture : false;
}
const delegate = arguments[1];
if (!delegate) {
return nativeRemoveEventListener.apply(this, arguments);
}
if (validateHandler &&
!validateHandler(nativeRemoveEventListener, delegate, target, arguments)) {
return;
}
const symbolEventNames = zoneSymbolEventNames[eventName];
let symbolEventName;
if (symbolEventNames) {
symbolEventName = symbolEventNames[capture ? TRUE_STR : FALSE_STR];
}
const existingTasks = symbolEventName && target[symbolEventName];
if (existingTasks) {
for (let i = 0; i < existingTasks.length; i++) {
const existingTask = existingTasks[i];
if (compare(existingTask, delegate)) {
existingTasks.splice(i, 1);
// set isRemoved to data for faster invokeTask check
(existingTask as any).isRemoved = true;
if (existingTasks.length === 0) {
// all tasks for the eventName + capture have gone,
// remove globalZoneAwareCallback and remove the task cache from target
(existingTask as any).allRemoved = true;
target[symbolEventName] = null;
}
existingTask.zone.cancelTask(existingTask);
if (returnTarget) {
return target;
}
return;
}
}
}
// issue 930, didn't find the event name or callback
// from zone kept existingTasks, the callback maybe
// added outside of zone, we need to call native removeEventListener
// to try to remove it.
return nativeRemoveEventListener.apply(this, arguments);
};
proto[LISTENERS_EVENT_LISTENER] = function() {
const target = this || _global;
const eventName = arguments[0];
const listeners: any[] = [];
const tasks =
findEventTasks(target, eventNameToString ? eventNameToString(eventName) : eventName);
for (let i = 0; i < tasks.length; i++) {
const task: any = tasks[i];
let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
listeners.push(delegate);
}
return listeners;
};
proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER] = function() {
const target = this || _global;
const eventName = arguments[0];
if (!eventName) {
const keys = Object.keys(target);
for (let i = 0; i < keys.length; i++) {
const prop = keys[i];
const match = EVENT_NAME_SYMBOL_REGX.exec(prop);
let evtName = match && match[1];
// in nodejs EventEmitter, removeListener event is
// used for monitoring the removeListener call,
// so just keep removeListener eventListener until
// all other eventListeners are removed
if (evtName && evtName !== 'removeListener') {
this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, evtName);
}
}
// remove removeListener listener finally
this[REMOVE_ALL_LISTENERS_EVENT_LISTENER].call(this, 'removeListener');
} else {
const symbolEventNames = zoneSymbolEventNames[eventName];
if (symbolEventNames) {
const symbolEventName = symbolEventNames[FALSE_STR];
const symbolCaptureEventName = symbolEventNames[TRUE_STR];
const tasks = target[symbolEventName];
const captureTasks = target[symbolCaptureEventName];
if (tasks) {
const removeTasks = tasks.slice();
for (let i = 0; i < removeTasks.length; i++) {
const task = removeTasks[i];
let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
}
}
if (captureTasks) {
const removeTasks = captureTasks.slice();
for (let i = 0; i < removeTasks.length; i++) {
const task = removeTasks[i];
let delegate = task.originalDelegate ? task.originalDelegate : task.callback;
this[REMOVE_EVENT_LISTENER].call(this, eventName, delegate, task.options);
}
}
}
}
if (returnTarget) {
return this;
}
};
// for native toString patch
attachOriginToPatched(proto[ADD_EVENT_LISTENER], nativeAddEventListener);
attachOriginToPatched(proto[REMOVE_EVENT_LISTENER], nativeRemoveEventListener);
if (nativeRemoveAllListeners) {
attachOriginToPatched(proto[REMOVE_ALL_LISTENERS_EVENT_LISTENER], nativeRemoveAllListeners);
}
if (nativeListeners) {
attachOriginToPatched(proto[LISTENERS_EVENT_LISTENER], nativeListeners);
}
return true;
}
let results: any[] = [];
for (let i = 0; i < apis.length; i++) {
results[i] = patchEventTargetMethods(apis[i], patchOptions);
}
return results;
}
export function findEventTasks(target: any, eventName: string): Task[] {
const foundTasks: any[] = [];
for (let prop in target) {
const match = EVENT_NAME_SYMBOL_REGX.exec(prop);
let evtName = match && match[1];
if (evtName && (!eventName || evtName === eventName)) {
const tasks: any = target[prop];
if (tasks) {
for (let i = 0; i < tasks.length; i++) {
foundTasks.push(tasks[i]);
}
}
}
}
return foundTasks;
}
export function patchEventPrototype(global: any, api: _ZonePrivate) {
const Event = global['Event'];
if (Event && Event.prototype) {
api.patchMethod(
Event.prototype, 'stopImmediatePropagation',
(delegate: Function) => function(self: any, args: any[]) {
self[IMMEDIATE_PROPAGATION_SYMBOL] = true;
// we need to call the native stopImmediatePropagation
// in case in some hybrid application, some part of
// application will be controlled by zone, some are not
delegate && delegate.apply(self, args);
});
}
}

View File

@ -0,0 +1,112 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @fileoverview
* @suppress {missingRequire}
*/
Zone.__load_patch('fetch', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
interface FetchTaskData extends TaskData {
fetchArgs?: any[];
}
let fetch = global['fetch'];
if (typeof fetch !== 'function') {
return;
}
const originalFetch = global[api.symbol('fetch')];
if (originalFetch) {
// restore unpatched fetch first
fetch = originalFetch;
}
const ZoneAwarePromise = global.Promise;
const symbolThenPatched = api.symbol('thenPatched');
const fetchTaskScheduling = api.symbol('fetchTaskScheduling');
const fetchTaskAborting = api.symbol('fetchTaskAborting');
const OriginalAbortController = global['AbortController'];
const supportAbort = typeof OriginalAbortController === 'function';
let abortNative: Function|null = null;
if (supportAbort) {
global['AbortController'] = function() {
const abortController = new OriginalAbortController();
const signal = abortController.signal;
signal.abortController = abortController;
return abortController;
};
abortNative = api.patchMethod(
OriginalAbortController.prototype, 'abort',
(delegate: Function) => (self: any, args: any) => {
if (self.task) {
return self.task.zone.cancelTask(self.task);
}
return delegate.apply(self, args);
});
}
const placeholder = function() {};
global['fetch'] = function() {
const args = Array.prototype.slice.call(arguments);
const options = args.length > 1 ? args[1] : null;
const signal = options && options.signal;
return new Promise((res, rej) => {
const task = Zone.current.scheduleMacroTask(
'fetch', placeholder, { fetchArgs: args } as FetchTaskData,
() => {
let fetchPromise;
let zone = Zone.current;
try {
(zone as any)[fetchTaskScheduling] = true;
fetchPromise = fetch.apply(this, args);
} catch (error) {
rej(error);
return;
} finally {
(zone as any)[fetchTaskScheduling] = false;
}
if (!(fetchPromise instanceof ZoneAwarePromise)) {
let ctor = fetchPromise.constructor;
if (!ctor[symbolThenPatched]) {
api.patchThen(ctor);
}
}
fetchPromise.then(
(resource: any) => {
if (task.state !== 'notScheduled') {
task.invoke();
}
res(resource);
},
(error: any) => {
if (task.state !== 'notScheduled') {
task.invoke();
}
rej(error);
});
},
() => {
if (!supportAbort) {
rej('No AbortController supported, can not cancel fetch');
return;
}
if (signal && signal.abortController && !signal.aborted &&
typeof signal.abortController.abort === 'function' && abortNative) {
try {
(Zone.current as any)[fetchTaskAborting] = true;
abortNative.call(signal.abortController);
} finally {
(Zone.current as any)[fetchTaskAborting] = false;
}
} else {
rej('cancel fetch need a AbortController.signal');
}
});
if (signal && signal.abortController) {
signal.abortController.task = task;
}
});
};
});

View File

@ -0,0 +1,481 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const ObjectDefineProperty = Object.defineProperty;
function readableObjectToString(obj: any) {
if (obj && obj.toString === Object.prototype.toString) {
const className = obj.constructor && obj.constructor.name;
return (className ? className : '') + ': ' + JSON.stringify(obj);
}
return obj ? obj.toString() : Object.prototype.toString.call(obj);
}
const __symbol__ = api.symbol;
const _uncaughtPromiseErrors: UncaughtPromiseError[] = [];
const symbolPromise = __symbol__('Promise');
const symbolThen = __symbol__('then');
const creationTrace = '__creationTrace__';
api.onUnhandledError = (e: any) => {
if (api.showUncaughtError()) {
const rejection = e && e.rejection;
if (rejection) {
console.error(
'Unhandled Promise rejection:',
rejection instanceof Error ? rejection.message : rejection, '; Zone:',
(<Zone>e.zone).name, '; Task:', e.task && (<Task>e.task).source, '; Value:', rejection,
rejection instanceof Error ? rejection.stack : undefined);
} else {
console.error(e);
}
}
};
api.microtaskDrainDone = () => {
while (_uncaughtPromiseErrors.length) {
while (_uncaughtPromiseErrors.length) {
const uncaughtPromiseError: UncaughtPromiseError = _uncaughtPromiseErrors.shift() !;
try {
uncaughtPromiseError.zone.runGuarded(() => { throw uncaughtPromiseError; });
} catch (error) {
handleUnhandledRejection(error);
}
}
}
};
const UNHANDLED_PROMISE_REJECTION_HANDLER_SYMBOL = __symbol__('unhandledPromiseRejectionHandler');
function handleUnhandledRejection(e: any) {
api.onUnhandledError(e);
try {
const handler = (Zone as any)[UNHANDLED_PROMISE_REJECTION_HANDLER_SYMBOL];
if (handler && typeof handler === 'function') {
handler.call(this, e);
}
} catch (err) {
}
}
function isThenable(value: any): boolean { return value && value.then; }
function forwardResolution(value: any): any { return value; }
function forwardRejection(rejection: any): any { return ZoneAwarePromise.reject(rejection); }
const symbolState: string = __symbol__('state');
const symbolValue: string = __symbol__('value');
const symbolFinally: string = __symbol__('finally');
const symbolParentPromiseValue: string = __symbol__('parentPromiseValue');
const symbolParentPromiseState: string = __symbol__('parentPromiseState');
const source: string = 'Promise.then';
const UNRESOLVED: null = null;
const RESOLVED = true;
const REJECTED = false;
const REJECTED_NO_CATCH = 0;
function makeResolver(promise: ZoneAwarePromise<any>, state: boolean): (value: any) => void {
return (v) => {
try {
resolvePromise(promise, state, v);
} catch (err) {
resolvePromise(promise, false, err);
}
// Do not return value or you will break the Promise spec.
};
}
const once = function() {
let wasCalled = false;
return function wrapper(wrappedFunction: Function) {
return function() {
if (wasCalled) {
return;
}
wasCalled = true;
wrappedFunction.apply(null, arguments);
};
};
};
const TYPE_ERROR = 'Promise resolved with itself';
const CURRENT_TASK_TRACE_SYMBOL = __symbol__('currentTaskTrace');
// Promise Resolution
function resolvePromise(
promise: ZoneAwarePromise<any>, state: boolean, value: any): ZoneAwarePromise<any> {
const onceWrapper = once();
if (promise === value) {
throw new TypeError(TYPE_ERROR);
}
if ((promise as any)[symbolState] === UNRESOLVED) {
// should only get value.then once based on promise spec.
let then: any = null;
try {
if (typeof value === 'object' || typeof value === 'function') {
then = value && value.then;
}
} catch (err) {
onceWrapper(() => { resolvePromise(promise, false, err); })();
return promise;
}
// if (value instanceof ZoneAwarePromise) {
if (state !== REJECTED && value instanceof ZoneAwarePromise &&
value.hasOwnProperty(symbolState) && value.hasOwnProperty(symbolValue) &&
(value as any)[symbolState] !== UNRESOLVED) {
clearRejectedNoCatch(<Promise<any>>value as any);
resolvePromise(promise, (value as any)[symbolState], (value as any)[symbolValue]);
} else if (state !== REJECTED && typeof then === 'function') {
try {
then.call(
value, onceWrapper(makeResolver(promise, state)),
onceWrapper(makeResolver(promise, false)));
} catch (err) {
onceWrapper(() => { resolvePromise(promise, false, err); })();
}
} else {
(promise as any)[symbolState] = state;
const queue = (promise as any)[symbolValue];
(promise as any)[symbolValue] = value;
if ((promise as any)[symbolFinally] === symbolFinally) {
// the promise is generated by Promise.prototype.finally
if (state === RESOLVED) {
// the state is resolved, should ignore the value
// and use parent promise value
(promise as any)[symbolState] = (promise as any)[symbolParentPromiseState];
(promise as any)[symbolValue] = (promise as any)[symbolParentPromiseValue];
}
}
// record task information in value when error occurs, so we can
// do some additional work such as render longStackTrace
if (state === REJECTED && value instanceof Error) {
// check if longStackTraceZone is here
const trace = Zone.currentTask && Zone.currentTask.data &&
(Zone.currentTask.data as any)[creationTrace];
if (trace) {
// only keep the long stack trace into error when in longStackTraceZone
ObjectDefineProperty(
value, CURRENT_TASK_TRACE_SYMBOL,
{configurable: true, enumerable: false, writable: true, value: trace});
}
}
for (let i = 0; i < queue.length;) {
scheduleResolveOrReject(promise, queue[i++], queue[i++], queue[i++], queue[i++]);
}
if (queue.length == 0 && state == REJECTED) {
(promise as any)[symbolState] = REJECTED_NO_CATCH;
try {
// try to print more readable error log
throw new Error(
'Uncaught (in promise): ' + readableObjectToString(value) +
(value && value.stack ? '\n' + value.stack : ''));
} catch (err) {
const error: UncaughtPromiseError = err;
error.rejection = value;
error.promise = promise;
error.zone = Zone.current;
error.task = Zone.currentTask !;
_uncaughtPromiseErrors.push(error);
api.scheduleMicroTask(); // to make sure that it is running
}
}
}
}
// Resolving an already resolved promise is a noop.
return promise;
}
const REJECTION_HANDLED_HANDLER = __symbol__('rejectionHandledHandler');
function clearRejectedNoCatch(promise: ZoneAwarePromise<any>): void {
if ((promise as any)[symbolState] === REJECTED_NO_CATCH) {
// if the promise is rejected no catch status
// and queue.length > 0, means there is a error handler
// here to handle the rejected promise, we should trigger
// windows.rejectionhandled eventHandler or nodejs rejectionHandled
// eventHandler
try {
const handler = (Zone as any)[REJECTION_HANDLED_HANDLER];
if (handler && typeof handler === 'function') {
handler.call(this, {rejection: (promise as any)[symbolValue], promise: promise});
}
} catch (err) {
}
(promise as any)[symbolState] = REJECTED;
for (let i = 0; i < _uncaughtPromiseErrors.length; i++) {
if (promise === _uncaughtPromiseErrors[i].promise) {
_uncaughtPromiseErrors.splice(i, 1);
}
}
}
}
function scheduleResolveOrReject<R, U1, U2>(
promise: ZoneAwarePromise<any>, zone: AmbientZone, chainPromise: ZoneAwarePromise<any>,
onFulfilled?: ((value: R) => U1) | null | undefined,
onRejected?: ((error: any) => U2) | null | undefined): void {
clearRejectedNoCatch(promise);
const promiseState = (promise as any)[symbolState];
const delegate = promiseState ?
(typeof onFulfilled === 'function') ? onFulfilled : forwardResolution :
(typeof onRejected === 'function') ? onRejected : forwardRejection;
zone.scheduleMicroTask(source, () => {
try {
const parentPromiseValue = (promise as any)[symbolValue];
const isFinallyPromise =
!!chainPromise && symbolFinally === (chainPromise as any)[symbolFinally];
if (isFinallyPromise) {
// if the promise is generated from finally call, keep parent promise's state and value
(chainPromise as any)[symbolParentPromiseValue] = parentPromiseValue;
(chainPromise as any)[symbolParentPromiseState] = promiseState;
}
// should not pass value to finally callback
const value = zone.run(
delegate, undefined,
isFinallyPromise && delegate !== forwardRejection && delegate !== forwardResolution ?
[] :
[parentPromiseValue]);
resolvePromise(chainPromise, true, value);
} catch (error) {
// if error occurs, should always return this error
resolvePromise(chainPromise, false, error);
}
}, chainPromise as TaskData);
}
const ZONE_AWARE_PROMISE_TO_STRING = 'function ZoneAwarePromise() { [native code] }';
class ZoneAwarePromise<R> implements Promise<R> {
static toString() { return ZONE_AWARE_PROMISE_TO_STRING; }
static resolve<R>(value: R): Promise<R> {
return resolvePromise(<ZoneAwarePromise<R>>new this(null as any), RESOLVED, value);
}
static reject<U>(error: U): Promise<U> {
return resolvePromise(<ZoneAwarePromise<U>>new this(null as any), REJECTED, error);
}
static race<R>(values: PromiseLike<any>[]): Promise<R> {
let resolve: (v: any) => void;
let reject: (v: any) => void;
let promise: any = new this((res, rej) => {
resolve = res;
reject = rej;
});
function onResolve(value: any) { resolve(value); }
function onReject(error: any) { reject(error); }
for (let value of values) {
if (!isThenable(value)) {
value = this.resolve(value);
}
value.then(onResolve, onReject);
}
return promise;
}
static all<R>(values: any): Promise<R> {
let resolve: (v: any) => void;
let reject: (v: any) => void;
let promise = new this<R>((res, rej) => {
resolve = res;
reject = rej;
});
// Start at 2 to prevent prematurely resolving if .then is called immediately.
let unresolvedCount = 2;
let valueIndex = 0;
const resolvedValues: any[] = [];
for (let value of values) {
if (!isThenable(value)) {
value = this.resolve(value);
}
const curValueIndex = valueIndex;
value.then((value: any) => {
resolvedValues[curValueIndex] = value;
unresolvedCount--;
if (unresolvedCount === 0) {
resolve !(resolvedValues);
}
}, reject !);
unresolvedCount++;
valueIndex++;
}
// Make the unresolvedCount zero-based again.
unresolvedCount -= 2;
if (unresolvedCount === 0) {
resolve !(resolvedValues);
}
return promise;
}
constructor(
executor:
(resolve: (value?: R|PromiseLike<R>) => void, reject: (error?: any) => void) => void) {
const promise: ZoneAwarePromise<R> = this;
if (!(promise instanceof ZoneAwarePromise)) {
throw new Error('Must be an instanceof Promise.');
}
(promise as any)[symbolState] = UNRESOLVED;
(promise as any)[symbolValue] = []; // queue;
try {
executor && executor(makeResolver(promise, RESOLVED), makeResolver(promise, REJECTED));
} catch (error) {
resolvePromise(promise, false, error);
}
}
get[Symbol.toStringTag]() { return 'Promise' as any; }
then<TResult1 = R, TResult2 = never>(
onFulfilled?: ((value: R) => TResult1 | PromiseLike<TResult1>)|undefined|null,
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>)|undefined|
null): Promise<TResult1|TResult2> {
const chainPromise: Promise<TResult1|TResult2> =
new (this.constructor as typeof ZoneAwarePromise)(null as any);
const zone = Zone.current;
if ((this as any)[symbolState] == UNRESOLVED) {
(<any[]>(this as any)[symbolValue]).push(zone, chainPromise, onFulfilled, onRejected);
} else {
scheduleResolveOrReject(this, zone, chainPromise as any, onFulfilled, onRejected);
}
return chainPromise;
}
catch<TResult = never>(onRejected?: ((reason: any) => TResult | PromiseLike<TResult>)|undefined|
null): Promise<R|TResult> {
return this.then(null, onRejected);
}
finally<U>(onFinally?: () => U | PromiseLike<U>): Promise<R> {
const chainPromise: Promise<R|never> =
new (this.constructor as typeof ZoneAwarePromise)(null as any);
(chainPromise as any)[symbolFinally] = symbolFinally;
const zone = Zone.current;
if ((this as any)[symbolState] == UNRESOLVED) {
(<any[]>(this as any)[symbolValue]).push(zone, chainPromise, onFinally, onFinally);
} else {
scheduleResolveOrReject(this, zone, chainPromise as any, onFinally, onFinally);
}
return chainPromise;
}
}
// Protect against aggressive optimizers dropping seemingly unused properties.
// E.g. Closure Compiler in advanced mode.
ZoneAwarePromise['resolve'] = ZoneAwarePromise.resolve;
ZoneAwarePromise['reject'] = ZoneAwarePromise.reject;
ZoneAwarePromise['race'] = ZoneAwarePromise.race;
ZoneAwarePromise['all'] = ZoneAwarePromise.all;
const NativePromise = global[symbolPromise] = global['Promise'];
const ZONE_AWARE_PROMISE = Zone.__symbol__('ZoneAwarePromise');
let desc = ObjectGetOwnPropertyDescriptor(global, 'Promise');
if (!desc || desc.configurable) {
desc && delete desc.writable;
desc && delete desc.value;
if (!desc) {
desc = {configurable: true, enumerable: true};
}
desc.get = function() {
// if we already set ZoneAwarePromise, use patched one
// otherwise return native one.
return global[ZONE_AWARE_PROMISE] ? global[ZONE_AWARE_PROMISE] : global[symbolPromise];
};
desc.set = function(NewNativePromise) {
if (NewNativePromise === ZoneAwarePromise) {
// if the NewNativePromise is ZoneAwarePromise
// save to global
global[ZONE_AWARE_PROMISE] = NewNativePromise;
} else {
// if the NewNativePromise is not ZoneAwarePromise
// for example: after load zone.js, some library just
// set es6-promise to global, if we set it to global
// directly, assertZonePatched will fail and angular
// will not loaded, so we just set the NewNativePromise
// to global[symbolPromise], so the result is just like
// we load ES6 Promise before zone.js
global[symbolPromise] = NewNativePromise;
if (!NewNativePromise.prototype[symbolThen]) {
patchThen(NewNativePromise);
}
api.setNativePromise(NewNativePromise);
}
};
ObjectDefineProperty(global, 'Promise', desc);
}
global['Promise'] = ZoneAwarePromise;
const symbolThenPatched = __symbol__('thenPatched');
function patchThen(Ctor: Function) {
const proto = Ctor.prototype;
const prop = ObjectGetOwnPropertyDescriptor(proto, 'then');
if (prop && (prop.writable === false || !prop.configurable)) {
// check Ctor.prototype.then propertyDescriptor is writable or not
// in meteor env, writable is false, we should ignore such case
return;
}
const originalThen = proto.then;
// Keep a reference to the original method.
proto[symbolThen] = originalThen;
Ctor.prototype.then = function(onResolve: any, onReject: any) {
const wrapped =
new ZoneAwarePromise((resolve, reject) => { originalThen.call(this, resolve, reject); });
return wrapped.then(onResolve, onReject);
};
(Ctor as any)[symbolThenPatched] = true;
}
api.patchThen = patchThen;
function zoneify(fn: Function) {
return function() {
let resultPromise = fn.apply(this, arguments);
if (resultPromise instanceof ZoneAwarePromise) {
return resultPromise;
}
let ctor = resultPromise.constructor;
if (!ctor[symbolThenPatched]) {
patchThen(ctor);
}
return resultPromise;
};
}
if (NativePromise) {
patchThen(NativePromise);
const fetch = global['fetch'];
if (typeof fetch == 'function') {
global[api.symbol('fetch')] = fetch;
global['fetch'] = zoneify(fetch);
}
}
// This is not part of public API, but it is useful for tests, so we expose it.
(Promise as any)[Zone.__symbol__('uncaughtPromiseErrors')] = _uncaughtPromiseErrors;
return ZoneAwarePromise;
});

View File

@ -0,0 +1,133 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* @fileoverview
* @suppress {missingRequire}
*/
import {patchMethod, scheduleMacroTaskWithCurrentZone, zoneSymbol} from './utils';
const taskSymbol = zoneSymbol('zoneTask');
interface TimerOptions extends TaskData {
handleId?: number;
args: any[];
}
export function patchTimer(window: any, setName: string, cancelName: string, nameSuffix: string) {
let setNative: Function|null = null;
let clearNative: Function|null = null;
setName += nameSuffix;
cancelName += nameSuffix;
const tasksByHandleId: {[id: number]: Task} = {};
function scheduleTask(task: Task) {
const data = <TimerOptions>task.data;
function timer() {
try {
task.invoke.apply(this, arguments);
} finally {
// issue-934, task will be cancelled
// even it is a periodic task such as
// setInterval
if (!(task.data && task.data.isPeriodic)) {
if (typeof data.handleId === 'number') {
// in non-nodejs env, we remove timerId
// from local cache
delete tasksByHandleId[data.handleId];
} else if (data.handleId) {
// Node returns complex objects as handleIds
// we remove task reference from timer object
(data.handleId as any)[taskSymbol] = null;
}
}
}
}
data.args[0] = timer;
data.handleId = setNative !.apply(window, data.args);
return task;
}
function clearTask(task: Task) { return clearNative !((<TimerOptions>task.data).handleId); }
setNative =
patchMethod(window, setName, (delegate: Function) => function(self: any, args: any[]) {
if (typeof args[0] === 'function') {
const options: TimerOptions = {
isPeriodic: nameSuffix === 'Interval',
delay: (nameSuffix === 'Timeout' || nameSuffix === 'Interval') ? args[1] || 0 :
undefined,
args: args
};
const task =
scheduleMacroTaskWithCurrentZone(setName, args[0], options, scheduleTask, clearTask);
if (!task) {
return task;
}
// Node.js must additionally support the ref and unref functions.
const handle: any = (<TimerOptions>task.data).handleId;
if (typeof handle === 'number') {
// for non nodejs env, we save handleId: task
// mapping in local cache for clearTimeout
tasksByHandleId[handle] = task;
} else if (handle) {
// for nodejs env, we save task
// reference in timerId Object for clearTimeout
handle[taskSymbol] = task;
}
// check whether handle is null, because some polyfill or browser
// may return undefined from setTimeout/setInterval/setImmediate/requestAnimationFrame
if (handle && handle.ref && handle.unref && typeof handle.ref === 'function' &&
typeof handle.unref === 'function') {
(<any>task).ref = (<any>handle).ref.bind(handle);
(<any>task).unref = (<any>handle).unref.bind(handle);
}
if (typeof handle === 'number' || handle) {
return handle;
}
return task;
} else {
// cause an error by calling it directly.
return delegate.apply(window, args);
}
});
clearNative =
patchMethod(window, cancelName, (delegate: Function) => function(self: any, args: any[]) {
const id = args[0];
let task: Task;
if (typeof id === 'number') {
// non nodejs env.
task = tasksByHandleId[id];
} else {
// nodejs env.
task = id && id[taskSymbol];
// other environments.
if (!task) {
task = id;
}
}
if (task && typeof task.type === 'string') {
if (task.state !== 'notScheduled' &&
(task.cancelFn && task.data !.isPeriodic || task.runCount === 0)) {
if (typeof id === 'number') {
delete tasksByHandleId[id];
} else if (id) {
id[taskSymbol] = null;
}
// Do not cancel already canceled functions
task.zone.cancelTask(task);
}
} else {
// cause an error by calling it directly.
delegate.apply(window, args);
}
});
}

View File

@ -0,0 +1,57 @@
/**
* @license
* Copyright Google Inc. 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 {zoneSymbol} from './utils';
// override Function.prototype.toString to make zone.js patched function
// look like native function
Zone.__load_patch('toString', (global: any) => {
// patch Func.prototype.toString to let them look like native
const originalFunctionToString = Function.prototype.toString;
const ORIGINAL_DELEGATE_SYMBOL = zoneSymbol('OriginalDelegate');
const PROMISE_SYMBOL = zoneSymbol('Promise');
const ERROR_SYMBOL = zoneSymbol('Error');
const newFunctionToString = function toString() {
if (typeof this === 'function') {
const originalDelegate = this[ORIGINAL_DELEGATE_SYMBOL];
if (originalDelegate) {
if (typeof originalDelegate === 'function') {
return originalFunctionToString.call(originalDelegate);
} else {
return Object.prototype.toString.call(originalDelegate);
}
}
if (this === Promise) {
const nativePromise = global[PROMISE_SYMBOL];
if (nativePromise) {
return originalFunctionToString.call(nativePromise);
}
}
if (this === Error) {
const nativeError = global[ERROR_SYMBOL];
if (nativeError) {
return originalFunctionToString.call(nativeError);
}
}
}
return originalFunctionToString.call(this);
};
(newFunctionToString as any)[ORIGINAL_DELEGATE_SYMBOL] = originalFunctionToString;
Function.prototype.toString = newFunctionToString;
// patch Object.prototype.toString to let them look like native
const originalObjectToString = Object.prototype.toString;
const PROMISE_OBJECT_TO_STRING = '[object Promise]';
Object.prototype.toString = function() {
if (this instanceof Promise) {
return PROMISE_OBJECT_TO_STRING;
}
return originalObjectToString.call(this);
};
});

View File

@ -0,0 +1,509 @@
/**
* @license
* Copyright Google Inc. 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
*/
/**
* Suppress closure compiler errors about unknown 'Zone' variable
* @fileoverview
* @suppress {undefinedVars,globalThis,missingRequire}
*/
/// <reference types="node"/>
// issue #989, to reduce bundle size, use short name
/** Object.getOwnPropertyDescriptor */
export const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
/** Object.defineProperty */
export const ObjectDefineProperty = Object.defineProperty;
/** Object.getPrototypeOf */
export const ObjectGetPrototypeOf = Object.getPrototypeOf;
/** Object.create */
export const ObjectCreate = Object.create;
/** Array.prototype.slice */
export const ArraySlice = Array.prototype.slice;
/** addEventListener string const */
export const ADD_EVENT_LISTENER_STR = 'addEventListener';
/** removeEventListener string const */
export const REMOVE_EVENT_LISTENER_STR = 'removeEventListener';
/** zoneSymbol addEventListener */
export const ZONE_SYMBOL_ADD_EVENT_LISTENER = Zone.__symbol__(ADD_EVENT_LISTENER_STR);
/** zoneSymbol removeEventListener */
export const ZONE_SYMBOL_REMOVE_EVENT_LISTENER = Zone.__symbol__(REMOVE_EVENT_LISTENER_STR);
/** true string const */
export const TRUE_STR = 'true';
/** false string const */
export const FALSE_STR = 'false';
/** Zone symbol prefix string const. */
export const ZONE_SYMBOL_PREFIX = Zone.__symbol__('');
export function wrapWithCurrentZone<T extends Function>(callback: T, source: string): T {
return Zone.current.wrap(callback, source);
}
export function scheduleMacroTaskWithCurrentZone(
source: string, callback: Function, data?: TaskData, customSchedule?: (task: Task) => void,
customCancel?: (task: Task) => void): MacroTask {
return Zone.current.scheduleMacroTask(source, callback, data, customSchedule, customCancel);
}
// Hack since TypeScript isn't compiling this for a worker.
declare const WorkerGlobalScope: any;
export const zoneSymbol = Zone.__symbol__;
const isWindowExists = typeof window !== 'undefined';
const internalWindow: any = isWindowExists ? window : undefined;
const _global: any = isWindowExists && internalWindow || typeof self === 'object' && self || global;
const REMOVE_ATTRIBUTE = 'removeAttribute';
const NULL_ON_PROP_VALUE: [any] = [null];
export function bindArguments(args: any[], source: string): any[] {
for (let i = args.length - 1; i >= 0; i--) {
if (typeof args[i] === 'function') {
args[i] = wrapWithCurrentZone(args[i], source + '_' + i);
}
}
return args;
}
export function patchPrototype(prototype: any, fnNames: string[]) {
const source = prototype.constructor['name'];
for (let i = 0; i < fnNames.length; i++) {
const name = fnNames[i];
const delegate = prototype[name];
if (delegate) {
const prototypeDesc = ObjectGetOwnPropertyDescriptor(prototype, name);
if (!isPropertyWritable(prototypeDesc)) {
continue;
}
prototype[name] = ((delegate: Function) => {
const patched: any = function() {
return delegate.apply(this, bindArguments(<any>arguments, source + '.' + name));
};
attachOriginToPatched(patched, delegate);
return patched;
})(delegate);
}
}
}
export function isPropertyWritable(propertyDesc: any) {
if (!propertyDesc) {
return true;
}
if (propertyDesc.writable === false) {
return false;
}
return !(typeof propertyDesc.get === 'function' && typeof propertyDesc.set === 'undefined');
}
export const isWebWorker: boolean =
(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope);
// Make sure to access `process` through `_global` so that WebPack does not accidentally browserify
// this code.
export const isNode: boolean =
(!('nw' in _global) && typeof _global.process !== 'undefined' &&
{}.toString.call(_global.process) === '[object process]');
export const isBrowser: boolean =
!isNode && !isWebWorker && !!(isWindowExists && internalWindow['HTMLElement']);
// we are in electron of nw, so we are both browser and nodejs
// Make sure to access `process` through `_global` so that WebPack does not accidentally browserify
// this code.
export const isMix: boolean = typeof _global.process !== 'undefined' &&
{}.toString.call(_global.process) === '[object process]' && !isWebWorker &&
!!(isWindowExists && internalWindow['HTMLElement']);
const zoneSymbolEventNames: {[eventName: string]: string} = {};
const wrapFn = function(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;
if (!event) {
return;
}
let eventNameSymbol = zoneSymbolEventNames[event.type];
if (!eventNameSymbol) {
eventNameSymbol = zoneSymbolEventNames[event.type] = zoneSymbol('ON_PROPERTY' + event.type);
}
const target = this || event.target || _global;
const listener = target[eventNameSymbol];
let result;
if (isBrowser && target === internalWindow && event.type === 'error') {
// window.onerror have different signiture
// https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror#window.onerror
// and onerror callback will prevent default when callback return true
const errorEvent: ErrorEvent = event as any;
result = listener &&
listener.call(
this, errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno,
errorEvent.error);
if (result === true) {
event.preventDefault();
}
} else {
result = listener && listener.apply(this, arguments);
if (result != undefined && !result) {
event.preventDefault();
}
}
return result;
};
export function patchProperty(obj: any, prop: string, prototype?: any) {
let desc = ObjectGetOwnPropertyDescriptor(obj, prop);
if (!desc && prototype) {
// when patch window object, use prototype to check prop exist or not
const prototypeDesc = ObjectGetOwnPropertyDescriptor(prototype, prop);
if (prototypeDesc) {
desc = {enumerable: true, configurable: true};
}
}
// if the descriptor not exists or is not configurable
// just return
if (!desc || !desc.configurable) {
return;
}
const onPropPatchedSymbol = zoneSymbol('on' + prop + 'patched');
if (obj.hasOwnProperty(onPropPatchedSymbol) && obj[onPropPatchedSymbol]) {
return;
}
// A property descriptor cannot have getter/setter and be writable
// deleting the writable and value properties avoids this error:
//
// TypeError: property descriptors must not specify a value or be writable when a
// getter or setter has been specified
delete desc.writable;
delete desc.value;
const originalDescGet = desc.get;
const originalDescSet = desc.set;
// substr(2) cuz 'onclick' -> 'click', etc
const eventName = prop.substr(2);
let eventNameSymbol = zoneSymbolEventNames[eventName];
if (!eventNameSymbol) {
eventNameSymbol = zoneSymbolEventNames[eventName] = zoneSymbol('ON_PROPERTY' + eventName);
}
desc.set = function(newValue) {
// in some of windows's onproperty callback, this is undefined
// so we need to check it
let target = this;
if (!target && obj === _global) {
target = _global;
}
if (!target) {
return;
}
let previousValue = target[eventNameSymbol];
if (previousValue) {
target.removeEventListener(eventName, wrapFn);
}
// issue #978, when onload handler was added before loading zone.js
// we should remove it with originalDescSet
if (originalDescSet) {
originalDescSet.apply(target, NULL_ON_PROP_VALUE);
}
if (typeof newValue === 'function') {
target[eventNameSymbol] = newValue;
target.addEventListener(eventName, wrapFn, false);
} else {
target[eventNameSymbol] = null;
}
};
// The getter would return undefined for unassigned properties but the default value of an
// unassigned property is null
desc.get = function() {
// in some of windows's onproperty callback, this is undefined
// so we need to check it
let target = this;
if (!target && obj === _global) {
target = _global;
}
if (!target) {
return null;
}
const listener = target[eventNameSymbol];
if (listener) {
return listener;
} else if (originalDescGet) {
// result will be null when use inline event attribute,
// such as <button onclick="func();">OK</button>
// because the onclick function is internal raw uncompiled handler
// the onclick will be evaluated when first time event was triggered or
// the property is accessed, https://github.com/angular/zone.js/issues/525
// so we should use original native get to retrieve the handler
let value = originalDescGet && originalDescGet.call(this);
if (value) {
desc !.set !.call(this, value);
if (typeof target[REMOVE_ATTRIBUTE] === 'function') {
target.removeAttribute(prop);
}
return value;
}
}
return null;
};
ObjectDefineProperty(obj, prop, desc);
obj[onPropPatchedSymbol] = true;
}
export function patchOnProperties(obj: any, properties: string[] | null, prototype?: any) {
if (properties) {
for (let i = 0; i < properties.length; i++) {
patchProperty(obj, 'on' + properties[i], prototype);
}
} else {
const onProperties = [];
for (const prop in obj) {
if (prop.substr(0, 2) == 'on') {
onProperties.push(prop);
}
}
for (let j = 0; j < onProperties.length; j++) {
patchProperty(obj, onProperties[j], prototype);
}
}
}
const originalInstanceKey = zoneSymbol('originalInstance');
// wrap some native API on `window`
export function patchClass(className: string) {
const OriginalClass = _global[className];
if (!OriginalClass) return;
// keep original class in global
_global[zoneSymbol(className)] = OriginalClass;
_global[className] = function() {
const a = bindArguments(<any>arguments, className);
switch (a.length) {
case 0:
this[originalInstanceKey] = new OriginalClass();
break;
case 1:
this[originalInstanceKey] = new OriginalClass(a[0]);
break;
case 2:
this[originalInstanceKey] = new OriginalClass(a[0], a[1]);
break;
case 3:
this[originalInstanceKey] = new OriginalClass(a[0], a[1], a[2]);
break;
case 4:
this[originalInstanceKey] = new OriginalClass(a[0], a[1], a[2], a[3]);
break;
default:
throw new Error('Arg list too long.');
}
};
// attach original delegate to patched function
attachOriginToPatched(_global[className], OriginalClass);
const instance = new OriginalClass(function() {});
let prop;
for (prop in instance) {
// https://bugs.webkit.org/show_bug.cgi?id=44721
if (className === 'XMLHttpRequest' && prop === 'responseBlob') continue;
(function(prop) {
if (typeof instance[prop] === 'function') {
_global[className].prototype[prop] = function() {
return this[originalInstanceKey][prop].apply(this[originalInstanceKey], arguments);
};
} else {
ObjectDefineProperty(_global[className].prototype, prop, {
set: function(fn) {
if (typeof fn === 'function') {
this[originalInstanceKey][prop] = wrapWithCurrentZone(fn, className + '.' + prop);
// keep callback in wrapped function so we can
// use it in Function.prototype.toString to return
// the native one.
attachOriginToPatched(this[originalInstanceKey][prop], fn);
} else {
this[originalInstanceKey][prop] = fn;
}
},
get: function() { return this[originalInstanceKey][prop]; }
});
}
}(prop));
}
for (prop in OriginalClass) {
if (prop !== 'prototype' && OriginalClass.hasOwnProperty(prop)) {
_global[className][prop] = OriginalClass[prop];
}
}
}
export function copySymbolProperties(src: any, dest: any) {
if (typeof(Object as any).getOwnPropertySymbols !== 'function') {
return;
}
const symbols: any = (Object as any).getOwnPropertySymbols(src);
symbols.forEach((symbol: any) => {
const desc = Object.getOwnPropertyDescriptor(src, symbol);
Object.defineProperty(dest, symbol, {
get: function() { return src[symbol]; },
set: function(value: any) {
if (desc && (!desc.writable || typeof desc.set !== 'function')) {
// if src[symbol] is not writable or not have a setter, just return
return;
}
src[symbol] = value;
},
enumerable: desc ? desc.enumerable : true,
configurable: desc ? desc.configurable : true
});
});
}
let shouldCopySymbolProperties = false;
export function setShouldCopySymbolProperties(flag: boolean) {
shouldCopySymbolProperties = flag;
}
export function patchMethod(
target: any, name: string, patchFn: (delegate: Function, delegateName: string, name: string) =>
(self: any, args: any[]) => any): Function|null {
let proto = target;
while (proto && !proto.hasOwnProperty(name)) {
proto = ObjectGetPrototypeOf(proto);
}
if (!proto && target[name]) {
// somehow we did not find it, but we can see it. This happens on IE for Window properties.
proto = target;
}
const delegateName = zoneSymbol(name);
let delegate: Function|null = null;
if (proto && !(delegate = proto[delegateName])) {
delegate = proto[delegateName] = proto[name];
// check whether proto[name] is writable
// some property is readonly in safari, such as HtmlCanvasElement.prototype.toBlob
const desc = proto && ObjectGetOwnPropertyDescriptor(proto, name);
if (isPropertyWritable(desc)) {
const patchDelegate = patchFn(delegate !, delegateName, name);
proto[name] = function() { return patchDelegate(this, arguments as any); };
attachOriginToPatched(proto[name], delegate);
if (shouldCopySymbolProperties) {
copySymbolProperties(delegate, proto[name]);
}
}
}
return delegate;
}
export interface MacroTaskMeta extends TaskData {
name: string;
target: any;
cbIdx: number;
args: any[];
}
// TODO: @JiaLiPassion, support cancel task later if necessary
export function patchMacroTask(
obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MacroTaskMeta) {
let setNative: Function|null = null;
function scheduleTask(task: Task) {
const data = <MacroTaskMeta>task.data;
data.args[data.cbIdx] = function() { task.invoke.apply(this, arguments); };
setNative !.apply(data.target, data.args);
return task;
}
setNative = patchMethod(obj, funcName, (delegate: Function) => function(self: any, args: any[]) {
const meta = metaCreator(self, args);
if (meta.cbIdx >= 0 && typeof args[meta.cbIdx] === 'function') {
return scheduleMacroTaskWithCurrentZone(meta.name, args[meta.cbIdx], meta, scheduleTask);
} else {
// cause an error by calling it directly.
return delegate.apply(self, args);
}
});
}
export interface MicroTaskMeta extends TaskData {
name: string;
target: any;
cbIdx: number;
args: any[];
}
export function patchMicroTask(
obj: any, funcName: string, metaCreator: (self: any, args: any[]) => MicroTaskMeta) {
let setNative: Function|null = null;
function scheduleTask(task: Task) {
const data = <MacroTaskMeta>task.data;
data.args[data.cbIdx] = function() { task.invoke.apply(this, arguments); };
setNative !.apply(data.target, data.args);
return task;
}
setNative = patchMethod(obj, funcName, (delegate: Function) => function(self: any, args: any[]) {
const meta = metaCreator(self, args);
if (meta.cbIdx >= 0 && typeof args[meta.cbIdx] === 'function') {
return Zone.current.scheduleMicroTask(meta.name, args[meta.cbIdx], meta, scheduleTask);
} else {
// cause an error by calling it directly.
return delegate.apply(self, args);
}
});
}
export function attachOriginToPatched(patched: Function, original: any) {
(patched as any)[zoneSymbol('OriginalDelegate')] = original;
}
let isDetectedIEOrEdge = false;
let ieOrEdge = false;
export function isIE() {
try {
const ua = internalWindow.navigator.userAgent;
if (ua.indexOf('MSIE ') !== -1 || ua.indexOf('Trident/') !== -1) {
return true;
}
} catch (error) {
}
return false;
}
export function isIEOrEdge() {
if (isDetectedIEOrEdge) {
return ieOrEdge;
}
isDetectedIEOrEdge = true;
try {
const ua = internalWindow.navigator.userAgent;
if (ua.indexOf('MSIE ') !== -1 || ua.indexOf('Trident/') !== -1 || ua.indexOf('Edge/') !== -1) {
ieOrEdge = true;
}
} catch (error) {
}
return ieOrEdge;
}

View File

@ -0,0 +1,55 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('bluebird', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
// TODO: @JiaLiPassion, we can automatically patch bluebird
// if global.Promise = Bluebird, but sometimes in nodejs,
// global.Promise is not Bluebird, and Bluebird is just be
// used by other libraries such as sequelize, so I think it is
// safe to just expose a method to patch Bluebird explicitly
const BLUEBIRD = 'bluebird';
(Zone as any)[Zone.__symbol__(BLUEBIRD)] = function patchBluebird(Bluebird: any) {
// patch method of Bluebird.prototype which not using `then` internally
const bluebirdApis: string[] = ['then', 'spread', 'finally'];
bluebirdApis.forEach(bapi => {
api.patchMethod(
Bluebird.prototype, bapi, (delegate: Function) => (self: any, args: any[]) => {
const zone = Zone.current;
for (let i = 0; i < args.length; i++) {
const func = args[i];
if (typeof func === 'function') {
args[i] = function() {
const argSelf: any = this;
const argArgs: any = arguments;
return new Bluebird((res: any, rej: any) => {
zone.scheduleMicroTask('Promise.then', () => {
try {
res(func.apply(argSelf, argArgs));
} catch (error) {
rej(error);
}
});
});
};
}
}
return delegate.apply(self, args);
});
});
Bluebird.onPossiblyUnhandledRejection(function(e: any, promise: any) {
try {
Zone.current.runGuarded(() => { throw e; });
} catch (err) {
api.onUnhandledError(err);
}
});
// override global promise
global[api.symbol('ZoneAwarePromise')] = Bluebird;
};
});

View File

@ -0,0 +1,39 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('cordova', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
if (global.cordova) {
const SUCCESS_SOURCE = 'cordova.exec.success';
const ERROR_SOURCE = 'cordova.exec.error';
const FUNCTION = 'function';
const nativeExec: Function|null =
api.patchMethod(global.cordova, 'exec', () => function(self: any, args: any[]) {
if (args.length > 0 && typeof args[0] === FUNCTION) {
args[0] = Zone.current.wrap(args[0], SUCCESS_SOURCE);
}
if (args.length > 1 && typeof args[1] === FUNCTION) {
args[1] = Zone.current.wrap(args[1], ERROR_SOURCE);
}
return nativeExec !.apply(self, args);
});
}
});
Zone.__load_patch('cordova.FileReader', (global: any, Zone: ZoneType) => {
if (global.cordova && typeof global['FileReader'] !== 'undefined') {
document.addEventListener('deviceReady', () => {
const FileReader = global['FileReader'];
['abort', 'error', 'load', 'loadstart', 'loadend', 'progress'].forEach(prop => {
const eventNameSymbol = Zone.__symbol__('ON_PROPERTY' + prop);
Object.defineProperty(FileReader.prototype, eventNameSymbol, {
configurable: true,
get: function() { return this._realReader && this._realReader[eventNameSymbol]; }
});
});
});
}
});

View File

@ -0,0 +1,31 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('electron', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
function patchArguments(target: any, name: string, source: string): Function|null {
return api.patchMethod(target, name, (delegate: Function) => (self: any, args: any[]) => {
return delegate && delegate.apply(self, api.bindArguments(args, source));
});
}
const {desktopCapturer, shell, CallbacksRegistry} = require('electron');
// patch api in renderer process directly
// desktopCapturer
if (desktopCapturer) {
patchArguments(desktopCapturer, 'getSources', 'electron.desktopCapturer.getSources');
}
// shell
if (shell) {
patchArguments(shell, 'openExternal', 'electron.shell.openExternal');
}
// patch api in main process through CallbackRegistry
if (!CallbacksRegistry) {
return;
}
patchArguments(CallbacksRegistry.prototype, 'add', 'CallbackRegistry.add');
});

View File

@ -0,0 +1,77 @@
/**
* @license
* Copyright Google Inc. 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
*/
Zone.__load_patch('jsonp', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
const noop = function() {};
// because jsonp is not a standard api, there are a lot of
// implementations, so zone.js just provide a helper util to
// patch the jsonp send and onSuccess/onError callback
// the options is an object which contains
// - jsonp, the jsonp object which hold the send function
// - sendFuncName, the name of the send function
// - successFuncName, success func name
// - failedFuncName, failed func name
(Zone as any)[Zone.__symbol__('jsonp')] = function patchJsonp(options: any) {
if (!options || !options.jsonp || !options.sendFuncName) {
return;
}
const noop = function() {};
[options.successFuncName, options.failedFuncName].forEach(methodName => {
if (!methodName) {
return;
}
const oriFunc = global[methodName];
if (oriFunc) {
api.patchMethod(global, methodName, (delegate: Function) => (self: any, args: any[]) => {
const task = global[api.symbol('jsonTask')];
if (task) {
task.callback = delegate;
return task.invoke.apply(self, args);
} else {
return delegate.apply(self, args);
}
});
} else {
Object.defineProperty(global, methodName, {
configurable: true,
enumerable: true,
get: function() {
return function() {
const task = global[api.symbol('jsonpTask')];
const target = this ? this : global;
const delegate = global[api.symbol(`jsonp${methodName}callback`)];
if (task) {
if (delegate) {
task.callback = delegate;
}
global[api.symbol('jsonpTask')] = undefined;
return task.invoke.apply(this, arguments);
} else {
if (delegate) {
return delegate.apply(this, arguments);
}
}
return null;
};
},
set: function(callback: Function) {
this[api.symbol(`jsonp${methodName}callback`)] = callback;
}
});
}
});
api.patchMethod(
options.jsonp, options.sendFuncName, (delegate: Function) => (self: any, args: any[]) => {
global[api.symbol('jsonpTask')] = Zone.current.scheduleMacroTask(
'jsonp', noop, {}, (task: Task) => { return delegate.apply(self, args); }, noop);
});
};
});

View File

@ -0,0 +1,22 @@
/**
* @license
* Copyright Google Inc. 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
*/
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, [io.Socket.prototype], {
useG: false,
chkDup: false,
rt: true,
diff: (task: any, delegate: any) => { return task.callback === delegate; }
});
// also patch io.Socket.prototype.on/off/removeListener/removeAllListeners
io.Socket.prototype.on = io.Socket.prototype.addEventListener;
io.Socket.prototype.off = io.Socket.prototype.removeListener =
io.Socket.prototype.removeAllListeners = io.Socket.prototype.removeEventListener;
};
});

View File

@ -0,0 +1,302 @@
/**
* @license
* Copyright Google Inc. 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
*/
/// <reference types="jasmine"/>
'use strict';
((_global: any) => {
const __extends = function(d: any, b: any) {
for (const p in b)
if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new (__ as any)());
};
// Patch jasmine's describe/it/beforeEach/afterEach functions so test code always runs
// in a testZone (ProxyZone). (See: angular/zone.js#91 & angular/angular#10503)
if (!Zone) throw new Error('Missing: zone.js');
if (typeof jasmine == 'undefined') throw new Error('Missing: jasmine.js');
if ((jasmine as any)['__zone_patch__'])
throw new Error(`'jasmine' has already been patched with 'Zone'.`);
(jasmine as any)['__zone_patch__'] = true;
const SyncTestZoneSpec: {new (name: string): ZoneSpec} = (Zone as any)['SyncTestZoneSpec'];
const ProxyZoneSpec: {new (): ZoneSpec} = (Zone as any)['ProxyZoneSpec'];
if (!SyncTestZoneSpec) throw new Error('Missing: SyncTestZoneSpec');
if (!ProxyZoneSpec) throw new Error('Missing: ProxyZoneSpec');
const ambientZone = Zone.current;
// Create a synchronous-only zone in which to run `describe` blocks in order to raise an
// error if any asynchronous operations are attempted inside of a `describe` but outside of
// a `beforeEach` or `it`.
const syncZone = ambientZone.fork(new SyncTestZoneSpec('jasmine.describe'));
const symbol = Zone.__symbol__;
// whether patch jasmine clock when in fakeAsync
const disablePatchingJasmineClock = _global[symbol('fakeAsyncDisablePatchingClock')] === true;
// the original variable name fakeAsyncPatchLock is not accurate, so the name will be
// fakeAsyncAutoFakeAsyncWhenClockPatched and if this enablePatchingJasmineClock is false, we also
// automatically disable the auto jump into fakeAsync feature
const enableAutoFakeAsyncWhenClockPatched = !disablePatchingJasmineClock &&
((_global[symbol('fakeAsyncPatchLock')] === true) ||
(_global[symbol('fakeAsyncAutoFakeAsyncWhenClockPatched')] === true));
const ignoreUnhandledRejection = _global[symbol('ignoreUnhandledRejection')] === true;
if (!ignoreUnhandledRejection) {
const globalErrors = (jasmine as any).GlobalErrors;
if (globalErrors && !(jasmine as any)[symbol('GlobalErrors')]) {
(jasmine as any)[symbol('GlobalErrors')] = globalErrors;
(jasmine as any).GlobalErrors = function() {
const instance = new globalErrors();
const originalInstall = instance.install;
if (originalInstall && !instance[symbol('install')]) {
instance[symbol('install')] = originalInstall;
instance.install = function() {
const originalHandlers = process.listeners('unhandledRejection');
const r = originalInstall.apply(this, arguments);
process.removeAllListeners('unhandledRejection');
if (originalHandlers) {
originalHandlers.forEach(h => process.on('unhandledRejection', h));
}
return r;
};
}
return instance;
};
}
}
// Monkey patch all of the jasmine DSL so that each function runs in appropriate zone.
const jasmineEnv: any = jasmine.getEnv();
['describe', 'xdescribe', 'fdescribe'].forEach(methodName => {
let originalJasmineFn: Function = jasmineEnv[methodName];
jasmineEnv[methodName] = function(description: string, specDefinitions: Function) {
return originalJasmineFn.call(this, description, wrapDescribeInZone(specDefinitions));
};
});
['it', 'xit', 'fit'].forEach(methodName => {
let originalJasmineFn: Function = jasmineEnv[methodName];
jasmineEnv[symbol(methodName)] = originalJasmineFn;
jasmineEnv[methodName] = function(
description: string, specDefinitions: Function, timeout: number) {
arguments[1] = wrapTestInZone(specDefinitions);
return originalJasmineFn.apply(this, arguments);
};
});
['beforeEach', 'afterEach', 'beforeAll', 'afterAll'].forEach(methodName => {
let originalJasmineFn: Function = jasmineEnv[methodName];
jasmineEnv[symbol(methodName)] = originalJasmineFn;
jasmineEnv[methodName] = function(specDefinitions: Function, timeout: number) {
arguments[0] = wrapTestInZone(specDefinitions);
return originalJasmineFn.apply(this, arguments);
};
});
if (!disablePatchingJasmineClock) {
// need to patch jasmine.clock().mockDate and jasmine.clock().tick() so
// they can work properly in FakeAsyncTest
const originalClockFn: Function = ((jasmine as any)[symbol('clock')] = jasmine['clock']);
(jasmine as any)['clock'] = function() {
const clock = originalClockFn.apply(this, arguments);
if (!clock[symbol('patched')]) {
clock[symbol('patched')] = symbol('patched');
const originalTick = (clock[symbol('tick')] = clock.tick);
clock.tick = function() {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec) {
return fakeAsyncZoneSpec.tick.apply(fakeAsyncZoneSpec, arguments);
}
return originalTick.apply(this, arguments);
};
const originalMockDate = (clock[symbol('mockDate')] = clock.mockDate);
clock.mockDate = function() {
const fakeAsyncZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
if (fakeAsyncZoneSpec) {
const dateTime = arguments.length > 0 ? arguments[0] : new Date();
return fakeAsyncZoneSpec.setCurrentRealTime.apply(
fakeAsyncZoneSpec, dateTime && typeof dateTime.getTime === 'function' ?
[dateTime.getTime()] :
arguments);
}
return originalMockDate.apply(this, arguments);
};
// for auto go into fakeAsync feature, we need the flag to enable it
if (enableAutoFakeAsyncWhenClockPatched) {
['install', 'uninstall'].forEach(methodName => {
const originalClockFn: Function = (clock[symbol(methodName)] = clock[methodName]);
clock[methodName] = function() {
const FakeAsyncTestZoneSpec = (Zone as any)['FakeAsyncTestZoneSpec'];
if (FakeAsyncTestZoneSpec) {
(jasmine as any)[symbol('clockInstalled')] = 'install' === methodName;
return;
}
return originalClockFn.apply(this, arguments);
};
});
}
}
return clock;
};
}
/**
* Gets a function wrapping the body of a Jasmine `describe` block to execute in a
* synchronous-only zone.
*/
function wrapDescribeInZone(describeBody: Function): Function {
return function() { return syncZone.run(describeBody, this, (arguments as any) as any[]); };
}
function runInTestZone(testBody: Function, applyThis: any, queueRunner: any, done?: Function) {
const isClockInstalled = !!(jasmine as any)[symbol('clockInstalled')];
const testProxyZoneSpec = queueRunner.testProxyZoneSpec;
const testProxyZone = queueRunner.testProxyZone;
let lastDelegate;
if (isClockInstalled && enableAutoFakeAsyncWhenClockPatched) {
// auto run a fakeAsync
const fakeAsyncModule = (Zone as any)[Zone.__symbol__('fakeAsyncTest')];
if (fakeAsyncModule && typeof fakeAsyncModule.fakeAsync === 'function') {
testBody = fakeAsyncModule.fakeAsync(testBody);
}
}
if (done) {
return testProxyZone.run(testBody, applyThis, [done]);
} else {
return testProxyZone.run(testBody, applyThis);
}
}
/**
* Gets a function wrapping the body of a Jasmine `it/beforeEach/afterEach` block to
* execute in a ProxyZone zone.
* This will run in `testProxyZone`. The `testProxyZone` will be reset by the `ZoneQueueRunner`
*/
function wrapTestInZone(testBody: Function): Function {
// The `done` callback is only passed through if the function expects at least one argument.
// Note we have to make a function with correct number of arguments, otherwise jasmine will
// think that all functions are sync or async.
return (testBody && (testBody.length ? function(done: Function) {
return runInTestZone(testBody, this, this.queueRunner, done);
} : function() { return runInTestZone(testBody, this, this.queueRunner); }));
}
interface QueueRunner {
execute(): void;
}
interface QueueRunnerAttrs {
queueableFns: {fn: Function}[];
clearStack: (fn: any) => void;
catchException: () => boolean;
fail: () => void;
onComplete: () => void;
onException: (error: any) => void;
userContext: any;
timeout: {setTimeout: Function; clearTimeout: Function};
}
const QueueRunner = (jasmine as any).QueueRunner as {
new (attrs: QueueRunnerAttrs): QueueRunner;
};
(jasmine as any).QueueRunner = (function(_super) {
__extends(ZoneQueueRunner, _super);
function ZoneQueueRunner(attrs: QueueRunnerAttrs) {
attrs.onComplete = (fn => () => {
// All functions are done, clear the test zone.
this.testProxyZone = null;
this.testProxyZoneSpec = null;
ambientZone.scheduleMicroTask('jasmine.onComplete', fn);
})(attrs.onComplete);
const nativeSetTimeout = _global[Zone.__symbol__('setTimeout')];
const nativeClearTimeout = _global[Zone.__symbol__('clearTimeout')];
if (nativeSetTimeout) {
// should run setTimeout inside jasmine outside of zone
attrs.timeout = {
setTimeout: nativeSetTimeout ? nativeSetTimeout : _global.setTimeout,
clearTimeout: nativeClearTimeout ? nativeClearTimeout : _global.clearTimeout
};
}
// create a userContext to hold the queueRunner itself
// so we can access the testProxy in it/xit/beforeEach ...
if ((jasmine as any).UserContext) {
if (!attrs.userContext) {
attrs.userContext = new (jasmine as any).UserContext();
}
attrs.userContext.queueRunner = this;
} else {
if (!attrs.userContext) {
attrs.userContext = {};
}
attrs.userContext.queueRunner = this;
}
// patch attrs.onException
const onException = attrs.onException;
attrs.onException = function(error: any) {
if (error &&
error.message ===
'Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.') {
// jasmine timeout, we can make the error message more
// reasonable to tell what tasks are pending
const proxyZoneSpec: any = this && this.testProxyZoneSpec;
if (proxyZoneSpec) {
const pendingTasksInfo = proxyZoneSpec.getAndClearPendingTasksInfo();
try {
// try catch here in case error.message is not writable
error.message += pendingTasksInfo;
} catch (err) {
}
}
}
if (onException) {
onException.call(this, error);
}
};
_super.call(this, attrs);
}
ZoneQueueRunner.prototype.execute = function() {
let zone: Zone|null = Zone.current;
let isChildOfAmbientZone = false;
while (zone) {
if (zone === ambientZone) {
isChildOfAmbientZone = true;
break;
}
zone = zone.parent;
}
if (!isChildOfAmbientZone) throw new Error('Unexpected Zone: ' + Zone.current.name);
// This is the zone which will be used for running individual tests.
// It will be a proxy zone, so that the tests function can retroactively install
// different zones.
// Example:
// - In beforeEach() do childZone = Zone.current.fork(...);
// - In it() try to do fakeAsync(). The issue is that because the beforeEach forked the
// zone outside of fakeAsync it will be able to escape the fakeAsync rules.
// - Because ProxyZone is parent fo `childZone` fakeAsync can retroactively add
// fakeAsync behavior to the childZone.
this.testProxyZoneSpec = new ProxyZoneSpec();
this.testProxyZone = ambientZone.fork(this.testProxyZoneSpec);
if (!Zone.currentTask) {
// if we are not running in a task then if someone would register a
// element.addEventListener and then calling element.click() the
// addEventListener callback would think that it is the top most task and would
// drain the microtask queue on element.click() which would be incorrect.
// For this reason we always force a task when running jasmine tests.
Zone.current.scheduleMicroTask(
'jasmine.execute().forceTask', () => QueueRunner.prototype.execute.call(this));
} else {
_super.prototype.execute.call(this);
}
};
return ZoneQueueRunner;
})(QueueRunner);
})(global);

View File

@ -0,0 +1,13 @@
/**
* @license
* Copyright Google Inc. 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 '../zone';
import '../common/promise';
import '../common/to-string';
import '../browser/browser';
import '../node/node';

View File

@ -0,0 +1,161 @@
/**
* @license
* Copyright Google Inc. 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
*/
'use strict';
((context: any) => {
const Mocha = context.Mocha;
if (typeof Mocha === 'undefined') {
throw new Error('Missing Mocha.js');
}
if (typeof Zone === 'undefined') {
throw new Error('Missing Zone.js');
}
const ProxyZoneSpec = (Zone as any)['ProxyZoneSpec'];
const SyncTestZoneSpec = (Zone as any)['SyncTestZoneSpec'];
if (!ProxyZoneSpec) {
throw new Error('Missing ProxyZoneSpec');
}
if (Mocha['__zone_patch__']) {
throw new Error('"Mocha" has already been patched with "Zone".');
}
Mocha['__zone_patch__'] = true;
const rootZone = Zone.current;
const syncZone = rootZone.fork(new SyncTestZoneSpec('Mocha.describe'));
let testZone: Zone|null = null;
const suiteZone = rootZone.fork(new ProxyZoneSpec());
const mochaOriginal = {
after: Mocha.after,
afterEach: Mocha.afterEach,
before: Mocha.before,
beforeEach: Mocha.beforeEach,
describe: Mocha.describe,
it: Mocha.it
};
function modifyArguments(args: IArguments, syncTest: Function, asyncTest?: Function): any[] {
for (let i = 0; i < args.length; i++) {
let arg = args[i];
if (typeof arg === 'function') {
// The `done` callback is only passed through if the function expects at
// least one argument.
// Note we have to make a function with correct number of arguments,
// otherwise mocha will
// think that all functions are sync or async.
args[i] = (arg.length === 0) ? syncTest(arg) : asyncTest !(arg);
// Mocha uses toString to view the test body in the result list, make sure we return the
// correct function body
args[i].toString = function() { return arg.toString(); };
}
}
return args as any;
}
function wrapDescribeInZone(args: IArguments): any[] {
const syncTest: any = function(fn: Function) {
return function() { return syncZone.run(fn, this, arguments as any as any[]); };
};
return modifyArguments(args, syncTest);
}
function wrapTestInZone(args: IArguments): any[] {
const asyncTest = function(fn: Function) {
return function(done: Function) { return testZone !.run(fn, this, [done]); };
};
const syncTest: any = function(fn: Function) {
return function() { return testZone !.run(fn, this); };
};
return modifyArguments(args, syncTest, asyncTest);
}
function wrapSuiteInZone(args: IArguments): any[] {
const asyncTest = function(fn: Function) {
return function(done: Function) { return suiteZone.run(fn, this, [done]); };
};
const syncTest: any = function(fn: Function) {
return function() { return suiteZone.run(fn, this); };
};
return modifyArguments(args, syncTest, asyncTest);
}
context.describe = context.suite = Mocha.describe = function() {
return mochaOriginal.describe.apply(this, wrapDescribeInZone(arguments));
};
context.xdescribe = context.suite.skip = Mocha.describe.skip = function() {
return mochaOriginal.describe.skip.apply(this, wrapDescribeInZone(arguments));
};
context.describe.only = context.suite.only = Mocha.describe.only = function() {
return mochaOriginal.describe.only.apply(this, wrapDescribeInZone(arguments));
};
context.it = context.specify = context.test =
Mocha.it = function() { return mochaOriginal.it.apply(this, wrapTestInZone(arguments)); };
context.xit = context.xspecify = Mocha.it.skip = function() {
return mochaOriginal.it.skip.apply(this, wrapTestInZone(arguments));
};
context.it.only = context.test.only = Mocha.it.only = function() {
return mochaOriginal.it.only.apply(this, wrapTestInZone(arguments));
};
context.after = context.suiteTeardown = Mocha.after = function() {
return mochaOriginal.after.apply(this, wrapSuiteInZone(arguments));
};
context.afterEach = context.teardown = Mocha.afterEach = function() {
return mochaOriginal.afterEach.apply(this, wrapTestInZone(arguments));
};
context.before = context.suiteSetup = Mocha.before = function() {
return mochaOriginal.before.apply(this, wrapSuiteInZone(arguments));
};
context.beforeEach = context.setup = Mocha.beforeEach = function() {
return mochaOriginal.beforeEach.apply(this, wrapTestInZone(arguments));
};
((originalRunTest, originalRun) => {
Mocha.Runner.prototype.runTest = function(fn: Function) {
Zone.current.scheduleMicroTask('mocha.forceTask', () => { originalRunTest.call(this, fn); });
};
Mocha.Runner.prototype.run = function(fn: Function) {
this.on('test', (e: any) => { testZone = rootZone.fork(new ProxyZoneSpec()); });
this.on('fail', (test: any, err: any) => {
const proxyZoneSpec = testZone && testZone.get('ProxyZoneSpec');
if (proxyZoneSpec && err) {
try {
// try catch here in case err.message is not writable
err.message += proxyZoneSpec.getAndClearPendingTasksInfo();
} catch (error) {
}
}
});
return originalRun.call(this, fn);
};
})(Mocha.Runner.prototype.runTest, Mocha.Runner.prototype.run);
})(global);

Some files were not shown because too many files have changed in this diff Show More