Compare commits

..

1 Commits

Author SHA1 Message Date
Charles Kerr
8f6dd05f84 fix: try clearing InspectableWebContents delegate earlier
Fix a crash that appears to be a DevTools callback to `DevToolsOpened()`
while the WebContents teardown is underway.

This PR re-applies 5bd2938 / #49406: the first thing the WebContents
destructor does is to clear the IWCV's delegate. That approach was
accidentaly circumvented a little by 9f9a5b8 / #50032 which added new
code to the beginning of the destructor before clearing the delgate.

Sample crash trace:

Received signal 11 SEGV_MAPERR 0000000001b8
 0 0x55b70ad996b2 base::debug::CollectStackTrace() [../../base/debug/stack_trace_posix.cc:1048:7]
 1 0x55b70ad81021 base::debug::StackTrace::StackTrace() [../../base/debug/stack_trace.cc:280:20]
 2 0x55b70ad9906f base::debug::(anonymous namespace)::StackDumpSignalHandler() [../../base/debug/stack_trace_posix.cc:483:3]
 3 0x7fe851b19520 (/usr/lib/x86_64-linux-gnu/libc.so.6+0x4251f)
 4 0x55b70ac8c60d base::internal::WeakReference::IsValid() [../../base/memory/weak_ptr.cc:74:0]
 5 0x55b7041101e8 electron::api::WebContents::DevToolsOpened() [../../base/memory/weak_ptr.h:238:32]
 6 0x55b7041f5141 electron::InspectableWebContents::LoadCompleted() [../../electron/shell/browser/ui/inspectable_web_contents.cc:632:27]
 7 0x55b704033be3 base::RepeatingCallback<>::Run() [../../base/functional/callback.h:343:12]
 8 0x55b712272d9a (anonymous namespace)::ParseAndHandle<>() [../../chrome/browser/devtools/devtools_embedder_message_dispatcher.cc:320:13]
 9 0x55b712272ec2 base::internal::Invoker<>::Run() [../../base/functional/bind_internal.h:673:12]
10 0x55b712272cf3 base::RepeatingCallback<>::Run() [../../base/functional/callback.h:343:12]
11 0x55b712272c36 DispatcherImpl::Dispatch() [../../chrome/browser/devtools/devtools_embedder_message_dispatcher.cc:389:48]
12 0x55b7041f89c6 electron::InspectableWebContents::HandleMessageFromDevToolsFrontend() [../../electron/shell/browser/ui/inspectable_web_contents.cc:962:33]
2026-04-30 16:27:01 -05:00
4 changed files with 21 additions and 134 deletions

View File

@@ -134,12 +134,6 @@ When a cookie is deleted, the change cause remains `explicit`.
When the cookie being set is identical to an existing one (same name, domain, path, and value, with no actual changes), the change cause is `inserted-no-change-overwrite`.
When the value of the cookie being set remains unchanged but some of its attributes are updated, such as the expiration attribute, the change cause will be `inserted-no-value-change-overwrite`.
### Deprecated: `showHiddenFiles` in Dialogs on Linux
This property will still be honored on macOS and Windows, but support on Linux
will be removed in Electron 42. GTK intends for this to be a user choice rather
than an app choice and has removed the API to do this programmatically.
## Planned Breaking API Changes (40.0)
### Deprecated: `clipboard` API access from renderer processes
@@ -153,6 +147,12 @@ your preload script and expose it using the [contextBridge](https://www.electron
Debug symbols for MacOS (dSYM) now use xz compression in order to handle larger file sizes. `dsym.zip` files are now
`dsym.tar.xz` files. End users using debug symbols may need to update their zip utilities.
### Deprecated: `showHiddenFiles` in Dialogs on Linux
This property will still be honored on macOS and Windows, but support on Linux
will be removed in Electron 42. GTK intends for this to be a user choice rather
than an app choice and has removed the API to do this programmatically.
## Planned Breaking API Changes (39.0)
### Deprecated: `--host-rules` command line switch

View File

@@ -72,17 +72,8 @@ if (process.platform === 'win32') {
}
}
// Map process.exit to app.exit, which quits gracefully. When called without
// an explicit code, fall back to process.exitCode like Node.js does.
process.exit = ((code: number | string | undefined | null) => {
// Refs https://github.com/nodejs/node/blob/fc192ee030ee076b948ce7d9d72cba6c101989b8/lib/internal/process/per_thread.js#L229-L252
if (code !== undefined) {
// Node.js handles any string to number conversion here for us
process.exitCode = code;
}
app.exit(process.exitCode || 0);
}) as typeof process.exit;
// Map process.exit to app.exit, which quits gracefully.
process.exit = app.exit as () => never;
// Load the RPC server.
require('@electron/internal/browser/rpc-server');

View File

@@ -1122,6 +1122,12 @@ void WebContents::InitWithWebContents(
}
WebContents::~WebContents() {
// DevTools frontend messages use base::Unretained delegate callbacks.
// Clear the delegate before other teardown work can trigger callbacks
// into this partially destroyed WebContents.
if (inspectable_web_contents_)
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
if (web_contents()) {
auto* permission_manager = static_cast<ElectronPermissionManager*>(
web_contents()->GetBrowserContext()->GetPermissionControllerDelegate());
@@ -1129,9 +1135,6 @@ WebContents::~WebContents() {
permission_manager->CancelPendingRequests(web_contents());
}
if (inspectable_web_contents_)
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
if (owner_window_) {
owner_window_->RemoveBackgroundThrottlingSource(this);
}

View File

@@ -17,14 +17,7 @@ import {
spawn
} from './lib/codesign-helpers';
import { withTempDirectory } from './lib/fs-helpers';
import {
getRemoteContext,
ifdescribe,
ifit,
itremote,
startRemoteControlApp,
useRemoteContext
} from './lib/spec-helpers';
import { getRemoteContext, ifdescribe, ifit, itremote, useRemoteContext } from './lib/spec-helpers';
const mainFixturesPath = path.resolve(__dirname, 'fixtures');
@@ -593,115 +586,9 @@ describe('node feature', () => {
});
});
describe('process.exit', () => {
it('exits with exit code zero when called without an argument', async () => {
const rc = await startRemoteControlApp();
rc.remotely(() => {
setImmediate(() => process.exit());
}).catch(() => {});
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(0);
});
it('uses process.exitCode when called without an argument', async () => {
const rc = await startRemoteControlApp();
rc.remotely(() => {
process.exitCode = 42;
setImmediate(() => process.exit());
}).catch(() => {});
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(42);
});
it('overrides process.exitCode when called with an argument', async () => {
const rc = await startRemoteControlApp();
rc.remotely(() => {
process.exitCode = 42;
setImmediate(() => process.exit(11));
}).catch(() => {});
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(11);
});
it('can be called with a null argument', async () => {
const rc = await startRemoteControlApp();
rc.remotely(() => {
setImmediate(() => process.exit(null));
}).catch(() => {});
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(0);
});
it('can be called with a number argument', async () => {
const rc = await startRemoteControlApp();
rc.remotely(() => {
setImmediate(() => process.exit(7));
}).catch(() => {});
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(7);
});
it('throws with an invalid number argument', async () => {
const rc = await startRemoteControlApp();
let stdout = '';
rc.process.stdout!.on('data', (d) => {
stdout += d.toString();
});
rc.remotely(() => {
setImmediate(() => {
try {
process.exit(4.2);
} catch (err) {
console.log(err);
process.exit(99);
}
});
}).catch(() => {});
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(99);
expect(stdout).to.match(
/RangeError \[ERR_OUT_OF_RANGE\]: The value of "code" is out of range. It must be an integer./
);
});
it('can be called with a string argument', async () => {
const rc = await startRemoteControlApp();
rc.remotely(() => {
setImmediate(() => process.exit('12'));
}).catch(() => {});
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(12);
});
it('throws with an invalid string argument', async () => {
const rc = await startRemoteControlApp();
let stdout = '';
rc.process.stdout!.on('data', (d) => {
stdout += d.toString();
});
rc.remotely(() => {
setImmediate(() => {
try {
process.exit('invalid');
} catch (err) {
console.log(err);
process.exit(99);
}
});
}).catch(() => {});
const [code] = await once(rc.process, 'exit');
expect(code).to.equal(99);
expect(stdout).to.match(/TypeError \[ERR_INVALID_ARG_TYPE\]/);
});
});
describe('process.stdout', () => {
useRemoteContext();
it('is a real Node stream', () => {
expect((process.stdout as any)._type).to.not.be.undefined();
});
itremote('does not throw an exception when accessed', () => {
expect(() => process.stdout).to.not.throw();
});
@@ -1036,6 +923,12 @@ describe('node feature', () => {
});
});
describe('process.stdout', () => {
it('is a real Node stream', () => {
expect((process.stdout as any)._type).to.not.be.undefined();
});
});
describe('fs.readFile', () => {
it('can accept a FileHandle as the Path argument', async () => {
const filePathForHandle = path.resolve(mainFixturesPath, 'dogs-running.txt');