Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
111a62d
fs: key glob matcher cache by platform
Archkon Jul 30, 2026
f9e2fe0
wasm: register missing SetURL function
Archkon Jul 30, 2026
f85fdab
ffi: fix optimized buffer conversions
trivikr Jul 30, 2026
c8e2a82
ffi: validate fast 32-bit integer argument ranges
trivikr Jul 30, 2026
a58aad7
sqlite: check database state before calling SQLite
trivikr Jul 31, 2026
bec3d0b
vfs: speed up recursive readdir test setup
trivikr Jul 31, 2026
598693b
doc: remove obsolete cctest node.gyp instructions
soulee-dev Jul 31, 2026
67af6d1
quic: fix segfault after fragmented client hello
pimterry Jul 31, 2026
756a023
test: update WPT for url to 4832db4761
nodejs-github-bot Jul 31, 2026
c543cfb
stream: use the ring buffer for pending BYOB pull-into descriptors
mcollina Jul 31, 2026
b7d29fe
test_runner: add support for --test-coverage-include-all
avivkeller Jul 29, 2026
a0d1911
deps: V8: backport 5177b10891e6
avivkeller Jul 27, 2026
8a1ca0f
test: remove test-repl-user-error-handler from flaky
avivkeller Jul 27, 2026
8dce37c
stream: use validateString for consumer encoding
sjungwon03 Aug 1, 2026
ab1e5fc
typings: add heap_utils internalBinding types
HoonDongKang Aug 1, 2026
a6a1bd4
quic: fix coverage comment typo
sjungwon03 Aug 1, 2026
a7d16a8
net: improve dtls cert verification
jasnell Aug 1, 2026
53ea5a1
doc: fix duplicated word in test snapshot docs
Rawal27 Aug 1, 2026
5ba72ae
zlib: add ZipEntry, ZipFile, and ZipBuffer
pipobscure Aug 1, 2026
b9dacd4
test: reuse ffi.suffix instead of reimplementing it
leah-1ee Aug 1, 2026
a46087d
test: fix lint in dtls tests
mcollina Aug 1, 2026
ce6702d
vfs: add ZipProvider
pipobscure Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions benchmark/webstreams/tee.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';
const common = require('../common.js');
const { ReadableStream } = require('node:stream/web');

const bench = common.createBenchmark(main, {
n: [1e5],
type: ['normal', 'bytes'],
});

async function main({ n, type }) {
let i = 0;
const source = type === 'bytes' ?
{
type: 'bytes',
pull(controller) {
if (i++ < n) controller.enqueue(new Uint8Array(16));
else controller.close();
},
} :
{
pull(controller) {
if (i++ < n) controller.enqueue('a');
else controller.close();
},
};

const rs = new ReadableStream(source);
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader();
const reader2 = branch2.getReader();
let reads = 0;

bench.start();
for (;;) {
const [result1, result2] = await Promise.all([
reader1.read(),
reader2.read(),
]);
if (result1.done || result2.done) break;
reads++;
}
bench.end(reads);
console.assert(reads === n);
}
2 changes: 1 addition & 1 deletion common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

# Reset this number to 0 on major V8 upgrades.
# Increment by one for each non-official patch applied to deps/v8.
'v8_embedder_string': '-node.27',
'v8_embedder_string': '-node.28',

##### V8 defaults for Node.js #####

Expand Down
1 change: 1 addition & 0 deletions deps/v8/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Artem Kobzar <artem.kobzar@jetbrains.com>
Arthur Islamov <arthur@islamov.ai>
Asuka Shikina <shikina.asuka@gmail.com>
Aurèle Barrière <aurele.barriere@gmail.com>
Aviv Keller <me@aviv.sh>
Bala Avulapati <bavulapati@gmail.com>
Bangfu Tao <bangfu.tao@samsung.com>
Ben Coe <bencoe@gmail.com>
Expand Down
43 changes: 37 additions & 6 deletions deps/v8/src/inspector/injected-script.cc
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,7 @@ class InjectedScript::ProtocolPromiseHandler {
PromiseHandlerTracker::DiscardReason::kFulfilled);
}

ProtocolPromiseHandler(PromiseHandlerTracker::Id id,
V8InspectorSessionImpl* session,
ProtocolPromiseHandler(V8InspectorSessionImpl* session,
int executionContextId, const String16& objectGroup,
std::unique_ptr<WrapOptions> wrapOptions,
bool replMode, bool throwOnSideEffect,
Expand All @@ -220,7 +219,13 @@ class InjectedScript::ProtocolPromiseHandler {
m_replMode(replMode),
m_throwOnSideEffect(throwOnSideEffect),
m_callback(std::move(callback)),
m_evaluationResult(m_inspector->isolate(), evaluationResult) {
m_evaluationResult(m_inspector->isolate(), evaluationResult) {}

void makeWeak(PromiseHandlerTracker::Id id) {
if (m_isActive || m_evaluationResult.IsEmpty() ||
m_evaluationResult.IsWeak()) {
return;
}
m_evaluationResult.SetWeak(reinterpret_cast<PromiseHandlerTracker::Id*>(id),
cleanup, v8::WeakCallbackType::kParameter);
}
Expand All @@ -238,6 +243,7 @@ class InjectedScript::ProtocolPromiseHandler {
}

void thenCallback(v8::Local<v8::Value> value) {
m_isActive = true;
// We don't need the m_evaluationResult in the `thenCallback`, but we also
// don't want `cleanup` running in case we re-enter JS.
m_evaluationResult.Reset();
Expand Down Expand Up @@ -285,9 +291,10 @@ class InjectedScript::ProtocolPromiseHandler {
}

void catchCallback(v8::Local<v8::Value> result) {
m_isActive = true;
// Hold strongly onto m_evaluationResult now to prevent `cleanup` from
// running in case any code below triggers GC.
m_evaluationResult.ClearWeak();
if (m_evaluationResult.IsWeak()) m_evaluationResult.ClearWeak();
V8InspectorSessionImpl* session =
m_inspector->sessionById(m_contextGroupId, m_sessionId);
if (!session) return;
Expand Down Expand Up @@ -393,6 +400,7 @@ class InjectedScript::ProtocolPromiseHandler {
std::unique_ptr<WrapOptions> m_wrapOptions;
bool m_replMode;
bool m_throwOnSideEffect;
bool m_isActive = false;
std::weak_ptr<EvaluateCallback> m_callback;
v8::Global<v8::Promise> m_evaluationResult;
};
Expand Down Expand Up @@ -1190,8 +1198,7 @@ template <typename... Args>
PromiseHandlerTracker::Id PromiseHandlerTracker::create(Args&&... args) {
Id id = m_lastUsedId++;
InjectedScript::ProtocolPromiseHandler* handler =
new InjectedScript::ProtocolPromiseHandler(id,
std::forward<Args>(args)...);
new InjectedScript::ProtocolPromiseHandler(std::forward<Args>(args)...);
m_promiseHandlers.emplace(id, handler);
return id;
}
Expand Down Expand Up @@ -1225,6 +1232,30 @@ InjectedScript::ProtocolPromiseHandler* PromiseHandlerTracker::get(
return iter->second.get();
}

void PromiseHandlerTracker::makeWeakForContext(int executionContextId) {
for (auto& [id, handler] : m_promiseHandlers) {
if (handler->m_executionContextId == executionContextId) {
handler->makeWeak(id);
}
}
}

void PromiseHandlerTracker::makeWeakForObjectGroup(
int sessionId, const String16& objectGroup) {
for (auto& [id, handler] : m_promiseHandlers) {
if (handler->m_sessionId == sessionId &&
handler->m_objectGroup == objectGroup) {
handler->makeWeak(id);
}
}
}

void PromiseHandlerTracker::makeWeakForSession(int sessionId) {
for (auto& [id, handler] : m_promiseHandlers) {
if (handler->m_sessionId == sessionId) handler->makeWeak(id);
}
}

void PromiseHandlerTracker::sendFailure(
InjectedScript::ProtocolPromiseHandler* handler,
const protocol::DispatchResponse& response) const {
Expand Down
3 changes: 3 additions & 0 deletions deps/v8/src/inspector/injected-script.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ class PromiseHandlerTracker {
Id create(Args&&... args);
void discard(Id id, DiscardReason reason);
InjectedScript::ProtocolPromiseHandler* get(Id id) const;
void makeWeakForContext(int executionContextId);
void makeWeakForObjectGroup(int sessionId, const String16& objectGroup);
void makeWeakForSession(int sessionId);

private:
void sendFailure(InjectedScript::ProtocolPromiseHandler* handler,
Expand Down
1 change: 1 addition & 0 deletions deps/v8/src/inspector/v8-inspector-impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ void V8InspectorImpl::contextCollected(int groupId, int contextId) {
session->runtimeAgent()->reportExecutionContextDestroyed(inspectedContext);
});
discardInspectedContext(groupId, contextId);
m_promiseHandlerTracker.makeWeakForContext(contextId);
}

void V8InspectorImpl::resetContextGroup(int contextGroupId) {
Expand Down
5 changes: 5 additions & 0 deletions deps/v8/src/inspector/v8-inspector-session-impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ void V8InspectorSessionImpl::discardInjectedScripts() {
[&sessionId](InspectedContext* context) {
context->discardInjectedScript(sessionId);
});
m_inspector->promiseHandlerTracker().makeWeakForSession(sessionId);
}

Response V8InspectorSessionImpl::findInjectedScript(
Expand Down Expand Up @@ -260,6 +261,10 @@ void V8InspectorSessionImpl::releaseObjectGroup(const String16& objectGroup) {
InjectedScript* injectedScript = context->getInjectedScript(sessionId);
if (injectedScript) injectedScript->releaseObjectGroup(objectGroup);
});
if (!objectGroup.isEmpty()) {
m_inspector->promiseHandlerTracker().makeWeakForObjectGroup(m_sessionId,
objectGroup);
}
}

bool V8InspectorSessionImpl::unwrapObject(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Tests the lifetime of pending Runtime.evaluate requests.

Running test: testPromiseIsKeptAlive
Using replMode:
{
id : <messageId>
result : {
result : {
description : 42
type : number
value : 42
}
}
}
Using awaitPromise:
{
id : <messageId>
result : {
result : {
description : 42
type : number
value : 42
}
}
}

Running test: testObjectGroupReleaseMakesPromiseCollectible
Using replMode:
{
error : {
code : -32000
message : Promise was collected
}
id : <messageId>
}
Using awaitPromise:
{
error : {
code : -32000
message : Promise was collected
}
id : <messageId>
}

Running test: testContextDestructionDiscardsPromise
Using replMode:
{
error : {
code : -32000
message : Execution context was destroyed.
}
id : <messageId>
}
Using awaitPromise:
{
error : {
code : -32000
message : Execution context was destroyed.
}
id : <messageId>
}

Running test: testSessionDestructionMakesPromiseCollectible
Promise is alive before disconnect: true
Promise is alive after disconnect: false
105 changes: 105 additions & 0 deletions deps/v8/test/inspector/runtime/evaluate-promise-lifetime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright 2026 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --no-stress-incremental-marking

let {Protocol} = InspectorTest.start(
'Tests the lifetime of pending Runtime.evaluate requests.');

const evaluationModes = [
{
name: 'replMode',
arguments: {replMode: true},
expression:
'await new Promise(resolve => globalThis.resolve = resolve); 42',
resolveExpression: 'resolve()',
pendingExpression: 'await new Promise(() => {})',
},
{
name: 'awaitPromise',
arguments: {awaitPromise: true},
expression: `(() => {
let resolve;
const promise = new Promise(r => resolve = r);
promise.resolve = resolve;
globalThis.weak = new WeakRef(promise);
return promise;
})()`,
resolveExpression: 'weak.deref().resolve(42)',
pendingExpression: 'new Promise(() => {})',
},
];

function evaluate(Protocol, mode, expression, extraArguments = {}) {
return Protocol.Runtime.evaluate(
{...mode.arguments, ...extraArguments, expression});
}

InspectorTest.runAsyncTestSuite([
async function testPromiseIsKeptAlive() {
for (const mode of evaluationModes) {
InspectorTest.log(`Using ${mode.name}:`);
const evaluation = evaluate(Protocol, mode, mode.expression);

await Protocol.HeapProfiler.collectGarbage();
await Protocol.Runtime.evaluate({expression: mode.resolveExpression});

InspectorTest.logMessage(await evaluation);
}
},

async function testObjectGroupReleaseMakesPromiseCollectible() {
for (const mode of evaluationModes) {
InspectorTest.log(`Using ${mode.name}:`);
const evaluation = evaluate(
Protocol, mode, mode.pendingExpression,
{objectGroup: 'evaluation'});

await Protocol.Runtime.releaseObjectGroup({objectGroup: 'evaluation'});
await Protocol.HeapProfiler.collectGarbage();

InspectorTest.logMessage(await evaluation);
}
},

async function testContextDestructionDiscardsPromise() {
for (const mode of evaluationModes) {
InspectorTest.log(`Using ${mode.name}:`);
const contextGroup = new InspectorTest.ContextGroup();
const session = contextGroup.connect();
const evaluation = evaluate(
session.Protocol, mode, mode.pendingExpression);

await session.Protocol.Runtime.evaluate(
{expression: 'inspector.fireContextDestroyed()'});

InspectorTest.logMessage(await evaluation);
session.disconnect();
}
},

async function testSessionDestructionMakesPromiseCollectible() {
const contextGroup = new InspectorTest.ContextGroup();
const session1 = contextGroup.connect();
const session2 = contextGroup.connect();
session1.Protocol.Runtime.evaluate({
expression: evaluationModes[1].expression,
awaitPromise: true,
});

await session2.Protocol.HeapProfiler.collectGarbage();
let result = await session2.Protocol.Runtime.evaluate(
{expression: 'weak.deref() !== undefined'});
InspectorTest.log(
`Promise is alive before disconnect: ${result.result.result.value}`);

session1.disconnect();
await session2.Protocol.HeapProfiler.collectGarbage();
result = await session2.Protocol.Runtime.evaluate(
{expression: 'weak.deref() !== undefined'});
InspectorTest.log(
`Promise is alive after disconnect: ${result.result.result.value}`);
session2.disconnect();
},
]);
16 changes: 16 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2877,6 +2877,21 @@ This option may be specified multiple times to include multiple glob patterns.
If both `--test-coverage-exclude` and `--test-coverage-include` are provided,
files must meet **both** criteria to be included in the coverage report.

### `--test-coverage-include-all`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental

Includes source files that were never loaded by the test run in the coverage
report, where they are reported as having zero coverage.

Candidate files are searched for in the current working directory, and are
subject to the same `--test-coverage-include` and `--test-coverage-exclude`
filtering as the rest of the report.

### `--test-coverage-lines=threshold`

<!-- YAML
Expand Down Expand Up @@ -3920,6 +3935,7 @@ one is included in the list below.
* `--test-coverage-branches`
* `--test-coverage-exclude`
* `--test-coverage-functions`
* `--test-coverage-include-all`
* `--test-coverage-include`
* `--test-coverage-lines`
* `--test-global-setup`
Expand Down
Loading