Separate direct from curried calls of `describe`/`it` modifiers
(direct: `skip`, `only`, etc.; curried: `runIf`, `each`, etc.)
and perform the required patching to them.
Fixes: #69748
Initialize `zoneSymbolEventNames` and `patches` with `Object.create(null)` instead of `{}`.
This is a hardening change rather than a fix for an exploitable vulnerability. Calling `addEventListener('__proto__', fn)` is not directly attacker-controlled; its presence already implies an application bug. However, if such a call does occur, the current implementation can behave unexpectedly depending on the environment.
For `zoneSymbolEventNames`, accessing `zoneSymbolEventNames['__proto__']` on a plain object invokes the inherited `__proto__` accessor and returns `Object.prototype`, which is truthy. This causes `prepareEventNames()` to be skipped, leaving `symbolEventName` undefined and eventually leading to a runtime error when `window['undefined'] = []` is executed.
In Node.js environments running with `--disable-proto=throw`, the assignment:
```ts id="z8n4qm"
zoneSymbolEventNames['__proto__'] = {};
```
throws immediately because it triggers the disabled `__proto__` setter.
The `patches` registry has a similar issue. A `__proto__` key passed to `__load_patch()` bypasses the duplicate-patch check and reaches:
```ts id="f3v7kx"
patches['__proto__'] = fn(...);
```
which invokes the `__proto__` setter and changes the prototype of the `patches` object.
Using `Object.create(null)` removes the inherited `__proto__` accessor entirely, causing these keys to behave like ordinary properties rather than interacting with JavaScript's prototype machinery.
As part of this change, `patches.hasOwnProperty(name)` is also updated to:
```ts id="n2c8wp"
Object.prototype.hasOwnProperty.call(patches, name)
```
since null-prototype objects do not inherit `hasOwnProperty`.
Initialize zoneSymbolEventNames with Object.create(null) instead of {}.
This is hardening only. addEventListener('__proto__', fn) is not
directly attacker-controllable — its presence in an application is
itself an application bug and a prerequisite for any issue here.
Without this change, if that application bug exists, two unexpected
behaviors follow depending on environment:
Browser: zoneSymbolEventNames['__proto__'] reads the __proto__ getter
and returns Object.prototype (truthy), bypassing prepareEventNames.
symbolEventName resolves to undefined and window['undefined'] = []
throws TypeError.
Node.js + --disable-proto=throw: the assignment
zoneSymbolEventNames['__proto__'] = {} inside prepareEventNames
triggers the disabled __proto__ setter and throws.
Using Object.create(null) removes the __proto__ accessor from the
map so the key is treated as a plain missing property in both cases.
Previously, `__Zone_symbol_prefix` was read directly from `globalThis` without validating its type:
const symbolPrefix = global['__Zone_symbol_prefix'] || '__zone_symbol__';
This made it possible for DOM clobbering to interfere with Zone’s internal symbol handling. If an attacker injected a DOM element with the same name (for example via a form field or anchor ID), `global['__Zone_symbol_prefix']` could resolve to a DOM element instead of a string. Because DOM elements are truthy, the fallback would not be used, and Zone would construct invalid internal keys (e.g. “[object HTMLFormElement]...”), breaking patching and lookup logic in subtle ways.
This prevents DOM clobbering from influencing Zone’s internal symbol generation and keeps the patching system stable even in the presence of malicious or unexpected global values.
Ensure that when a custom object with a 'rejection' property is thrown as a raw promise rejection, the unhandled promise rejection error logger does not crash with a TypeError while trying to access undefined zone properties.
Also wrap microtask queue draining and task frame counter updates with defensive try-finally blocks to guarantee internal scheduler states are properly reset under any potential call stack exception unwinding scenarios.
Replace specific file patches applied during google3 sync with generic comment-based mechanisms.
By adding `// g3-only` prefix comments to g3-specific exports and declarations, and appending `// 3p-only` context to `@internal` tags, we enable generic tooling to handle these modifications during the sync process.
Additionally, wrap 3rd-party-only imports and exports (which should be stripped in google3) with `// 3p-only-start` and `// 3p-only-end` comment markers.
This reduces the need for maintaining custom file-specific patches in google3.
Also, add a comprehensive guide to these sync comment markers in `contributing-docs/google-markers.md` to assist external contributors.
Specifically:
- Add `// 3p-only` context to `@internal` in `directives.ts` for `foreignImports` and `deferredImports`.
- Add `// g3-only` commented exports in `core.ts`.
- Add `// g3-only-start`/`// g3-only-end` commented global declaration block in `zone.ts`.
- Wrap 3p-only imports in `fake_navigation.ts` with `// 3p-only-start` and `// 3p-only-end`.
- Wrap 3p-only exports in `compiler-cli/index.ts` with `// 3p-only-start` and `// 3p-only-end`.
- Add `// g3-only` and `// 3p-only` markers to `shared.ts` for `setDisabledStateDefault` configuration.
- Add `// g3-only` and `// 3p-only` markers to `feature_detection.ts` for semver dependency.
- Add `// g3-only` and `// 3p-only` markers to `domino_adapter.ts` for domino import path.
- Add `// 3p-only` marker to `ng_dev_mode` import in `event_dispatcher.ts`.
- Add `// g3-only` and `// 3p-only` markers to `MOUSE_SPECIAL_SUPPORT` in `event_contract_defines.ts`.
- Add `// g3-only` and `// 3p-only` markers to `BrowserModule` imports in `module.ts` (animations) and `browser.ts` (testing).
- Add `// 3p-only` marker to `goog.d.ts` reference tags in `util.ts` (platform-browser), `types.d.ts`, `ng_i18n_closure_mode.ts`, `tokens.ts`, and `global_utils.ts`.
- Wrap `Default` enum value of `ChangeDetectionStrategy` in `constants.ts` with `// 3p-only-start` and `// 3p-only-end`.
- Add `// g3-only` and `// 3p-only` markers to `LEGACY_OPTIONAL_CHAINING_DEFAULT` in `legacy_optional_chaining_default.ts` and `legacyOptionalChaining` in `directive.ts`.
- Add `// g3-only` and `// 3p-only` markers to `DEFAULT_PARAMS_INHERITANCE_STRATEGY` in `router_state.ts`.
- Add `// g3-only` and `// 3p-only` (and block variants) markers to `@mcp-b/webmcp-types` imports in `declare_tool.ts`, `provide_tools.ts`, and `types.ts`.
- Add `contributing-docs/google-markers.md` guide.
TAG=agy
CONV=cd09a4f3-869a-4f41-949b-c91f1b8f1c51
To support `fakeAsync` usage while using `vitest` as a test runner, Zone.js
now provides patching when using the `zone.js/testing` package import.
This patching is similar to that of the existing jasmine, mocha, and jest
functionality.
PR Close#68395
These changes are essentially the same as those introduced in
angular#45273, but they include backward compatibility
for applications that explicitly rely on the order in which microtasks are drained.
This is critically important for our code and other third-party code, which is
beyond our control, to work properly. If a microtask is scheduled within an event
listener to be executed "later", it should indeed be executed later and not synchronously,
as this would break the expected flow of code execution.
The simple code that reproduces the behavior that exists now:
```ts
Zone.current.fork({name: 'child'}).run(() => {
const div = document.createElement('div');
div.style.height = '200px';
div.style.width = '200px';
div.style.backgroundColor = 'red';
document.body.appendChild(div);
function listener() {
Promise.resolve().then(() => {
div.style.height = '400px';
});
}
div.addEventListener('fakeEvent', listener);
div.dispatchEvent(new Event('fakeEvent'));
console.log(div.getBoundingClientRect().height); // 400
});
```
The code above logs 400 as the height, but it should actually log 200 because the
height is updated in a microtask within the event listener.
When using Angular with microfrontend applications, especially when other apps might be
using React, zone.js can disrupt the classical order of operations. For example, when using a
`react-component/trigger`, it schedules a microtask within an event listener using
`Promise.resolve().then(...)` to determine whether the event needs to be re-dispatched.
The event is re-dispatched when the layout has changed, which is why a microtask is used.
With this change, we introduce a global configuration flag,
`__zone_symbol__enable_native_microtask_draining`, to allow consumers to enable
microtask draining within a browser microtask.
This flag is necessary to prevent any breaking changes resulting from this modification.
The previous attempt to address this issue caused a significant number of failures in g3.
Therefore, we are hiding that fix behind the configuration flag.
Closes angular#44446
Closes angular#55590
Closes angular#51328
When Zone patches Promise, it uses ZoneAwarePromise. The new Promise.try API was undefined on ZoneAwarePromise, making it unavailable when zone was present. This change gracefully passes through Promise.try to the native Promise implementation, if available, without patching it to execute in the right zone (our stance is not to add new patches but avoid destructively making new APIs unavailable).
Fixes#67057
The Zone.js build was depending on the `--outFile` flag from TypeScript which is deprecated. These changes switch to using `--outDir` and copying the files out of the directory instead.