Compare commits

..

18 Commits

Author SHA1 Message Date
trop[bot]
9453e8bfe1 build: roll build-image to a82b87d (#49450)
build: roll build-image to a82b87d

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-19 19:01:42 +01:00
trop[bot]
a4eb213a04 fix: restore AXDocument accessibility attribute for representedFilename on macOS (#49418)
Starting from Chromium 134.0.6989.0 (Electron 35.0.0-beta.5), the
NativeWidgetMacNSWindow class overrides accessibilityDocument to return
the web content URL from the accessibility tree, but doesn't fall back
to NSWindow's default behavior when that URL is empty.

This broke Electron's setRepresentedFilename() API - the file path was
still set on the NSWindow, but no longer exposed via the AXDocument
accessibility attribute that screen readers use.

This fix adds an accessibilityDocument override in ElectronNSWindow that
checks representedFilename first, falling back to Chromium's behavior
for web content URLs.

Fixes: https://github.com/electron/electron/issues/XXXXX

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Daniel Gräfe <Daniel.Alm@ForumD.net>
2026-01-19 15:02:51 +01:00
trop[bot]
81c08e80f6 docs: fix webContents.hostWebContents types (#49445)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2026-01-19 10:58:28 +01:00
trop[bot]
5f630c7de7 fix: try clearing InspectableWebContents delegate earlier (#49423)
fix: try clearing InspectableWebContents delegate earlier

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-18 09:52:20 +01:00
trop[bot]
2dc82ea1f3 fix: make toplevel icon Wayland protocol work (#49415)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Alex Schwartz <alexschwartz01@gmail.com>
2026-01-16 11:04:40 -05:00
Shelley Vohr
f57d6f92b6 feat: support WebSocket authentication handling (#49065)
* feat: support WebSocket authentication handling

* chore: make linter happy

---------

Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-01-13 08:52:14 -05:00
Robo
744142fe54 fix: reduce stack memory consumption in BytecodeGenerator (#49360)
Reduce stack memory consumption in BytecodeGenerator

Backports

1) https://chromium-review.googlesource.com/c/v8/v8/+/7180480
2) https://chromium-review.googlesource.com/c/v8/v8/+/7160576
3) https://chromium-review.googlesource.com/c/v8/v8/+/7062734

2 and 3 are needed to cleanly land 1. However, most of the code
changes are noop since v8_flags.proto_assign_seq_opt is experimental
and disabled by default for feature. The reason why stack memory
consumption is improved for all scenarios can be found in
https://github.com/microsoft/vscode/issues/283403#issuecomment-3737968271
2026-01-13 19:04:51 +09:00
trop[bot]
b200b8d6c0 build: roll build-tools SHA to 4430e4a (#49367)
build: roll build-tools SHA to 4430e4a

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-01-12 15:44:02 -05:00
electron-roller[bot]
cdaf0e96b6 chore: bump chromium to 142.0.7444.265 (39-x-y) (#49322)
chore: bump chromium in DEPS to 142.0.7444.265

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
2026-01-12 11:29:53 +01:00
trop[bot]
981df181c1 chore: improvements to script/run-clang-tidy.ts (#49341)
* chore: disable color output for clang-tidy in CI

Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

* chore: small QoL improvements to run-clang-tidy.ts

Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

* chore: add --fix option to script/run-clang-tidy.ts

Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2026-01-09 14:43:00 -06:00
John Kleinschmidt
218300e57f build: use @electron-ci/dev-root for package.json default (#49319)
* build: use @electron-ci/dev-root for package.json default (#49154)

(cherry picked from commit bab6bd3dae)

* fxiup
2026-01-07 09:48:08 -05:00
Charles Kerr
6ccee512e4 chore: remove patches/v8/cherry-pick-e0052e7af9c9 (#49309)
chore: remove patches/v8/cherry-pick-e0052e7af9c9.patch

included in latest roll of upstream w/v8 14.2.231.22
2026-01-06 12:43:37 -06:00
trop[bot]
b6e4f514d8 docs: update roundedCorners documentation (#49310)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Niklas Wenzel <dev@nikwen.de>
2026-01-06 11:00:23 -05:00
trop[bot]
ade4c00984 fix: webRequest.onBeforeSendHeaders not being able to modify reserved headers (#49242)
* fix: `webRequest.onBeforeSendHeaders` not being able to modify reserved headers

Co-authored-by: Samuel Attard <sattard@anthropic.com>

* chore: add unit test for reserved header

Co-authored-by: Samuel Attard <sattard@anthropic.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
2026-01-05 16:30:50 -05:00
trop[bot]
d8687cfc9d build: fixup release notes generation (#49304)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-01-05 15:56:30 -05:00
electron-roller[bot]
2ab4489447 chore: bump chromium to 142.0.7444.243 (39-x-y) (#49228)
chore: bump chromium in DEPS to 142.0.7444.243

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
2026-01-02 13:49:22 +09:00
Keeley Hammond
ab9b156113 chore: cherry-pick e0052e7af9c9 from v8 (#49287)
* chore: cherry-pick e0052e7af9c9 from v8

* chore: update patches
2025-12-31 17:02:33 +13:00
trop[bot]
35a531953b build: drop dugite as a dependency (#49205)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2025-12-15 16:39:41 -05:00
40 changed files with 1052 additions and 185 deletions

View File

@@ -2,7 +2,7 @@ version: '3'
services:
buildtools:
image: ghcr.io/electron/devcontainer:933c7d6ff6802706875270bec2e3c891cf8add3f
image: ghcr.io/electron/devcontainer:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
volumes:
- ..:/workspaces/gclient/src/electron:cached

View File

@@ -15,7 +15,7 @@ runs:
git config --global core.preloadindex true
git config --global core.longpaths true
fi
export BUILD_TOOLS_SHA=a5d9f9052dcc36ee88bef5c8b13acbefd87b7d8d
export BUILD_TOOLS_SHA=4430e4a505e0f4fa2a41b707a10a36f780bbdd26
npm i -g @electron/build-tools
# Update depot_tools to ensure python
e d update_depot_tools

View File

@@ -14,7 +14,7 @@ jobs:
permissions:
contents: read
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
options: --user root
volumes:
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
@@ -37,7 +37,7 @@ jobs:
permissions:
contents: read
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
options: --user root --device /dev/fuse --cap-add SYS_ADMIN
volumes:
- /mnt/win-cache:/mnt/win-cache
@@ -63,7 +63,7 @@ jobs:
# This job updates the same git cache as linux, so it needs to run after the linux one.
needs: build-git-cache-linux
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
options: --user root
volumes:
- /mnt/cross-instance-cache:/mnt/cross-instance-cache

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
required: true
skip-macos:
type: boolean
@@ -76,7 +76,7 @@ jobs:
id: set-output
run: |
if [ -z "${{ inputs.build-image-sha }}" ]; then
echo "build-image-sha=933c7d6ff6802706875270bec2e3c891cf8add3f" >> "$GITHUB_OUTPUT"
echo "build-image-sha=a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb" >> "$GITHUB_OUTPUT"
else
echo "build-image-sha=${{ inputs.build-image-sha }}" >> "$GITHUB_OUTPUT"
fi

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
upload-to-storage:
description: 'Uploads to Azure storage'
required: false

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
required: true
upload-to-storage:
description: 'Uploads to Azure storage'

View File

@@ -6,7 +6,7 @@ on:
build-image-sha:
type: string
description: 'SHA for electron/build image'
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
required: true
upload-to-storage:
description: 'Uploads to Azure storage'

2
DEPS
View File

@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
vars = {
'chromium_version':
'142.0.7444.235',
'142.0.7444.265',
'node_version':
'v22.21.1',
'nan_version':

View File

@@ -102,9 +102,9 @@
* `trafficLightPosition` [Point](point.md) (optional) _macOS_ -
Set a custom position for the traffic light buttons in frameless windows.
* `roundedCorners` boolean (optional) _macOS_ _Windows_ - Whether frameless window
should have rounded corners. Default is `true`. Setting this property
to `false` will prevent the window from being fullscreenable on macOS.
On Windows versions older than Windows 11 Build 22000 this property has no effect, and frameless windows will not have rounded corners.
should have rounded corners. Default is `true`. On Windows versions older than
Windows 11 Build 22000 this property has no effect, and frameless windows will
not have rounded corners.
* `thickFrame` boolean (optional) _Windows_ - Use `WS_THICKFRAME` style for
frameless windows on Windows, which adds the standard window frame. Setting it
to `false` will remove window shadow and window animations, and disable window

View File

@@ -2410,7 +2410,8 @@ A [`NavigationHistory`](navigation-history.md) used by this webContents.
#### `contents.hostWebContents` _Readonly_
A [`WebContents`](web-contents.md) instance that might own this `WebContents`.
A `WebContents | null` property that represents a [`WebContents`](web-contents.md)
instance that might own this `WebContents`.
#### `contents.devToolsWebContents` _Readonly_

View File

@@ -1,5 +1,5 @@
{
"name": "electron",
"name": "@electron-ci/dev-root",
"version": "0.0.0-development",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
@@ -25,7 +25,6 @@
"buffer": "^6.0.3",
"chalk": "^4.1.0",
"check-for-leaks": "^1.2.1",
"dugite": "^2.7.1",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.32.0",
@@ -148,9 +147,6 @@
"dependenciesMeta": {
"abstract-socket": {
"built": true
},
"dugite": {
"built": true
}
}
}

View File

@@ -1,2 +1,5 @@
chore_allow_customizing_microtask_policy_per_context.patch
turboshaft_avoid_introducing_too_many_variables.patch
runtime_setprototypeproperties_handling_of.patch
runtime_correcting_setprototypeproperties.patch
reduce_stack_memory_consumption_in_bytecodegenerator.patch

View File

@@ -0,0 +1,263 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jakob Kummerow <jkummerow@chromium.org>
Date: Fri, 21 Nov 2025 11:52:15 +0100
Subject: Reduce stack memory consumption in BytecodeGenerator
Using a SmallVector with 64 stack-allocated entries is not great
for functions that can be used in deep recursions, so this patch
replaces that with a ZoneVector.
By allocating any backing store only when it's actually needed,
and presizing that initial backing store when that happens, this
should have similar performance while using a lot less stack space,
allowing us to compile more deeply nested expressions.
For the highly artificial example of a function full of nested
empty blocks `function f() {{{{{...}}}}}`, this increases the max
nesting level from around 670 to around 2600. With more aggressive
inlining in official builds, it can have similar effects on patterns
that don't even call `VisitStatements()` recursively.
Bug: 429332174
Change-Id: Id11e15ba0fd9bc39efa68bf83e1181fe0e7274a7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7180480
Reviewed-by: Raphael Herouart <rherouart@chromium.org>
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#103870}
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index 63400abcc7ca0bd5369d566dd37e2ae93d7c3efe..a1cf142edc4c0224731ebab988bcbf9272acd57e 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -2306,10 +2306,7 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
// The subsequent ones must be about the same <var> to return true.
bool BytecodeGenerator::IsPrototypeAssignment(
- Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
- std::unordered_set<const AstRawString*>& duplicates) {
+ Statement* stmt, std::unique_ptr<PrototypeAssignments>* assignments) {
// The expression Statement is an assignment
// ========================================
ExpressionStatement* expr_stmt = stmt->AsExpressionStatement();
@@ -2381,41 +2378,46 @@ bool BytecodeGenerator::IsPrototypeAssignment(
return false;
}
- if (!duplicates.insert(prop_str).second) {
- return false;
- }
-
- if (*var == nullptr) {
- // This is the first proto assignment in the sequence
- *var = tmp_var;
- *hole_check_mode = proto_prop->obj()->AsVariableProxy()->hole_check_mode();
- } else if (*var != tmp_var) {
- // This prototype assignment is about another var
- return false;
+ if (!*assignments) {
+ // This is the first prototype assignment in the sequence.
+ *assignments = std::make_unique<PrototypeAssignments>(
+ tmp_var, proto_prop->obj()->AsVariableProxy()->hole_check_mode(),
+ ZoneVector<PrototypeAssignment>(zone()));
+ (*assignments)->properties.reserve(8);
+ (*assignments)->duplicates.insert(prop_str);
+ } else {
+ if (!(*assignments)->duplicates.insert(prop_str).second) return false;
+ if ((*assignments)->var != tmp_var) {
+ // This prototype assignment is about another var.
+ return false;
+ }
+ DCHECK_EQ((*assignments)->hole_check_mode,
+ proto_prop->obj()->AsVariableProxy()->hole_check_mode());
}
- // Success
- properties.push_back(std::make_pair(
- prop_str,
- value)); // This will be reused as part of an ObjectLiteral
+ // Success!
+ (*assignments)
+ ->properties.push_back(std::make_pair(
+ prop_str,
+ value)); // This will be reused as part of an ObjectLiteral.
- DCHECK_EQ(*hole_check_mode,
- proto_prop->obj()->AsVariableProxy()->hole_check_mode());
return true;
}
void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
- const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
- Variable* var, HoleCheckMode hole_check_mode) {
+ std::unique_ptr<PrototypeAssignments> assignments) {
// Create a boiler plate object in the constant pool to be merged into the
- // proto
+ // proto.
size_t entry = builder()->AllocateDeferredConstantPoolEntry();
- proto_assign_seq_.push_back(std::make_pair(
- zone()->New<ProtoAssignmentSeqBuilder>(properties), entry));
+ proto_assign_seq_.push_back(
+ std::make_pair(zone()->New<ProtoAssignmentSeqBuilder>(
+ std::move(assignments->properties)),
+ entry));
+ const ZoneVector<PrototypeAssignment>* props =
+ proto_assign_seq_.back().first->properties();
int first_idx = -1;
- for (auto& p : properties) {
+ for (auto& p : *props) {
auto func = p.second->AsFunctionLiteral();
if (func) {
int idx = GetNewClosureSlot(func);
@@ -2426,13 +2428,13 @@ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
}
}
- // We need it to be valid, even if unused
+ // We need {first_idx} to be valid, even if it's unused.
if (first_idx == -1) {
first_idx = 0;
}
- // Load the variable whose prototype is to be set into the Accumulator
- BuildVariableLoad(var, hole_check_mode);
- // Merge in-place proto-def boilerplate object into Accumulator
+ // Load the variable whose prototype is to be set into the Accumulator.
+ BuildVariableLoad(assignments->var, assignments->hole_check_mode);
+ // Merge in-place proto-def boilerplate object into the Accumulator.
builder()->SetPrototypeProperties(entry, first_idx);
}
@@ -2440,25 +2442,23 @@ void BytecodeGenerator::VisitStatements(
const ZonePtrList<Statement>* statements, int start) {
for (int stmt_idx = start; stmt_idx < statements->length(); stmt_idx++) {
if (v8_flags.proto_assign_seq_opt) {
- Variable* var = nullptr;
- HoleCheckMode hole_check_mode;
-
int proto_assign_idx = stmt_idx;
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>
- properties(zone());
- std::unordered_set<const AstRawString*> duplicates;
+ // {VisitStatements} can be used for deep recursions, so this is a
+ // stack-friendly design: statically we only need one {unique_ptr}, and
+ // the actual storage is heap-allocated when it is needed.
+ std::unique_ptr<PrototypeAssignments> assignments;
while (proto_assign_idx < statements->length() &&
- IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
- &hole_check_mode, properties, duplicates)) {
+ IsPrototypeAssignment(statements->at(proto_assign_idx),
+ &assignments)) {
++proto_assign_idx;
}
if (proto_assign_idx - stmt_idx > 1) {
- DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
- VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
- stmt_idx = proto_assign_idx - 1; // the outer loop should now ignore
- // these statements
+ DCHECK_EQ(static_cast<size_t>(proto_assign_idx - stmt_idx),
+ assignments->properties.size());
+ VisitConsecutivePrototypeAssignments(std::move(assignments));
+ stmt_idx = proto_assign_idx - 1; // The outer loop should now ignore
+ // these statements.
DCHECK(!builder()->RemainderOfBlockIsDead());
continue;
}
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
index 5dd136a01848aaf4a182dd31aa05a6ebe845fae8..b2e4cf6d40c3cd66042f5f52533b4883ff159418 100644
--- a/src/interpreter/bytecode-generator.h
+++ b/src/interpreter/bytecode-generator.h
@@ -30,6 +30,13 @@ class LoopBuilder;
class BlockCoverageBuilder;
class BytecodeJumpTable;
+struct PrototypeAssignments {
+ Variable* var;
+ HoleCheckMode hole_check_mode;
+ ZoneVector<PrototypeAssignment> properties;
+ std::unordered_set<const AstRawString*> duplicates;
+};
+
class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
public:
enum TypeHint : uint8_t {
@@ -69,18 +76,12 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
#define DECLARE_VISIT(type) void Visit##type(type* node);
AST_NODE_LIST(DECLARE_VISIT)
#undef DECLARE_VISIT
- static constexpr int kInitialPropertyCount = 64;
bool IsPrototypeAssignment(
- Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
- std::unordered_set<const AstRawString*>& duplicate);
-
- void VisitConsecutivePrototypeAssignments(
- const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
- Variable* var, HoleCheckMode hole_check_mode);
+ Statement* stmt, std::unique_ptr<PrototypeAssignments>* assignments);
+ V8_NOINLINE void VisitConsecutivePrototypeAssignments(
+ std::unique_ptr<PrototypeAssignments> assignments);
+
// Visiting function for declarations list and statements are overridden.
void VisitModuleDeclarations(Declaration::List* declarations);
void VisitGlobalDeclarations(Declaration::List* declarations);
diff --git a/src/interpreter/prototype-assignment-sequence-builder.cc b/src/interpreter/prototype-assignment-sequence-builder.cc
index a7c05b6e8555fde41ba6a81523437fd03b16c3e6..fb427c825c2753d3e12f557279a5230985a1ec44 100644
--- a/src/interpreter/prototype-assignment-sequence-builder.cc
+++ b/src/interpreter/prototype-assignment-sequence-builder.cc
@@ -25,7 +25,7 @@ void ProtoAssignmentSeqBuilder::BuildBoilerplateDescription(
int position = 0;
for (size_t i = 0; i < properties_.size(); i++) {
- auto pair = properties_.at(i);
+ PrototypeAssignment pair = properties_.at(i);
const AstRawString* key_str = pair.first;
DirectHandle<Object> key = Cast<Object>(key_str->string());
diff --git a/src/interpreter/prototype-assignment-sequence-builder.h b/src/interpreter/prototype-assignment-sequence-builder.h
index 0c29f4f4bf0843877b2bd568866d8e290d4f3b51..6583022aa43a30df707325e6b7a14891f97a4ef1 100644
--- a/src/interpreter/prototype-assignment-sequence-builder.h
+++ b/src/interpreter/prototype-assignment-sequence-builder.h
@@ -7,17 +7,17 @@
#include "src/ast/ast.h"
#include "src/objects/literal-objects.h"
+#include "src/zone/zone-containers.h"
namespace v8 {
namespace internal {
+using PrototypeAssignment = std::pair<const AstRawString*, Expression*>;
+
class ProtoAssignmentSeqBuilder final : public ZoneObject {
public:
- static constexpr int kInitialPropertyCount = 64;
-
explicit ProtoAssignmentSeqBuilder(
- const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties)
+ ZoneVector<PrototypeAssignment>&& properties)
: properties_(std::move(properties)) {}
~ProtoAssignmentSeqBuilder() = default;
@@ -53,10 +53,10 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
IsolateT* isolate,
Handle<Script> script);
+ const ZoneVector<PrototypeAssignment>* properties() { return &properties_; }
+
private:
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>
- properties_;
+ ZoneVector<PrototypeAssignment> properties_;
IndirectHandle<ObjectBoilerplateDescription> boilerplate_description_;
};

View File

@@ -0,0 +1,148 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Raphael Herouart <rherouart@chromium.org>
Date: Tue, 18 Nov 2025 11:00:26 +0000
Subject: [runtime] Correcting SetPrototypeProperties:
- Off by One Error in code generation
- Attributes should be preserved when setting properties
- SmallVector Memory Leak
Bug: 429332174
Change-Id: I8a669a98f44777ad54ea7e7bb06fc4f8c552c865
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7160576
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Raphael Herouart <rherouart@chromium.org>
Cr-Commit-Position: refs/heads/main@{#103784}
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index bf874d94ec34056d499fea9cbd034f959d506b58..63400abcc7ca0bd5369d566dd37e2ae93d7c3efe 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -2307,8 +2307,8 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
bool BytecodeGenerator::IsPrototypeAssignment(
Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties,
std::unordered_set<const AstRawString*>& duplicates) {
// The expression Statement is an assignment
// ========================================
@@ -2405,8 +2405,8 @@ bool BytecodeGenerator::IsPrototypeAssignment(
}
void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
- const base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
+ const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties,
Variable* var, HoleCheckMode hole_check_mode) {
// Create a boiler plate object in the constant pool to be merged into the
// proto
@@ -2444,9 +2444,9 @@ void BytecodeGenerator::VisitStatements(
HoleCheckMode hole_check_mode;
int proto_assign_idx = stmt_idx;
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>
- properties;
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>
+ properties(zone());
std::unordered_set<const AstRawString*> duplicates;
while (proto_assign_idx < statements->length() &&
IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
@@ -2457,10 +2457,10 @@ void BytecodeGenerator::VisitStatements(
if (proto_assign_idx - stmt_idx > 1) {
DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
- stmt_idx = proto_assign_idx; // the outer loop should now ignore these
- // statements
+ stmt_idx = proto_assign_idx - 1; // the outer loop should now ignore
+ // these statements
DCHECK(!builder()->RemainderOfBlockIsDead());
- if (stmt_idx == statements->length()) break;
+ continue;
}
}
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
index 41c5cda1ca6dd4017b329e41bad8af3bcc4ee242..5dd136a01848aaf4a182dd31aa05a6ebe845fae8 100644
--- a/src/interpreter/bytecode-generator.h
+++ b/src/interpreter/bytecode-generator.h
@@ -73,13 +73,13 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
bool IsPrototypeAssignment(
Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties,
std::unordered_set<const AstRawString*>& duplicate);
void VisitConsecutivePrototypeAssignments(
- const base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties,
+ const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties,
Variable* var, HoleCheckMode hole_check_mode);
// Visiting function for declarations list and statements are overridden.
void VisitModuleDeclarations(Declaration::List* declarations);
diff --git a/src/interpreter/prototype-assignment-sequence-builder.h b/src/interpreter/prototype-assignment-sequence-builder.h
index 880ed4dcec8399e9b52e6d1924b8773bff1db063..0c29f4f4bf0843877b2bd568866d8e290d4f3b51 100644
--- a/src/interpreter/prototype-assignment-sequence-builder.h
+++ b/src/interpreter/prototype-assignment-sequence-builder.h
@@ -16,8 +16,8 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
static constexpr int kInitialPropertyCount = 64;
explicit ProtoAssignmentSeqBuilder(
- const base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>& properties)
+ const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>& properties)
: properties_(std::move(properties)) {}
~ProtoAssignmentSeqBuilder() = default;
@@ -54,8 +54,8 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
Handle<Script> script);
private:
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
- kInitialPropertyCount>
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
+ kInitialPropertyCount>
properties_;
IndirectHandle<ObjectBoilerplateDescription> boilerplate_description_;
};
diff --git a/src/runtime/runtime-literals.cc b/src/runtime/runtime-literals.cc
index ded3b466625c99da6f27936ae4e5e0c879baec1b..961b9ee5b7d329bb018300fe2f18bea60e1cd883 100644
--- a/src/runtime/runtime-literals.cc
+++ b/src/runtime/runtime-literals.cc
@@ -730,6 +730,13 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
feedback_cell_array, current_slot));
}
+ if (IsSpecialReceiverMap(js_proto->map())) {
+ RETURN_RESULT_OR_FAILURE(
+ isolate, SetPrototypePropertiesSlow(isolate, context, obj,
+ object_boilerplate_description,
+ feedback_cell_array, current_slot));
+ }
+
bool is_default_func_prototype =
IsDefaultFunctionPrototype(js_proto, isolate);
@@ -782,7 +789,11 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
value = InstantiateIfSharedFunctionInfo(
context, isolate, value, feedback_cell_array, current_slot);
DirectHandle<String> name = Cast<String>(key);
- JSObject::SetOwnPropertyIgnoreAttributes(js_proto, name, value, NONE)
+ Maybe<PropertyAttributes> maybe = JSReceiver::GetPropertyAttributes(&it);
+ PropertyAttributes attr = maybe.ToChecked();
+ if (attr == ABSENT) attr = NONE;
+
+ JSObject::SetOwnPropertyIgnoreAttributes(js_proto, name, value, attr)
.Check();
result = value;

View File

@@ -0,0 +1,153 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Raphael Herouart <rherouart@chromium.org>
Date: Tue, 21 Oct 2025 14:49:06 +0000
Subject: [runtime] SetPrototypeProperties handling of
Hole/Undefined/Non-Object Values
Bug: 451750214
Change-Id: Ida29afe0b92ed4a6acff97214005ed3589093294
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7062734
Commit-Queue: Raphael Herouart <rherouart@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#103261}
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index 6d444622bacbb71c4754bc055da5cae39ed213b7..bf874d94ec34056d499fea9cbd034f959d506b58 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -2306,7 +2306,7 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
// The subsequent ones must be about the same <var> to return true.
bool BytecodeGenerator::IsPrototypeAssignment(
- Statement* stmt, Variable** var,
+ Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>& properties,
std::unordered_set<const AstRawString*>& duplicates) {
@@ -2388,6 +2388,7 @@ bool BytecodeGenerator::IsPrototypeAssignment(
if (*var == nullptr) {
// This is the first proto assignment in the sequence
*var = tmp_var;
+ *hole_check_mode = proto_prop->obj()->AsVariableProxy()->hole_check_mode();
} else if (*var != tmp_var) {
// This prototype assignment is about another var
return false;
@@ -2398,13 +2399,15 @@ bool BytecodeGenerator::IsPrototypeAssignment(
prop_str,
value)); // This will be reused as part of an ObjectLiteral
+ DCHECK_EQ(*hole_check_mode,
+ proto_prop->obj()->AsVariableProxy()->hole_check_mode());
return true;
}
void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
const base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>& properties,
- Variable* var) {
+ Variable* var, HoleCheckMode hole_check_mode) {
// Create a boiler plate object in the constant pool to be merged into the
// proto
size_t entry = builder()->AllocateDeferredConstantPoolEntry();
@@ -2428,7 +2431,7 @@ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
first_idx = 0;
}
// Load the variable whose prototype is to be set into the Accumulator
- BuildVariableLoad(var, HoleCheckMode::kElided);
+ BuildVariableLoad(var, hole_check_mode);
// Merge in-place proto-def boilerplate object into Accumulator
builder()->SetPrototypeProperties(entry, first_idx);
}
@@ -2438,6 +2441,8 @@ void BytecodeGenerator::VisitStatements(
for (int stmt_idx = start; stmt_idx < statements->length(); stmt_idx++) {
if (v8_flags.proto_assign_seq_opt) {
Variable* var = nullptr;
+ HoleCheckMode hole_check_mode;
+
int proto_assign_idx = stmt_idx;
base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>
@@ -2445,13 +2450,13 @@ void BytecodeGenerator::VisitStatements(
std::unordered_set<const AstRawString*> duplicates;
while (proto_assign_idx < statements->length() &&
IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
- properties, duplicates)) {
+ &hole_check_mode, properties, duplicates)) {
++proto_assign_idx;
}
if (proto_assign_idx - stmt_idx > 1) {
DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
- VisitConsecutivePrototypeAssignments(properties, var);
+ VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
stmt_idx = proto_assign_idx; // the outer loop should now ignore these
// statements
DCHECK(!builder()->RemainderOfBlockIsDead());
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
index b6421819c353febe283069852645a6b85e4c855a..41c5cda1ca6dd4017b329e41bad8af3bcc4ee242 100644
--- a/src/interpreter/bytecode-generator.h
+++ b/src/interpreter/bytecode-generator.h
@@ -72,7 +72,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
static constexpr int kInitialPropertyCount = 64;
bool IsPrototypeAssignment(
- Statement* stmt, Variable** var,
+ Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>& properties,
std::unordered_set<const AstRawString*>& duplicate);
@@ -80,7 +80,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
void VisitConsecutivePrototypeAssignments(
const base::SmallVector<std::pair<const AstRawString*, Expression*>,
kInitialPropertyCount>& properties,
- Variable* var);
+ Variable* var, HoleCheckMode hole_check_mode);
// Visiting function for declarations list and statements are overridden.
void VisitModuleDeclarations(Declaration::List* declarations);
void VisitGlobalDeclarations(Declaration::List* declarations);
diff --git a/src/runtime/runtime-literals.cc b/src/runtime/runtime-literals.cc
index 83074253314c438eed3006332c63c6bf7dfe4421..ded3b466625c99da6f27936ae4e5e0c879baec1b 100644
--- a/src/runtime/runtime-literals.cc
+++ b/src/runtime/runtime-literals.cc
@@ -613,7 +613,7 @@ static DirectHandle<Object> InstantiateIfSharedFunctionInfo(
}
static MaybeDirectHandle<Object> SetPrototypePropertiesSlow(
- Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSObject> obj,
+ Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSAny> obj,
Handle<ObjectBoilerplateDescription> object_boilerplate_description,
DirectHandle<ClosureFeedbackCellArray> feedback_cell_array,
int& current_slot, int start_index = 0) {
@@ -684,7 +684,7 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
DirectHandle<Context> context(isolate->context(), isolate);
- DirectHandle<JSObject> obj = args.at<JSObject>(0); // acc JS Object
+ DirectHandle<JSAny> obj = args.at<JSAny>(0); // acc JS Object
Handle<ObjectBoilerplateDescription> object_boilerplate_description =
args.at<ObjectBoilerplateDescription>(1);
DirectHandle<ClosureFeedbackCellArray> feedback_cell_array =
diff --git a/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden b/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
index 84ebbb755f8a5432c2e8da3d26ceb648bee2cf2d..bb9d697caec6e79810fa757abec4b71ad4cf3f2f 100644
--- a/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
+++ b/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
@@ -50,15 +50,17 @@ snippet: "
"
frame size: 0
parameter count: 1
-bytecode array length: 7
+bytecode array length: 9
bytecodes: [
B(LdaImmutableCurrentContextSlot), U8(2),
- /* 109 E> */ B(SetPrototypeProperties), U8(0), U8(0),
+ /* 109 E> */ B(ThrowReferenceErrorIfHole), U8(1),
+ B(SetPrototypeProperties), U8(0), U8(0),
B(LdaUndefined),
/* 245 S> */ B(Return),
]
constant pool: [
OBJECT_BOILERPLATE_DESCRIPTION_TYPE,
+ INTERNALIZED_ONE_BYTE_STRING_TYPE ["MyClass"],
]
handlers: [
]

View File

@@ -1,6 +1,6 @@
const chalk = require('chalk');
const { GitProcess } = require('dugite');
const childProcess = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
@@ -61,13 +61,16 @@ function getAbsoluteElectronExec () {
return path.resolve(SRC_DIR, getElectronExec());
}
async function handleGitCall (args, gitDir) {
const details = await GitProcess.exec(args, gitDir);
if (details.exitCode === 0) {
return details.stdout.replace(/^\*|\s+|\s+$/, '');
function handleGitCall (args, gitDir) {
const result = childProcess.spawnSync('git', args, {
cwd: gitDir,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (result.status === 0) {
return result.stdout.replace(/^\*|\s+|\s+$/, '');
} else {
const error = GitProcess.parseError(details.stderr);
console.log(`${fail} couldn't parse git process call: `, error);
console.log(`${fail} couldn't parse git process call: `, result.stderr);
process.exit(1);
}
}

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env node
const { GitProcess } = require('dugite');
const { ESLint } = require('eslint');
const minimist = require('minimist');
@@ -431,9 +430,13 @@ function populateLinterWithArgs (linter, opts) {
}
async function findChangedFiles (top) {
const result = await GitProcess.exec(['diff', '--name-only', '--cached'], top);
if (result.exitCode !== 0) {
console.log('Failed to find changed files', GitProcess.parseError(result.stderr));
const result = childProcess.spawnSync('git', ['diff', '--name-only', '--cached'], {
cwd: top,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (result.status !== 0) {
console.log('Failed to find changed files', result.stderr);
process.exit(1);
}
const relativePaths = result.stdout.split(/\r\n|\r|\n/g);

View File

@@ -13,6 +13,7 @@ import { createGitHubTokenStrategy } from '../github-token';
import { ELECTRON_ORG, ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from '../types';
const rootPackageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../../package.json'), 'utf-8'));
rootPackageJson.name = 'electron';
if (!process.env.ELECTRON_NPM_OTP) {
console.error('Please set ELECTRON_NPM_OTP');

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env node
import { Octokit } from '@octokit/rest';
import { GitProcess } from 'dugite';
import { valid, compare, gte, lte } from 'semver';
import { spawnSync } from 'node:child_process';
import { basename } from 'node:path';
import { parseArgs } from 'node:util';
@@ -20,8 +20,12 @@ const semverify = (version: string) => version.replace(/^origin\//, '').replace(
const runGit = async (args: string[]) => {
console.info(`Running: git ${args.join(' ')}`);
const response = await GitProcess.exec(args, ELECTRON_DIR);
if (response.exitCode !== 0) {
const response = spawnSync('git', args, {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (response.status !== 0) {
throw new Error(response.stderr.trim());
}
return response.stdout.trim();

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env node
import { Octokit } from '@octokit/rest';
import { GitProcess } from 'dugite';
import { spawnSync } from 'node:child_process';
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { resolve as _resolve } from 'node:path';
@@ -105,8 +105,13 @@ class Pool {
**/
const runGit = async (dir: string, args: string[]) => {
const response = await GitProcess.exec(args, dir);
if (response.exitCode !== 0) {
const response = spawnSync('git', args, {
cwd: dir,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe'],
maxBuffer: 100 * 1024 * 1024 // 100MB buffer to handle large git outputs
});
if (response.status !== 0) {
throw new Error(response.stderr.trim());
}
return response.stdout.trim();

View File

@@ -1,8 +1,7 @@
import { Octokit } from '@octokit/rest';
import * as chalk from 'chalk';
import { GitProcess } from 'dugite';
import { execSync } from 'node:child_process';
import { execSync, spawnSync } from 'node:child_process';
import { join } from 'node:path';
import { createGitHubTokenStrategy } from './github-token';
@@ -166,11 +165,12 @@ async function createRelease (
}
async function pushRelease (branch: string) {
const pushDetails = await GitProcess.exec(
['push', 'origin', `HEAD:${branch}`, '--follow-tags'],
ELECTRON_DIR
);
if (pushDetails.exitCode === 0) {
const pushDetails = spawnSync('git', ['push', 'origin', `HEAD:${branch}`, '--follow-tags'], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (pushDetails.status === 0) {
console.log(
`${pass} Successfully pushed the release. Wait for ` +
'release builds to finish before running "npm run release".'
@@ -191,11 +191,12 @@ async function runReleaseBuilds (branch: string, newVersion: string) {
async function tagRelease (version: string) {
console.log(`Tagging release ${version}.`);
const checkoutDetails = await GitProcess.exec(
['tag', '-a', '-m', version, version],
ELECTRON_DIR
);
if (checkoutDetails.exitCode === 0) {
const checkoutDetails = spawnSync('git', ['tag', '-a', '-m', version, version], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (checkoutDetails.status === 0) {
console.log(`${pass} Successfully tagged ${version}.`);
} else {
console.log(

View File

@@ -1,6 +1,7 @@
import { GitProcess } from 'dugite';
import * as semver from 'semver';
import { spawnSync } from 'node:child_process';
import { ELECTRON_DIR } from '../lib/utils';
export enum PreType {
@@ -27,7 +28,11 @@ export const isStable = (v: string) => {
export async function nextAlpha (v: string) {
const next = semver.coerce(semver.clean(v));
const tagBlob = await GitProcess.exec(['tag', '--list', '-l', `v${next}-alpha.*`], ELECTRON_DIR);
const tagBlob = spawnSync('git', ['tag', '--list', '-l', `v${next}-alpha.*`], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
const tags = tagBlob.stdout.split('\n').filter(e => e !== '');
tags.sort((t1, t2) => {
const a = parseInt(t1.split('.').pop()!, 10);
@@ -41,7 +46,11 @@ export async function nextAlpha (v: string) {
export async function nextBeta (v: string) {
const next = semver.coerce(semver.clean(v));
const tagBlob = await GitProcess.exec(['tag', '--list', '-l', `v${next}-beta.*`], ELECTRON_DIR);
const tagBlob = spawnSync('git', ['tag', '--list', '-l', `v${next}-beta.*`], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
const tags = tagBlob.stdout.split('\n').filter(e => e !== '');
tags.sort((t1, t2) => {
const a = parseInt(t1.split('.').pop()!, 10);
@@ -57,7 +66,11 @@ export async function nextNightly (v: string) {
let next = semver.valid(semver.coerce(v));
const pre = `nightly.${getCurrentDate()}`;
const branch = (await GitProcess.exec(['rev-parse', '--abbrev-ref', 'HEAD'], ELECTRON_DIR)).stdout.trim();
const branch = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
}).stdout.trim();
if (branch === 'main') {
next = semver.inc(await getLastMajorForMain(), 'major');
} else if (isStable(v)) {
@@ -69,8 +82,12 @@ export async function nextNightly (v: string) {
async function getLastMajorForMain () {
let branchNames;
const result = await GitProcess.exec(['branch', '-a', '--remote', '--list', 'origin/[0-9]*-x-y'], ELECTRON_DIR);
if (result.exitCode === 0) {
const result = spawnSync('git', ['branch', '-a', '--remote', '--list', 'origin/[0-9]*-x-y'], {
cwd: ELECTRON_DIR,
encoding: 'utf8',
stdio: ['inherit', 'pipe', 'pipe']
});
if (result.status === 0) {
branchNames = result.stdout.trim().split('\n');
const filtered = branchNames.map(b => b.replace('origin/', ''));
return getNextReleaseBranch(filtered);

View File

@@ -114,11 +114,14 @@ async function runClangTidy (
outDir: string,
filenames: string[],
checks: string = '',
jobs: number = 1
jobs: number = 1,
fix: boolean = false
): Promise<boolean> {
const cmd = path.resolve(LLVM_BIN, 'clang-tidy');
const args = [`-p=${outDir}`, '--use-color'];
const args = [`-p=${outDir}`];
if (!process.env.CI) args.push('--use-color');
if (fix) args.push('--fix');
if (checks) args.push(`--checks=${checks}`);
// Remove any files that aren't in the compilation database to prevent
@@ -209,7 +212,7 @@ function parseCommandLine () {
if (!arg || arg.startsWith('-')) {
console.log(
'Usage: script/run-clang-tidy.ts [-h|--help] [--jobs|-j] ' +
'[--checks] --out-dir OUTDIR [file1 file2]'
'[--fix] [--checks] --out-dir OUTDIR [file1 file2]'
);
process.exit(0);
}
@@ -218,7 +221,7 @@ function parseCommandLine () {
};
const opts = minimist(process.argv.slice(2), {
boolean: ['help'],
boolean: ['fix', 'help'],
string: ['checks', 'out-dir'],
default: { jobs: 1 },
alias: { help: 'h', jobs: 'j' },
@@ -270,17 +273,23 @@ async function main (): Promise<boolean> {
const filenames = [];
if (opts._.length > 0) {
if (opts._.some((filename) => filename.endsWith('.h'))) {
throw new ErrorWithExitCode(
'Filenames must be for translation units, not headers', 3
);
}
filenames.push(...opts._.map((filename) => path.resolve(filename)));
} else {
filenames.push(
...(await findMatchingFiles(
path.resolve(SOURCE_ROOT, 'shell'),
(filename: string) => /.*\.(?:cc|h|mm)$/.test(filename)
(filename: string) => /.*\.(?:cc|mm)$/.test(filename)
))
);
}
return runClangTidy(outDir, filenames, opts.checks, opts.jobs);
return runClangTidy(outDir, filenames, opts.checks, opts.jobs, opts.fix);
}
if (require.main === module) {

View File

@@ -1037,6 +1037,9 @@ void WebContents::InitWithWebContents(
}
WebContents::~WebContents() {
if (inspectable_web_contents_)
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
if (owner_window_) {
owner_window_->RemoveBackgroundThrottlingSource(this);
}
@@ -1051,8 +1054,6 @@ WebContents::~WebContents() {
return;
}
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
// This event is only for internal use, which is emitted when WebContents is
// being destroyed.
Emit("will-destroy");
@@ -2189,8 +2190,8 @@ void WebContents::DevToolsOpened() {
// Inherit owner window in devtools when it doesn't have one.
auto* devtools = inspectable_web_contents_->GetDevToolsWebContents();
bool has_window = devtools->GetUserData(NativeWindowRelay::UserDataKey());
if (owner_window_ && !has_window) {
DCHECK(!owner_window_.WasInvalidated());
if (owner_window() && !has_window) {
CHECK(!owner_window_.WasInvalidated());
DCHECK_EQ(handle->owner_window(), nullptr);
handle->SetOwnerWindow(devtools, owner_window());
}

View File

@@ -5,12 +5,14 @@
#include "shell/browser/api/electron_api_web_request.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include "base/containers/fixed_flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/values.h"
#include "content/public/browser/web_contents.h"
@@ -25,6 +27,7 @@
#include "shell/browser/api/electron_api_web_frame_main.h"
#include "shell/browser/electron_browser_context.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/login_handler.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_converters/gurl_converter.h"
@@ -108,7 +111,7 @@ v8::Local<v8::Value> HttpResponseHeadersToV8(
// Overloaded by multiple types to fill the |details| object.
void ToDictionary(gin_helper::Dictionary* details,
extensions::WebRequestInfo* info) {
const extensions::WebRequestInfo* info) {
details->Set("id", info->id);
details->Set("url", info->url);
details->Set("method", info->method);
@@ -255,7 +258,7 @@ bool WebRequest::RequestFilter::MatchesType(
}
bool WebRequest::RequestFilter::MatchesRequest(
extensions::WebRequestInfo* info) const {
const extensions::WebRequestInfo* info) const {
// Matches URL and type, and does not match exclude URL.
return MatchesURL(info->url, include_url_patterns_) &&
!MatchesURL(info->url, exclude_url_patterns_) &&
@@ -287,6 +290,10 @@ struct WebRequest::BlockedRequest {
net::CompletionOnceCallback callback;
// Only used for onBeforeSendHeaders.
BeforeSendHeadersCallback before_send_headers_callback;
// The callback to invoke for auth. If |auth_callback.is_null()| is false,
// |callback| must be NULL.
// Only valid for OnAuthRequired.
AuthCallback auth_callback;
// Only used for onBeforeSendHeaders.
raw_ptr<net::HttpRequestHeaders> request_headers = nullptr;
// Only used for onHeadersReceived.
@@ -297,6 +304,8 @@ struct WebRequest::BlockedRequest {
std::string status_line;
// Only used for onBeforeRequest.
raw_ptr<GURL> new_url = nullptr;
// Owns the LoginHandler while waiting for auth credentials.
std::unique_ptr<LoginHandler> login_handler;
};
WebRequest::SimpleListenerInfo::SimpleListenerInfo(RequestFilter filter_,
@@ -603,6 +612,36 @@ void WebRequest::OnSendHeaders(extensions::WebRequestInfo* info,
HandleSimpleEvent(SimpleEvent::kOnSendHeaders, info, request, headers);
}
WebRequest::AuthRequiredResponse WebRequest::OnAuthRequired(
const extensions::WebRequestInfo* request_info,
const net::AuthChallengeInfo& auth_info,
WebRequest::AuthCallback callback,
net::AuthCredentials* credentials) {
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
request_info->render_process_id, request_info->frame_routing_id);
content::WebContents* web_contents = nullptr;
if (rfh)
web_contents = content::WebContents::FromRenderFrameHost(rfh);
BlockedRequest blocked_request;
blocked_request.auth_callback = std::move(callback);
blocked_requests_[request_info->id] = std::move(blocked_request);
auto login_callback =
base::BindOnce(&WebRequest::OnLoginAuthResult, base::Unretained(this),
request_info->id, credentials);
scoped_refptr<net::HttpResponseHeaders> response_headers =
request_info->response_headers;
blocked_requests_[request_info->id].login_handler =
std::make_unique<LoginHandler>(
auth_info, web_contents,
static_cast<base::ProcessId>(request_info->render_process_id),
request_info->url, response_headers, std::move(login_callback));
return AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_IO_PENDING;
}
void WebRequest::OnBeforeRedirect(extensions::WebRequestInfo* info,
const network::ResourceRequest& request,
const GURL& new_location) {
@@ -732,6 +771,26 @@ void WebRequest::HandleSimpleEvent(SimpleEvent event,
info.listener.Run(gin::ConvertToV8(isolate, details));
}
void WebRequest::OnLoginAuthResult(
uint64_t id,
net::AuthCredentials* credentials,
const std::optional<net::AuthCredentials>& maybe_creds) {
auto iter = blocked_requests_.find(id);
if (iter == blocked_requests_.end())
NOTREACHED();
AuthRequiredResponse action =
AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_NO_ACTION;
if (maybe_creds.has_value()) {
*credentials = maybe_creds.value();
action = AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_SET_AUTH;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(iter->second.auth_callback), action));
blocked_requests_.erase(iter);
}
// static
gin_helper::Handle<WebRequest> WebRequest::FromOrCreate(
v8::Isolate* isolate,

View File

@@ -82,6 +82,11 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
void OnSendHeaders(extensions::WebRequestInfo* info,
const network::ResourceRequest& request,
const net::HttpRequestHeaders& headers) override;
AuthRequiredResponse OnAuthRequired(
const extensions::WebRequestInfo* info,
const net::AuthChallengeInfo& auth_info,
AuthCallback callback,
net::AuthCredentials* credentials) override;
void OnBeforeRedirect(extensions::WebRequestInfo* info,
const network::ResourceRequest& request,
const GURL& new_location) override;
@@ -157,6 +162,12 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
v8::Local<v8::Value> response);
void OnHeadersReceivedListenerResult(uint64_t id,
v8::Local<v8::Value> response);
// Callback invoked by LoginHandler when auth credentials are supplied via
// the unified 'login' event. Bridges back into WebRequest's AuthCallback.
void OnLoginAuthResult(
uint64_t id,
net::AuthCredentials* credentials,
const std::optional<net::AuthCredentials>& maybe_creds);
class RequestFilter {
public:
@@ -174,7 +185,7 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
bool is_match_pattern = true);
void AddType(extensions::WebRequestResourceType type);
bool MatchesRequest(extensions::WebRequestInfo* info) const;
bool MatchesRequest(const extensions::WebRequestInfo* info) const;
private:
bool MatchesURL(const GURL& url,

View File

@@ -42,6 +42,23 @@ LoginHandler::LoginHandler(
response_headers, first_auth_attempt));
}
LoginHandler::LoginHandler(
const net::AuthChallengeInfo& auth_info,
content::WebContents* web_contents,
base::ProcessId process_id,
const GURL& url,
scoped_refptr<net::HttpResponseHeaders> response_headers,
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback)
: LoginHandler(auth_info,
web_contents,
/*is_request_for_primary_main_frame=*/false,
/*is_request_for_navigation=*/false,
process_id,
url,
std::move(response_headers),
/*first_auth_attempt=*/false,
std::move(auth_required_callback)) {}
void LoginHandler::EmitEvent(
net::AuthChallengeInfo auth_info,
content::WebContents* web_contents,

View File

@@ -32,6 +32,13 @@ class LoginHandler : public content::LoginDelegate {
scoped_refptr<net::HttpResponseHeaders> response_headers,
bool first_auth_attempt,
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback);
LoginHandler(
const net::AuthChallengeInfo& auth_info,
content::WebContents* web_contents,
base::ProcessId process_id,
const GURL& url,
scoped_refptr<net::HttpResponseHeaders> response_headers,
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback);
~LoginHandler() override;
// disable copy

View File

@@ -55,9 +55,9 @@ ProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
proxied_loader_receiver_(this, std::move(loader_receiver)),
target_client_(std::move(client)),
current_response_(network::mojom::URLResponseHead::New()),
// Always use "extraHeaders" mode to be compatible with old APIs, except
// when the |request_id_| is zero, which is not supported in Chromium and
// only happens in Electron when the request is started from net module.
// Always use "extraHeaders" mode to be compatible with old APIs.
// A non-zero request ID is required to use the TrustedHeaderClient path
// which allows webRequest to modify headers like Proxy-Authorization.
has_any_extra_headers_listeners_(network_service_request_id != 0) {
// If there is a client error, clean up the request.
target_client_.set_disconnect_handler(base::BindOnce(

View File

@@ -372,19 +372,21 @@ void ProxyingWebSocket::OnHeadersReceivedComplete(int error_code) {
ContinueToCompleted();
}
void ProxyingWebSocket::OnAuthRequiredComplete(AuthRequiredResponse rv) {
void ProxyingWebSocket::OnAuthRequiredComplete(
WebRequestAPI::AuthRequiredResponse rv) {
CHECK(auth_required_callback_);
ResumeIncomingMethodCallProcessing();
switch (rv) {
case AuthRequiredResponse::kNoAction:
case AuthRequiredResponse::kCancelAuth:
case WebRequestAPI::AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_NO_ACTION:
case WebRequestAPI::AuthRequiredResponse::
AUTH_REQUIRED_RESPONSE_CANCEL_AUTH:
std::move(auth_required_callback_).Run(std::nullopt);
break;
case AuthRequiredResponse::kSetAuth:
case WebRequestAPI::AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_SET_AUTH:
std::move(auth_required_callback_).Run(auth_credentials_);
break;
case AuthRequiredResponse::kIoPending:
case WebRequestAPI::AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_IO_PENDING:
NOTREACHED();
}
}
@@ -401,8 +403,13 @@ void ProxyingWebSocket::OnHeadersReceivedCompleteForAuth(
auto continuation = base::BindRepeating(
&ProxyingWebSocket::OnAuthRequiredComplete, weak_factory_.GetWeakPtr());
auto auth_rv = AuthRequiredResponse::kCancelAuth;
auto auth_rv = web_request_api_->OnAuthRequired(
&info_, auth_info, std::move(continuation), &auth_credentials_);
PauseIncomingMethodCallProcessing();
if (auth_rv ==
WebRequestAPI::AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_IO_PENDING) {
return;
}
OnAuthRequiredComplete(auth_rv);
}

View File

@@ -36,21 +36,6 @@ class ProxyingWebSocket : public network::mojom::WebSocketHandshakeClient,
public:
using WebSocketFactory = content::ContentBrowserClient::WebSocketFactory;
// AuthRequiredResponse indicates how an OnAuthRequired call is handled.
enum class AuthRequiredResponse {
// No credentials were provided.
kNoAction,
// AuthCredentials is filled in with a username and password, which should
// be used in a response to the provided auth challenge.
kSetAuth,
// The request should be canceled.
kCancelAuth,
// The action will be decided asynchronously. |callback| will be invoked
// when the decision is made, and one of the other AuthRequiredResponse
// values will be passed in with the same semantics as described above.
kIoPending,
};
ProxyingWebSocket(
WebRequestAPI* web_request_api,
WebSocketFactory factory,
@@ -119,7 +104,7 @@ class ProxyingWebSocket : public network::mojom::WebSocketHandshakeClient,
void ContinueToStartRequest(int error_code);
void OnHeadersReceivedComplete(int error_code);
void ContinueToHeadersReceived();
void OnAuthRequiredComplete(AuthRequiredResponse rv);
void OnAuthRequiredComplete(WebRequestAPI::AuthRequiredResponse rv);
void OnHeadersReceivedCompleteForAuth(const net::AuthChallengeInfo& auth_info,
int rv);
void ContinueToCompleted();

View File

@@ -27,6 +27,23 @@ class WebRequestAPI {
const std::set<std::string>& set_headers,
int error_code)>;
// AuthRequiredResponse indicates how an OnAuthRequired call is handled.
enum class AuthRequiredResponse {
// No credentials were provided.
AUTH_REQUIRED_RESPONSE_NO_ACTION,
// AuthCredentials is filled in with a username and password, which should
// be used in a response to the provided auth challenge.
AUTH_REQUIRED_RESPONSE_SET_AUTH,
// The request should be canceled.
AUTH_REQUIRED_RESPONSE_CANCEL_AUTH,
// The action will be decided asynchronously. |callback| will be invoked
// when the decision is made, and one of the other AuthRequiredResponse
// values will be passed in with the same semantics as described above.
AUTH_REQUIRED_RESPONSE_IO_PENDING,
};
using AuthCallback = base::OnceCallback<void(AuthRequiredResponse)>;
virtual bool HasListener() const = 0;
virtual int OnBeforeRequest(extensions::WebRequestInfo* info,
const network::ResourceRequest& request,
@@ -36,6 +53,11 @@ class WebRequestAPI {
const network::ResourceRequest& request,
BeforeSendHeadersCallback callback,
net::HttpRequestHeaders* headers) = 0;
virtual AuthRequiredResponse OnAuthRequired(
const extensions::WebRequestInfo* info,
const net::AuthChallengeInfo& auth_info,
AuthCallback callback,
net::AuthCredentials* credentials) = 0;
virtual int OnHeadersReceived(
extensions::WebRequestInfo* info,
const network::ResourceRequest& request,

View File

@@ -285,6 +285,19 @@ void SwizzleSwipeWithEvent(NSView* view, SEL swiz_selector) {
return base::SysUTF8ToNSString(shell_ ? shell_->GetTitle() : "");
}
- (NSString*)accessibilityDocument {
// Prefer representedFilename set via Electron's setRepresentedFilename API.
// This works around a Chromium change (https://crrev.com/c/6187085) where
// NativeWidgetMacNSWindow's accessibilityDocument override doesn't fall back
// to NSWindow's default behavior of returning the representedFilename.
NSString* representedFilename = [self representedFilename];
if (representedFilename.length > 0) {
return [[NSURL fileURLWithPath:representedFilename] absoluteString];
}
// Fall back to Chromium's implementation for web content URLs.
return [super accessibilityDocument];
}
- (BOOL)canBecomeMainWindow {
return !self.disableKeyOrMainWindow;
}

View File

@@ -25,6 +25,7 @@
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/linux/linux_ui.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/platform_window/extensions/wayland_extension.h"
#include "ui/platform_window/platform_window.h"
#include "ui/platform_window/platform_window_init_properties.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host.h"
@@ -58,6 +59,24 @@ bool ElectronDesktopWindowTreeHostLinux::IsShowingFrame() const {
!native_window_view_->IsMinimized();
}
void ElectronDesktopWindowTreeHostLinux::SetWindowIcons(
const gfx::ImageSkia& window_icon,
const gfx::ImageSkia& app_icon) {
DesktopWindowTreeHostLinux::SetWindowIcons(window_icon, app_icon);
if (ui::GetWaylandToplevelExtension(*platform_window()))
saved_window_icon_ = window_icon;
}
void ElectronDesktopWindowTreeHostLinux::Show(
ui::mojom::WindowShowState show_state,
const gfx::Rect& restore_bounds) {
DesktopWindowTreeHostLinux::Show(show_state, restore_bounds);
if (!saved_window_icon_.isNull())
DesktopWindowTreeHostLinux::SetWindowIcons(saved_window_icon_, {});
}
gfx::Insets ElectronDesktopWindowTreeHostLinux::CalculateInsetsInDIP(
ui::PlatformWindowState window_state) const {
// If we are not showing frame, the insets should be zero.

View File

@@ -11,6 +11,7 @@
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/linux/device_scale_factor_observer.h"
#include "ui/linux/linux_ui.h"
#include "ui/native_theme/native_theme_observer.h"
@@ -44,6 +45,10 @@ class ElectronDesktopWindowTreeHostLinux
protected:
// views::DesktopWindowTreeHostLinuxImpl:
void OnWidgetInitDone() override;
void SetWindowIcons(const gfx::ImageSkia& window_icon,
const gfx::ImageSkia& app_icon) override;
void Show(ui::mojom::WindowShowState show_state,
const gfx::Rect& restore_bounds) override;
// ui::PlatformWindowDelegate
gfx::Insets CalculateInsetsInDIP(
@@ -71,6 +76,8 @@ class ElectronDesktopWindowTreeHostLinux
bool IsShowingFrame() const;
gfx::ImageSkia saved_window_icon_;
raw_ptr<NativeWindowViews> native_window_view_; // weak ref
base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>

View File

@@ -17,6 +17,7 @@
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/sequence_checker.h"
#include "content/public/browser/global_request_id.h"
#include "gin/object_template_builder.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
@@ -375,6 +376,13 @@ void SimpleURLLoaderWrapper::Start() {
loader_->SetAllowHttpErrorResults(true);
loader_->SetURLLoaderFactoryOptions(request_options_);
// Set a non-zero request ID so that the request can use the
// TrustedHeaderClient code path for webRequest header modifications.
// See proxying_url_loader_factory.cc for details.
if (electron::IsBrowserProcess()) {
loader_->SetRequestID(
content::GlobalRequestID::MakeBrowserInitiated().request_id);
}
loader_->SetOnResponseStartedCallback(base::BindOnce(
&SimpleURLLoaderWrapper::OnResponseStarted, weak_factory_.GetWeakPtr()));
loader_->SetOnRedirectCallback(base::BindRepeating(

View File

@@ -1,4 +1,4 @@
import { ipcMain, protocol, session, WebContents, webContents } from 'electron/main';
import { ipcMain, net, protocol, session, WebContents, webContents } from 'electron/main';
import { expect } from 'chai';
import * as WebSocket from 'ws';
@@ -455,6 +455,35 @@ describe('webRequest module', () => {
}));
expect(onSendHeadersCalled).to.be.true();
});
it('can inject Proxy-Authorization header for net module requests', async () => {
// Proxy-Authorization is normally rejected by Chromium's network service
// for security reasons. However, for Electron's trusted net module,
// webRequest.onBeforeSendHeaders should be able to inject it via the
// TrustedHeaderClient code path.
const proxyAuthValue = 'Basic test-credentials';
let receivedProxyAuth: string | undefined;
const server = http.createServer((req, res) => {
receivedProxyAuth = req.headers['proxy-authorization'];
res.end('ok');
});
const { url: serverUrl } = await listen(server);
try {
ses.webRequest.onBeforeSendHeaders((details, callback) => {
const requestHeaders = details.requestHeaders;
requestHeaders['Proxy-Authorization'] = proxyAuthValue;
callback({ requestHeaders });
});
const response = await net.fetch(serverUrl, { bypassCustomProtocolHandlers: true });
expect(response.ok).to.be.true();
expect(receivedProxyAuth).to.equal(proxyAuthValue);
} finally {
server.close();
}
});
});
describe('webRequest.onSendHeaders', () => {
@@ -733,5 +762,80 @@ describe('webRequest module', () => {
expect(reqHeaders['/websocket'].foo).to.equal('bar');
expect(reqHeaders['/'].foo).to.equal('bar');
});
it('authenticates a WebSocket via login event', async () => {
const authServer = http.createServer();
const wssAuth = new WebSocket.Server({ noServer: true });
const expected = 'Basic ' + Buffer.from('user:pass').toString('base64');
wssAuth.on('connection', ws => {
ws.send('Authenticated!');
});
authServer.on('upgrade', (req, socket, head) => {
const auth = req.headers.authorization || '';
if (auth !== expected) {
socket.write(
'HTTP/1.1 401 Unauthorized\r\n' +
'WWW-Authenticate: Basic realm="Test"\r\n' +
'Content-Length: 0\r\n' +
'\r\n'
);
socket.destroy();
return;
}
wssAuth.handleUpgrade(req, socket as Socket, head, ws => {
wssAuth.emit('connection', ws, req);
});
});
const { port } = await listen(authServer);
const ses = session.fromPartition(`WebRequestWSAuth-${Date.now()}`);
const contents = (webContents as typeof ElectronInternal.WebContents).create({
session: ses,
sandbox: true
});
defer(() => {
contents.destroy();
authServer.close();
wssAuth.close();
});
ses.webRequest.onBeforeRequest({ urls: ['ws://*/*'] }, (details, callback) => {
callback({});
});
contents.on('login', (event, details: any, _: any, callback: (u: string, p: string) => void) => {
if (details?.url?.startsWith(`ws://localhost:${port}`)) {
event.preventDefault();
callback('user', 'pass');
}
});
await contents.loadFile(path.join(fixturesPath, 'blank.html'));
const message = await contents.executeJavaScript(`new Promise((resolve, reject) => {
let attempts = 0;
function connect() {
attempts++;
const ws = new WebSocket('ws://localhost:${port}');
ws.onmessage = e => resolve(e.data);
ws.onerror = () => {
if (attempts < 3) {
setTimeout(connect, 50);
} else {
reject(new Error('WebSocket auth failed'));
}
};
}
connect();
setTimeout(() => reject(new Error('timeout')), 5000);
});`);
expect(message).to.equal('Authenticated!');
});
});
});

View File

@@ -1,12 +1,12 @@
import { expect } from 'chai';
import { GitProcess, IGitExecutionOptions, IGitResult } from 'dugite';
import * as sinon from 'sinon';
import { SpawnSyncReturns } from 'node:child_process';
import * as path from 'node:path';
import * as notes from '../script/release/notes/notes';
/* Fake a Dugite GitProcess that only returns the specific
/* Fake a git spawnSync that only returns the specific
commits that we want to test */
class Commit {
@@ -43,10 +43,10 @@ class GitFake {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
exec (args: string[], path: string, options?: IGitExecutionOptions | undefined): Promise<IGitResult> {
exec (command: string, args: string[], options?: any): SpawnSyncReturns<string> {
let stdout = '';
const stderr = '';
const exitCode = 0;
const status = 0;
if (args.length === 3 && args[0] === 'merge-base') {
// expected form: `git merge-base branchName1 branchName2`
@@ -78,10 +78,10 @@ class GitFake {
// git branch --all --contains ${ref} --sort version:refname
stdout = args[3];
} else {
console.error('unhandled GitProcess.exec():', args);
console.error('unhandled git spawnSync():', args);
}
return Promise.resolve({ exitCode, stdout, stderr });
return { status, stdout, stderr, pid: 0, output: [null, stdout, stderr], signal: null };
}
}
@@ -118,8 +118,14 @@ describe('release notes', () => {
});
beforeEach(() => {
const wrapper = (args: string[], path: string, options?: IGitExecutionOptions | undefined) => gitFake.exec(args, path, options);
sandbox.replace(GitProcess, 'exec', wrapper);
const wrapper = (command: unknown, args: unknown, options?: unknown) => {
if (command === 'git' && Array.isArray(args)) {
return gitFake.exec(command as string, args as string[], options);
}
// Default behavior for non-git commands
return { status: 0, stdout: '', stderr: '', pid: 0, output: [null, '', ''], signal: null };
};
sandbox.stub(require('node:child_process'), 'spawnSync').callsFake(wrapper);
gitFake.setBranch(oldBranch, [...sharedHistory, oldFix]);
});

View File

@@ -1,7 +1,8 @@
import { expect } from 'chai';
import { GitProcess, IGitExecutionOptions, IGitResult } from 'dugite';
import * as sinon from 'sinon';
import { SpawnSyncReturns } from 'node:child_process';
import { ifdescribe } from './lib/spec-helpers';
import { nextVersion } from '../script/release/version-bumper';
@@ -33,10 +34,10 @@ class GitFake {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
exec (args: string[], path: string, options?: IGitExecutionOptions | undefined): Promise<IGitResult> {
exec (command: string, args: string[], options?: any): SpawnSyncReturns<string> {
let stdout = '';
const stderr = '';
const exitCode = 0;
const status = 0;
// handle for promoting from current master HEAD
let branch = 'stable';
@@ -48,7 +49,7 @@ class GitFake {
if (!this.branches[branch]) this.setBranch(branch);
stdout = this.branches[branch].join('\n');
return Promise.resolve({ exitCode, stdout, stderr });
return { status, stdout, stderr, pid: 0, output: [null, stdout, stderr], signal: null };
}
}
@@ -163,8 +164,14 @@ describe('version-bumper', () => {
const gitFake = new GitFake();
beforeEach(() => {
const wrapper = (args: string[], path: string, options?: IGitExecutionOptions | undefined) => gitFake.exec(args, path, options);
sandbox.replace(GitProcess, 'exec', wrapper);
const wrapper = (command: unknown, args: unknown, options?: unknown) => {
if (command === 'git' && Array.isArray(args)) {
return gitFake.exec(command as string, args as string[], options);
}
// Default behavior for non-git commands
return { status: 0, stdout: '', stderr: '', pid: 0, output: [null, '', ''], signal: null };
};
sandbox.stub(require('node:child_process'), 'spawnSync').callsFake(wrapper);
});
afterEach(() => {

141
yarn.lock
View File

@@ -432,6 +432,70 @@ __metadata:
languageName: node
linkType: hard
"@electron-ci/dev-root@workspace:.":
version: 0.0.0-use.local
resolution: "@electron-ci/dev-root@workspace:."
dependencies:
"@azure/storage-blob": "npm:^12.28.0"
"@datadog/datadog-ci": "npm:^4.1.2"
"@electron/asar": "npm:^3.2.13"
"@electron/docs-parser": "npm:^2.0.0"
"@electron/fiddle-core": "npm:^1.3.4"
"@electron/github-app-auth": "npm:^3.2.0"
"@electron/lint-roller": "npm:^3.1.2"
"@electron/typescript-definitions": "npm:^9.1.2"
"@octokit/rest": "npm:^20.1.2"
"@primer/octicons": "npm:^10.0.0"
"@types/minimist": "npm:^1.2.5"
"@types/node": "npm:^22.7.7"
"@types/semver": "npm:^7.5.8"
"@types/stream-json": "npm:^1.7.8"
"@types/temp": "npm:^0.9.4"
"@typescript-eslint/eslint-plugin": "npm:^8.32.1"
"@typescript-eslint/parser": "npm:^8.7.0"
"@xmldom/xmldom": "npm:^0.8.11"
buffer: "npm:^6.0.3"
chalk: "npm:^4.1.0"
check-for-leaks: "npm:^1.2.1"
eslint: "npm:^8.57.1"
eslint-config-standard: "npm:^17.1.0"
eslint-plugin-import: "npm:^2.32.0"
eslint-plugin-markdown: "npm:^5.1.0"
eslint-plugin-mocha: "npm:^10.5.0"
eslint-plugin-n: "npm:^16.6.2"
eslint-plugin-node: "npm:^11.1.0"
eslint-plugin-promise: "npm:^6.6.0"
events: "npm:^3.2.0"
folder-hash: "npm:^4.1.1"
got: "npm:^11.8.5"
husky: "npm:^9.1.7"
lint-staged: "npm:^16.1.0"
markdownlint-cli2: "npm:^0.18.0"
minimist: "npm:^1.2.8"
node-gyp: "npm:^11.4.2"
null-loader: "npm:^4.0.1"
pre-flight: "npm:^2.0.0"
process: "npm:^0.11.10"
remark-cli: "npm:^12.0.1"
remark-preset-lint-markdown-style-guide: "npm:^6.0.1"
semver: "npm:^7.6.3"
stream-json: "npm:^1.9.1"
tap-xunit: "npm:^2.4.1"
temp: "npm:^0.9.4"
timers-browserify: "npm:1.4.2"
ts-loader: "npm:^8.0.2"
ts-node: "npm:6.2.0"
typescript: "npm:^5.6.2"
url: "npm:^0.11.4"
webpack: "npm:^5.95.0"
webpack-cli: "npm:^5.1.4"
wrapper-webpack-plugin: "npm:^2.2.0"
dependenciesMeta:
abstract-socket:
built: true
languageName: unknown
linkType: soft
"@electron-ci/echo@npm:*, @electron-ci/echo@workspace:spec/fixtures/native-addon/echo":
version: 0.0.0-use.local
resolution: "@electron-ci/echo@workspace:spec/fixtures/native-addon/echo"
@@ -4452,16 +4516,6 @@ __metadata:
languageName: node
linkType: hard
"dugite@npm:^2.7.1":
version: 2.7.1
resolution: "dugite@npm:2.7.1"
dependencies:
progress: "npm:^2.0.3"
tar: "npm:^6.1.11"
checksum: 10c0/8b7992b91abc2473e43a4900c5b100f5a4b98785b9955275c72b1a621152b5df9ff794cda50daced5ee452f4dc95b313ce9c30af5dc9e302241ef9f661a593df
languageName: node
linkType: hard
"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1":
version: 1.0.1
resolution: "dunder-proto@npm:1.0.1"
@@ -4569,73 +4623,6 @@ __metadata:
languageName: node
linkType: hard
"electron@workspace:.":
version: 0.0.0-use.local
resolution: "electron@workspace:."
dependencies:
"@azure/storage-blob": "npm:^12.28.0"
"@datadog/datadog-ci": "npm:^4.1.2"
"@electron/asar": "npm:^3.2.13"
"@electron/docs-parser": "npm:^2.0.0"
"@electron/fiddle-core": "npm:^1.3.4"
"@electron/github-app-auth": "npm:^3.2.0"
"@electron/lint-roller": "npm:^3.1.2"
"@electron/typescript-definitions": "npm:^9.1.2"
"@octokit/rest": "npm:^20.1.2"
"@primer/octicons": "npm:^10.0.0"
"@types/minimist": "npm:^1.2.5"
"@types/node": "npm:^22.7.7"
"@types/semver": "npm:^7.5.8"
"@types/stream-json": "npm:^1.7.8"
"@types/temp": "npm:^0.9.4"
"@typescript-eslint/eslint-plugin": "npm:^8.32.1"
"@typescript-eslint/parser": "npm:^8.7.0"
"@xmldom/xmldom": "npm:^0.8.11"
buffer: "npm:^6.0.3"
chalk: "npm:^4.1.0"
check-for-leaks: "npm:^1.2.1"
dugite: "npm:^2.7.1"
eslint: "npm:^8.57.1"
eslint-config-standard: "npm:^17.1.0"
eslint-plugin-import: "npm:^2.32.0"
eslint-plugin-markdown: "npm:^5.1.0"
eslint-plugin-mocha: "npm:^10.5.0"
eslint-plugin-n: "npm:^16.6.2"
eslint-plugin-node: "npm:^11.1.0"
eslint-plugin-promise: "npm:^6.6.0"
events: "npm:^3.2.0"
folder-hash: "npm:^4.1.1"
got: "npm:^11.8.5"
husky: "npm:^9.1.7"
lint-staged: "npm:^16.1.0"
markdownlint-cli2: "npm:^0.18.0"
minimist: "npm:^1.2.8"
node-gyp: "npm:^11.4.2"
null-loader: "npm:^4.0.1"
pre-flight: "npm:^2.0.0"
process: "npm:^0.11.10"
remark-cli: "npm:^12.0.1"
remark-preset-lint-markdown-style-guide: "npm:^6.0.1"
semver: "npm:^7.6.3"
stream-json: "npm:^1.9.1"
tap-xunit: "npm:^2.4.1"
temp: "npm:^0.9.4"
timers-browserify: "npm:1.4.2"
ts-loader: "npm:^8.0.2"
ts-node: "npm:6.2.0"
typescript: "npm:^5.6.2"
url: "npm:^0.11.4"
webpack: "npm:^5.95.0"
webpack-cli: "npm:^5.1.4"
wrapper-webpack-plugin: "npm:^2.2.0"
dependenciesMeta:
abstract-socket:
built: true
dugite:
built: true
languageName: unknown
linkType: soft
"emoji-regex@npm:^10.2.1, emoji-regex@npm:^10.3.0":
version: 10.4.0
resolution: "emoji-regex@npm:10.4.0"