Compare commits

..

2 Commits

Author SHA1 Message Date
Charles Kerr
206162c97c chore: make linter happy 2026-02-24 16:08:08 -06:00
Shelley Vohr
4c87f55fa3 fix: nodeIntegrationInWorker not working in AudioWorklet 2026-02-23 16:17:58 +01:00
20 changed files with 217 additions and 146 deletions

View File

@@ -51,6 +51,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v3.29.5
with:
sarif_file: results.sarif

View File

@@ -18,7 +18,7 @@ jobs:
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # tag: v10.2.0
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # tag: v10.1.1
with:
repo-token: ${{ steps.generate-token.outputs.token }}
days-before-stale: 90
@@ -42,7 +42,7 @@ jobs:
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # tag: v10.2.0
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # tag: v10.1.1
with:
repo-token: ${{ steps.generate-token.outputs.token }}
days-before-stale: -1

View File

@@ -31,7 +31,7 @@ jobs:
echo "isLatestRelease=false" >> $GITHUB_OUTPUT
fi
- name: Trigger website docs update
if: ${{ steps.check-if-latest-release.outputs.isLatestRelease == 'true' }}
if: ${{ steps.check-if-latest-release.outputs.isLatestRelease }}
env:
GH_REPO: electron/website
GH_TOKEN: ${{ fromJSON(steps.secret-service.outputs.secrets).WEBSITE_DOCS_UPDATER_APP_TOKEN }}

View File

@@ -1332,7 +1332,7 @@ Returns `boolean` - Whether the current desktop environment is Unity launcher.
### `app.getLoginItemSettings([options])` _macOS_ _Windows_
* `options` Object (optional)
* `type` string (optional) _macOS_ - Can be `mainAppService`, `agentService`, `daemonService`, or `loginItemService`. Defaults to `mainAppService`. Only available on macOS 13 and up. See [app.setLoginItemSettings](app.md#appsetloginitemsettingssettings-macos-windows) for more information about each type.
* `type` string (optional) _macOS_ - Can be one of `mainAppService`, `agentService`, `daemonService`, or `loginItemService`. Defaults to `mainAppService`. Only available on macOS 13 and up. See [app.setLoginItemSettings](app.md#appsetloginitemsettingssettings-macos-windows) for more information about each type.
* `serviceName` string (optional) _macOS_ - The name of the service. Required if `type` is non-default. Only available on macOS 13 and up.
* `path` string (optional) _Windows_ - The executable path to compare against. Defaults to `process.execPath`.
* `args` string[] (optional) _Windows_ - The command-line arguments to compare against. Defaults to an empty array.
@@ -1347,13 +1347,13 @@ Returns `Object`:
* `wasOpenedAtLogin` boolean _macOS_ - `true` if the app was opened at login automatically.
* `wasOpenedAsHidden` boolean _macOS_ _Deprecated_ - `true` if the app was opened as a hidden login item. This indicates that the app should not open any windows at startup. This setting is not available on [MAS builds][mas-builds] or on macOS 13 and up.
* `restoreState` boolean _macOS_ _Deprecated_ - `true` if the app was opened as a login item that should restore the state from the previous session. This indicates that the app should restore the windows that were open the last time the app was closed. This setting is not available on [MAS builds][mas-builds] or on macOS 13 and up.
* `status` string _macOS_ - can be `not-registered`, `enabled`, `requires-approval`, or `not-found`.
* `status` string _macOS_ - can be one of `not-registered`, `enabled`, `requires-approval`, or `not-found`.
* `executableWillLaunchAtLogin` boolean _Windows_ - `true` if app is set to open at login and its run key is not deactivated. This differs from `openAtLogin` as it ignores the `args` option, this property will be true if the given executable would be launched at login with **any** arguments.
* `launchItems` Object[] _Windows_
* `name` string _Windows_ - name value of a registry entry.
* `path` string _Windows_ - The executable to an app that corresponds to a registry entry.
* `args` string[] _Windows_ - the command-line arguments to pass to the executable.
* `scope` string _Windows_ - can be `user` or `machine`. Indicates whether the registry entry is under `HKEY_CURRENT USER` or `HKEY_LOCAL_MACHINE`.
* `scope` string _Windows_ - one of `user` or `machine`. Indicates whether the registry entry is under `HKEY_CURRENT USER` or `HKEY_LOCAL_MACHINE`.
* `enabled` boolean _Windows_ - `true` if the app registry key is startup approved and therefore shows as `enabled` in Task Manager and Windows settings.
### `app.setLoginItemSettings(settings)` _macOS_ _Windows_

View File

@@ -94,7 +94,6 @@
The actual output pixel format and color space of the texture should refer to [`OffscreenSharedTexture`](../structures/offscreen-shared-texture.md) object in the `paint` event.
* `argb` - The requested output texture format is 8-bit unorm RGBA, with SRGB SDR color space.
* `rgbaf16` - The requested output texture format is 16-bit float RGBA, with scRGB HDR color space.
* `nv12` - The requested output texture format is 12bpp with Y plane followed by a 2x2 interleaved UV plane, with REC709 color space.
* `deviceScaleFactor` number (optional) _Experimental_ - The device scale factor of the offscreen rendering output. If not set, will use `1` as default.
* `contextIsolation` boolean (optional) - Whether to run Electron APIs and
the specified `preload` script in a separate JavaScript context. Defaults

View File

@@ -23,6 +23,11 @@ Creates a new touch bar with the specified items. Use
> The TouchBar API is currently experimental and may change or be
> removed in future Electron releases.
> [!TIP]
> If you don't have a MacBook with Touch Bar, you can use
> [Touch Bar Simulator](https://github.com/sindresorhus/touch-bar-simulator)
> to test Touch Bar usage in your app.
### Static Properties
#### `TouchBarButton`

View File

@@ -14,23 +14,6 @@ const DEPS_REGEX = /chromium_version':\n +'(.+?)',/m;
const CL_REGEX = /https:\/\/chromium-review\.googlesource\.com\/c\/(?:chromium\/src|v8\/v8)\/\+\/(\d+)(#\S+)?/g;
const ROLLER_BRANCH_PATTERN = /^roller\/chromium\/(.+)$/;
function getCurrentBranch () {
// In CI, use `GITHUB_HEAD_REF` since we checkout the PR branch in detached HEAD state
if (process.env.GITHUB_HEAD_REF) {
return process.env.GITHUB_HEAD_REF;
}
try {
return execSync('git rev-parse --abbrev-ref HEAD', {
cwd: ELECTRON_DIR,
encoding: 'utf8'
}).trim();
} catch {
console.error('Could not determine current git branch');
process.exit(1);
}
}
function getCommitsSinceMergeBase (mergeBase) {
try {
const output = execSync(`git log --format=%H%n%B%n---COMMIT_END--- ${mergeBase}..HEAD`, {
@@ -109,7 +92,17 @@ async function getGerritPatchDetails (clUrl) {
}
async function main () {
const currentBranch = getCurrentBranch();
let currentBranch;
try {
currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
cwd: ELECTRON_DIR,
encoding: 'utf8'
}).trim();
} catch {
console.error('Could not determine current git branch');
process.exit(1);
}
// Check if we're on a roller/chromium/* branch
const branchMatch = ROLLER_BRANCH_PATTERN.exec(currentBranch);

View File

@@ -4746,19 +4746,6 @@ gin_helper::Handle<WebContents> WebContents::CreateFromWebPreferences(
existing_preferences->SetFromDictionary(web_preferences_dict);
web_contents->SetBackgroundColor(
existing_preferences->GetBackgroundColor());
double zoom_factor;
if (web_preferences.Get(options::kZoomFactor, &zoom_factor)) {
auto* zoom_controller = WebContentsZoomController::FromWebContents(
web_contents->web_contents());
if (zoom_controller) {
zoom_controller->SetDefaultZoomFactor(zoom_factor);
// Also set the current zoom level immediately, since the page
// has already navigated by the time we wrap the webContents.
zoom_controller->SetZoomLevel(
blink::ZoomFactorToZoomLevel(zoom_factor));
}
}
}
} else {
// Create one if not.

View File

@@ -33,7 +33,7 @@ WebContentsView::WebContentsView(v8::Isolate* isolate,
gin_helper::Handle<WebContents> web_contents)
: View(web_contents->inspectable_web_contents()->GetView()),
web_contents_(isolate, web_contents.ToV8()),
api_web_contents_(web_contents->GetWeakPtr()) {
api_web_contents_(web_contents.get()) {
set_delete_view(false);
view()->SetProperty(
views::kFlexBehaviorKey,

View File

@@ -7,7 +7,7 @@
#include <optional>
#include "base/memory/weak_ptr.h"
#include "base/memory/raw_ptr.h"
#include "content/public/browser/web_contents_observer.h"
#include "shell/browser/api/electron_api_view.h"
#include "shell/browser/draggable_region_provider.h"
@@ -63,7 +63,7 @@ class WebContentsView : public View,
// Keep a reference to v8 wrapper.
v8::Global<v8::Value> web_contents_;
base::WeakPtr<api::WebContents> api_web_contents_;
raw_ptr<api::WebContents> api_web_contents_;
};
} // namespace electron::api

View File

@@ -24,8 +24,6 @@ media::VideoPixelFormat GetTargetPixelFormatFromOption(
return media::PIXEL_FORMAT_ARGB;
} else if (pixel_format_option == "rgbaf16") {
return media::PIXEL_FORMAT_RGBAF16;
} else if (pixel_format_option == "nv12") {
return media::PIXEL_FORMAT_NV12;
}
// Use ARGB as default.

View File

@@ -92,13 +92,6 @@ InspectableWebContentsView::InspectableWebContentsView(
}
InspectableWebContentsView::~InspectableWebContentsView() {
if (devtools_window_web_view_)
devtools_window_web_view_->SetWebContents(nullptr);
if (devtools_web_view_)
devtools_web_view_->SetWebContents(nullptr);
if (contents_web_view_)
contents_web_view_->SetWebContents(nullptr);
if (devtools_window_)
inspectable_web_contents()->SaveDevToolsBounds(
devtools_window_->GetWindowBoundsInScreen());

View File

@@ -9,7 +9,6 @@
#include "components/input/native_web_keyboard_event.h"
#include "shell/browser/native_window.h"
#include "shell/browser/ui/views/menu_bar.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/views/layout/box_layout.h"
namespace electron {
@@ -22,21 +21,9 @@ bool IsAltKey(const input::NativeWebKeyboardEvent& event) {
bool IsAltModifier(const input::NativeWebKeyboardEvent& event) {
using Mods = input::NativeWebKeyboardEvent::Modifiers;
// AltGraph (AltGr) should not be treated as a single Alt keypress for
// menu-bar toggling.
if (event.windows_key_code == ui::VKEY_ALTGR ||
ui::KeycodeConverter::DomKeyToKeyString(event.dom_key) == "AltGraph") {
return false;
}
return (event.GetModifiers() & Mods::kKeyModifiers) == Mods::kAltKey;
}
bool IsSingleAltKey(const input::NativeWebKeyboardEvent& event) {
return IsAltKey(event) && IsAltModifier(event);
}
} // namespace
RootView::RootView(NativeWindow* window)
@@ -111,7 +98,7 @@ void RootView::HandleKeyEvent(const input::NativeWebKeyboardEvent& event) {
return;
// Show accelerator when "Alt" is pressed.
if (menu_bar_visible_ && IsSingleAltKey(event))
if (menu_bar_visible_ && IsAltKey(event))
menu_bar_->SetAcceleratorVisibility(
event.GetType() == blink::WebInputEvent::Type::kRawKeyDown);
@@ -134,11 +121,11 @@ void RootView::HandleKeyEvent(const input::NativeWebKeyboardEvent& event) {
// Toggle the menu bar only when a single Alt is released.
if (event.GetType() == blink::WebInputEvent::Type::kRawKeyDown &&
IsSingleAltKey(event)) {
IsAltKey(event)) {
// When a single Alt is pressed:
menu_bar_alt_pressed_ = true;
} else if (event.GetType() == blink::WebInputEvent::Type::kKeyUp &&
IsSingleAltKey(event) && menu_bar_alt_pressed_) {
IsAltKey(event) && menu_bar_alt_pressed_) {
// When a single Alt is released right after a Alt is pressed:
menu_bar_alt_pressed_ = false;
if (menu_bar_autohide_)

View File

@@ -275,16 +275,12 @@ bool WinFrameView::GetShouldPaintAsActive() {
}
gfx::Size WinFrameView::GetMinimumSize() const {
if (!window_)
return gfx::Size();
// Chromium expects minimum size to be in content dimensions on Windows
// because it adds the frame border automatically in OnGetMinMaxInfo.
return window_->GetContentMinimumSize();
}
gfx::Size WinFrameView::GetMaximumSize() const {
if (!window_)
return gfx::Size();
// Chromium expects minimum size to be in content dimensions on Windows
// because it adds the frame border automatically in OnGetMinMaxInfo.
gfx::Size size = window_->GetContentMaximumSize();

View File

@@ -31,8 +31,6 @@ std::string OsrVideoPixelFormatToString(media::VideoPixelFormat format) {
return "rgba";
case media::PIXEL_FORMAT_RGBAF16:
return "rgbaf16";
case media::PIXEL_FORMAT_NV12:
return "nv12";
default:
NOTREACHED();
}

View File

@@ -216,9 +216,9 @@ void ElectronRendererClient::WorkerScriptReadyForEvaluationOnWorkerThread(
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kNodeIntegrationInWorker)) {
auto* current = WebWorkerObserver::GetCurrent();
if (current)
return;
WebWorkerObserver::Create()->WorkerScriptReadyForEvaluation(context);
if (!current)
current = WebWorkerObserver::Create();
current->WorkerScriptReadyForEvaluation(context);
}
}

View File

@@ -10,8 +10,8 @@
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
#include "base/threading/thread_local.h"
#include "gin/converter.h"
#include "shell/common/api/electron_bindings.h"
#include "shell/common/gin_helper/event_emitter_caller.h"
#include "shell/common/node_bindings.h"
#include "shell/common/node_includes.h"
#include "shell/common/node_util.h"
@@ -48,6 +48,21 @@ WebWorkerObserver::~WebWorkerObserver() = default;
void WebWorkerObserver::WorkerScriptReadyForEvaluation(
v8::Local<v8::Context> worker_context) {
active_context_count_++;
if (environments_.empty()) {
// First context on this thread - do full Node.js initialization.
InitializeNewEnvironment(worker_context);
} else {
// Thread is being reused (AudioWorklet thread pooling). Share the
// existing Node.js environment with the new context instead of
// reinitializing, which would break existing contexts on this thread.
ShareEnvironmentWithContext(worker_context);
}
}
void WebWorkerObserver::InitializeNewEnvironment(
v8::Local<v8::Context> worker_context) {
v8::Context::Scope context_scope(worker_context);
v8::Isolate* const isolate = v8::Isolate::GetCurrent();
v8::MicrotasksScope microtasks_scope(
@@ -106,26 +121,160 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
environments_.insert(std::move(env));
}
void WebWorkerObserver::ShareEnvironmentWithContext(
v8::Local<v8::Context> worker_context) {
v8::Context::Scope context_scope(worker_context);
v8::Isolate* const isolate = v8::Isolate::GetCurrent();
v8::MicrotasksScope microtasks_scope(
worker_context, v8::MicrotasksScope::kDoNotRunMicrotasks);
// Get the existing environment from the first context on this thread.
DCHECK(!environments_.empty());
node::Environment* env = environments_.begin()->get();
// Initialize the V8 context for Node.js use.
v8::Maybe<bool> initialized = node::InitializeContext(worker_context);
CHECK(!initialized.IsNothing() && initialized.FromJust());
// Assign the existing Node.js environment to this new context so that
// node::Environment::GetCurrent(context) returns the shared environment.
env->AssignToContext(worker_context, env->principal_realm(),
node::ContextInfo("electron_worker"));
// Get process and require from the original context to make Node.js
// APIs available in the new context.
v8::Local<v8::Context> original_context = env->context();
v8::Local<v8::Object> original_global = original_context->Global();
v8::Local<v8::Object> new_global = worker_context->Global();
v8::Local<v8::Value> process_value;
CHECK(original_global
->Get(original_context, gin::StringToV8(isolate, "process"))
.ToLocal(&process_value));
v8::Local<v8::Value> require_value;
CHECK(original_global
->Get(original_context, gin::StringToV8(isolate, "require"))
.ToLocal(&require_value));
// Set up 'global' as an alias for globalThis. Node.js bootstrapping normally
// does this during LoadEnvironment, but we skip full bootstrap for shared
// contexts.
new_global
->Set(worker_context, gin::StringToV8(isolate, "global"), new_global)
.Check();
new_global
->Set(worker_context, gin::StringToV8(isolate, "process"), process_value)
.Check();
new_global
->Set(worker_context, gin::StringToV8(isolate, "require"), require_value)
.Check();
// Copy Buffer from the original context if it exists.
v8::Local<v8::Value> buffer_value;
if (original_global->Get(original_context, gin::StringToV8(isolate, "Buffer"))
.ToLocal(&buffer_value) &&
!buffer_value->IsUndefined()) {
new_global
->Set(worker_context, gin::StringToV8(isolate, "Buffer"), buffer_value)
.Check();
}
// Restore the Blink implementations of web APIs that Node.js may
// have deleted. For first-context init this is done by the node_init script
// but we can't run that for shared contexts (it calls internalBinding).
// Instead, copy the blink-prefixed values set during first init.
for (const std::string_view key :
{"fetch", "Response", "FormData", "Request", "Headers", "EventSource"}) {
// First, check if the new context has a working Blink version.
v8::MaybeLocal<v8::Value> blink_value =
new_global->Get(worker_context, gin::StringToV8(isolate, key));
if (!blink_value.IsEmpty() && !blink_value.ToLocalChecked()->IsUndefined())
continue;
// If not, copy from the original context.
std::string blink_key = base::StrCat({"blink", key});
v8::Local<v8::Value> orig_value;
if (original_global->Get(original_context, gin::StringToV8(isolate, key))
.ToLocal(&orig_value) &&
!orig_value->IsUndefined()) {
new_global->Set(worker_context, gin::StringToV8(isolate, key), orig_value)
.Check();
}
}
}
void WebWorkerObserver::ContextWillDestroy(v8::Local<v8::Context> context) {
node::Environment* env = node::Environment::GetCurrent(context);
if (env) {
v8::Context::Scope context_scope(env->context());
gin_helper::EmitEvent(env->isolate(), env->process_object(), "exit");
if (!env)
return;
active_context_count_--;
if (active_context_count_ == 0) {
// Last context on this thread — full cleanup.
{
v8::Context::Scope context_scope(env->context());
// Emit the "exit" event on the process object. We avoid using
// gin_helper::EmitEvent here because it goes through
// CallMethodWithArgs, which creates a node::CallbackScope. During
// worker shutdown (PrepareForShutdownOnWorkerThread), the
// CallbackScope destructor's InternalCallbackScope::Close() tries to
// process ticks and microtask checkpoints, which can SEGV because the
// worker context is being torn down by Blink.
v8::Isolate* isolate = env->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> ctx = env->context();
v8::Local<v8::Value> emit_v;
if (env->process_object()
->Get(ctx, gin::StringToV8(isolate, "emit"))
.ToLocal(&emit_v) &&
emit_v->IsFunction()) {
v8::Local<v8::Value> args[] = {gin::StringToV8(isolate, "exit")};
v8::TryCatch try_catch(isolate);
emit_v.As<v8::Function>()
->Call(ctx, env->process_object(), 1, args)
.FromMaybe(v8::Local<v8::Value>());
}
}
// Prevent UvRunOnce from using the environment after it's destroyed.
node_bindings_->set_uv_env(nullptr);
// Destroying the node environment will also run the uv loop.
{
util::ExplicitMicrotasksScope microtasks_scope(
context->GetMicrotaskQueue());
environments_.clear();
}
// ElectronBindings is tracking node environments.
electron_bindings_->EnvironmentDestroyed(env);
// Do NOT destroy the observer here. The worker thread may be reused
// for another worklet context (e.g., AudioWorklet thread pooling or
// PaintWorklet after page reload). Destroying the observer would
// force a new NodeBindings to be created, but Node.js cannot be
// fully reinitialized on the same thread (the allocator shim can
// only be loaded once). Instead, keep the observer and its
// NodeBindings alive so they can be reused. The environments_ set
// is now empty, so WorkerScriptReadyForEvaluation will call
// InitializeNewEnvironment on the next context.
} else {
// Other contexts still use the shared environment. Just unassign
// this context from the environment if it's not the primary context
// (the primary context must stay assigned because env->context()
// references it, and UvRunOnce enters that context scope).
if (context != env->context()) {
env->UnassignFromContext(context);
}
// If the destroyed context IS the primary context, we leave the env
// assigned to it. The env's PrincipalRealm holds a Global<Context>
// reference that keeps the V8 context alive even though Blink has
// torn down its side. This is safe because UvRunOnce only needs
// the V8 context scope, not Blink-side objects.
}
// Destroying the node environment will also run the uv loop.
{
util::ExplicitMicrotasksScope microtasks_scope(
context->GetMicrotaskQueue());
base::EraseIf(environments_,
[env](auto const& item) { return item.get() == env; });
}
// ElectronBindings is tracking node environments.
electron_bindings_->EnvironmentDestroyed(env);
if (lazy_tls->Get())
lazy_tls->Set(nullptr);
}
} // namespace electron

View File

@@ -40,9 +40,17 @@ class WebWorkerObserver {
void ContextWillDestroy(v8::Local<v8::Context> context);
private:
// Full initialization for the first context on a thread.
void InitializeNewEnvironment(v8::Local<v8::Context> context);
// Share existing environment with a new context on a reused thread.
void ShareEnvironmentWithContext(v8::Local<v8::Context> context);
std::unique_ptr<NodeBindings> node_bindings_;
std::unique_ptr<ElectronBindings> electron_bindings_;
base::flat_set<std::shared_ptr<node::Environment>> environments_;
// Number of active contexts using the environment on this thread.
size_t active_context_count_ = 0;
};
} // namespace electron

View File

@@ -3977,28 +3977,6 @@ describe('BrowserWindow module', () => {
expect(webPreferences!.contextIsolation).to.equal(false);
});
it('should apply zoomFactor from setWindowOpenHandler overrideBrowserWindowOptions', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true
}
});
w.webContents.setWindowOpenHandler(() => ({
action: 'allow',
overrideBrowserWindowOptions: {
webPreferences: {
zoomFactor: 2.0
}
}
}));
w.loadFile(path.join(fixtures, 'api', 'new-window.html'));
const [childWindow] = await once(w.webContents, 'did-create-window') as [BrowserWindow, any];
await once(childWindow.webContents, 'did-finish-load');
expect(childWindow.webContents.getZoomFactor()).to.be.closeTo(2.0, 0.1);
});
it('should set ipc event sender correctly', async () => {
const w = new BrowserWindow({
show: false,
@@ -5925,23 +5903,6 @@ describe('BrowserWindow module', () => {
});
});
ifdescribe(process.platform === 'linux')('menu bar AltGr behavior', () => {
it('does not toggle auto-hide menu bar visibility', async () => {
const w = new BrowserWindow({ show: false, autoHideMenuBar: true });
w.setMenuBarVisibility(false);
expect(w.isMenuBarVisible()).to.be.false('isMenuBarVisible');
w.show();
await once(w, 'show');
w.webContents.focus();
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'AltGr' });
w.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'AltGr' });
await setTimeout();
expect(w.isMenuBarVisible()).to.be.false('isMenuBarVisible');
});
});
ifdescribe(process.platform !== 'darwin')('when fullscreen state is changed', () => {
it('correctly remembers state prior to fullscreen change', async () => {
const w = new BrowserWindow({ show: false });

View File

@@ -59,11 +59,10 @@ describe('WebContentsView', () => {
const browserWindow = new BrowserWindow();
const webContentsView = new WebContentsView();
const wc = webContentsView.webContents;
wc.loadURL('about:blank');
wc.destroy();
webContentsView.webContents.loadURL('about:blank');
webContentsView.webContents.destroy();
const destroyed = once(wc, 'destroyed');
const destroyed = once(webContentsView.webContents, 'destroyed');
await destroyed;
expect(() => browserWindow.contentView.addChildView(webContentsView)).to.throw(
'Can\'t add a destroyed child view to a parent view'
@@ -91,14 +90,13 @@ describe('WebContentsView', () => {
const w = new BaseWindow({ show: false });
const v = new View();
const wcv = new WebContentsView();
const wc = wcv.webContents;
w.setContentView(v);
v.addChildView(wcv);
await wc.loadURL('about:blank');
const destroyed = once(wc, 'destroyed');
wc.executeJavaScript('window.close()');
await wcv.webContents.loadURL('about:blank');
const destroyed = once(wcv.webContents, 'destroyed');
wcv.webContents.executeJavaScript('window.close()');
await destroyed;
expect(wc.isDestroyed()).to.be.true();
expect(wcv.webContents.isDestroyed()).to.be.true();
v.removeChildView(wcv);
});
@@ -172,19 +170,18 @@ describe('WebContentsView', () => {
it('does not crash when closed via window.close()', async () => {
const bw = new BrowserWindow();
const wcv = new WebContentsView();
const wc = wcv.webContents;
await bw.loadURL('data:text/html,<h1>Main Window</h1>');
bw.contentView.addChildView(wcv);
const dto = new Promise<boolean>((resolve) => {
wc.on('blur', () => {
const devToolsOpen = !wc.isDestroyed() && wc.isDevToolsOpened();
wcv.webContents.on('blur', () => {
const devToolsOpen = wcv.webContents.isDevToolsOpened();
resolve(devToolsOpen);
});
});
wc.loadURL('data:text/html,<script>window.close()</script>');
wcv.webContents.loadURL('data:text/html,<script>window.close()</script>');
const open = await dto;
expect(open).to.be.false();