mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
4 Commits
remove-dec
...
fix-audio-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d85aee8251 | ||
|
|
f39ba782d2 | ||
|
|
7273708ab4 | ||
|
|
2b633ebe40 |
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@@ -442,7 +442,34 @@ jobs:
|
||||
contents: read
|
||||
needs: [docs-only, macos-x64, macos-arm64, linux-x64, linux-x64-asan, linux-arm, linux-arm64, windows-x64, windows-x86, windows-arm64]
|
||||
if: always() && github.repository == 'electron/electron' && !contains(needs.*.result, 'failure')
|
||||
steps:
|
||||
steps:
|
||||
- name: GitHub Actions Jobs Done
|
||||
run: |
|
||||
echo "All GitHub Actions Jobs are done"
|
||||
|
||||
check-signed-commits:
|
||||
name: Check signed commits in green PR
|
||||
needs: gha-done
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check signed commits in PR
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
|
||||
- name: Remove needs-signed-commits label
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --remove-label needs-signed-commits
|
||||
|
||||
@@ -13,6 +13,7 @@ permissions: {}
|
||||
jobs:
|
||||
check-signed-commits:
|
||||
name: Check signed commits in PR
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -22,9 +23,9 @@ jobs:
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
@@ -36,11 +37,3 @@ jobs:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --add-label needs-signed-commits
|
||||
|
||||
- name: Remove needs-signed-commits label
|
||||
if: ${{ success() && contains(github.event.pull_request.labels.*.name, 'needs-signed-commits') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --remove-label needs-signed-commits
|
||||
|
||||
@@ -11,6 +11,13 @@ app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
function decorateURL (url: string) {
|
||||
// safely add `?utm_source=default_app
|
||||
const parsedUrl = new URL(url);
|
||||
parsedUrl.searchParams.append('utm_source', 'default_app');
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
// Find the shortest path to the electron binary
|
||||
const absoluteElectronPath = process.execPath;
|
||||
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath);
|
||||
@@ -62,7 +69,7 @@ async function createWindow (backgroundColor?: string) {
|
||||
mainWindow.on('ready-to-show', () => mainWindow!.show());
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler(details => {
|
||||
shell.openExternal(details.url);
|
||||
shell.openExternal(decorateURL(details.url));
|
||||
return { action: 'deny' };
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/stream-json": "^1.7.8",
|
||||
"@types/temp": "^0.9.4",
|
||||
"@xmldom/xmldom": "^0.8.12",
|
||||
"@xmldom/xmldom": "^0.8.11",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.1.0",
|
||||
"check-for-leaks": "^1.2.1",
|
||||
|
||||
@@ -125,6 +125,7 @@ feat_separate_content_settings_callback_for_sync_and_async_clipboard.patch
|
||||
fix_win32_synchronous_spellcheck.patch
|
||||
chore_grandfather_in_electron_views_and_delegates.patch
|
||||
refactor_patch_electron_permissiontypes_into_blink.patch
|
||||
revert_views_remove_desktopwindowtreehostwin_window_enlargement.patch
|
||||
fix_add_macos_memory_query_fallback_to_avoid_crash.patch
|
||||
fix_resolve_dynamic_background_material_update_issue_on_windows_11.patch
|
||||
feat_add_support_for_embedder_snapshot_validation.patch
|
||||
|
||||
@@ -34,6 +34,48 @@ index 7ea6daec53a497bf867d799e041bf6ae7191ef7b..15940624940d5c629c40319f45c59282
|
||||
agent_group_scheduler_compositor_task_runner =
|
||||
execution_context->GetScheduler()
|
||||
->ToFrameScheduler()
|
||||
diff --git a/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc b/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc
|
||||
index 936f5ebe28caa993ed5de0f7de3613fa338e263f..961ac8091aa82128e1cfb8800a7efcb80d100a05 100644
|
||||
--- a/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc
|
||||
+++ b/third_party/blink/renderer/core/workers/threaded_worklet_messaging_proxy.cc
|
||||
@@ -13,10 +13,12 @@
|
||||
#include "third_party/blink/public/platform/task_type.h"
|
||||
#include "third_party/blink/renderer/core/dom/document.h"
|
||||
#include "third_party/blink/renderer/core/execution_context/security_context.h"
|
||||
+#include "third_party/blink/renderer/core/exported/web_view_impl.h"
|
||||
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
|
||||
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
|
||||
#include "third_party/blink/renderer/core/frame/local_frame.h"
|
||||
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
|
||||
+#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
|
||||
#include "third_party/blink/renderer/core/inspector/thread_debugger_common_impl.h"
|
||||
#include "third_party/blink/renderer/core/loader/worker_fetch_context.h"
|
||||
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
|
||||
@@ -135,6 +137,14 @@ void ThreadedWorkletMessagingProxy::Initialize(
|
||||
DCHECK(csp);
|
||||
|
||||
LocalFrameClient* frame_client = window->GetFrame()->Client();
|
||||
+ auto worklet_settings =
|
||||
+ std::make_unique<WorkerSettings>(window->GetFrame()->GetSettings());
|
||||
+ if (auto* web_local_frame = WebLocalFrameImpl::FromFrame(window->GetFrame())) {
|
||||
+ if (auto* web_view = web_local_frame->ViewImpl()) {
|
||||
+ worklet_settings->SetNodeIntegrationInWorker(
|
||||
+ web_view->GetWebPreferences().node_integration_in_worker);
|
||||
+ }
|
||||
+ }
|
||||
auto global_scope_creation_params =
|
||||
std::make_unique<GlobalScopeCreationParams>(
|
||||
window->Url(), mojom::blink::ScriptType::kModule, global_scope_name,
|
||||
@@ -147,8 +157,7 @@ void ThreadedWorkletMessagingProxy::Initialize(
|
||||
window->GetHttpsState(), worker_clients,
|
||||
frame_client->CreateWorkerContentSettingsClient(),
|
||||
OriginTrialContext::GetInheritedTrialFeatures(window).get(),
|
||||
- base::UnguessableToken::Create(),
|
||||
- std::make_unique<WorkerSettings>(window->GetFrame()->GetSettings()),
|
||||
+ base::UnguessableToken::Create(), std::move(worklet_settings),
|
||||
mojom::blink::V8CacheOptions::kDefault, module_responses_map,
|
||||
mojo::NullRemote() /* browser_interface_broker */,
|
||||
window->GetFrame()->Loader().CreateWorkerCodeCacheHost(),
|
||||
diff --git a/third_party/blink/renderer/core/workers/worker_settings.cc b/third_party/blink/renderer/core/workers/worker_settings.cc
|
||||
index 45680c5f6ea0c7e89ccf43eb88f8a11e3318c02e..3fa3af62f4e7ba8186441c5e3184b1c04fe32d12 100644
|
||||
--- a/third_party/blink/renderer/core/workers/worker_settings.cc
|
||||
@@ -71,3 +113,56 @@ index 45c60dd2c44b05fdd279f759069383479823c7f2..33a2a0337efb9a46293e11d0d09b3fc1
|
||||
|
||||
GenericFontFamilySettings generic_font_family_settings_;
|
||||
};
|
||||
diff --git a/third_party/blink/renderer/core/workers/worklet_global_scope.cc b/third_party/blink/renderer/core/workers/worklet_global_scope.cc
|
||||
index b5300dea97f20d72a807543a6da0baf61d21955f..a7030c1ba6851b26c765c7b05cd26e1453866719 100644
|
||||
--- a/third_party/blink/renderer/core/workers/worklet_global_scope.cc
|
||||
+++ b/third_party/blink/renderer/core/workers/worklet_global_scope.cc
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "third_party/blink/renderer/core/script/modulator.h"
|
||||
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
|
||||
#include "third_party/blink/renderer/core/workers/worker_reporting_proxy.h"
|
||||
+#include "third_party/blink/renderer/core/workers/worker_settings.h"
|
||||
#include "third_party/blink/renderer/core/workers/worker_thread.h"
|
||||
#include "third_party/blink/renderer/core/workers/worklet_module_responses_map.h"
|
||||
#include "third_party/blink/renderer/core/workers/worklet_module_tree_client.h"
|
||||
@@ -110,6 +111,10 @@ WorkletGlobalScope::WorkletGlobalScope(
|
||||
parent_cross_origin_isolated_capability_(
|
||||
creation_params->cross_origin_isolated_capability),
|
||||
parent_is_isolated_context_(creation_params->parent_is_isolated_context),
|
||||
+ node_integration_in_worker_(
|
||||
+ creation_params->worker_settings
|
||||
+ ? creation_params->worker_settings->NodeIntegrationInWorker()
|
||||
+ : false),
|
||||
browser_interface_broker_proxy_(this) {
|
||||
DCHECK((thread_type_ == ThreadType::kMainThread && frame_) ||
|
||||
(thread_type_ == ThreadType::kOffMainThread && worker_thread_));
|
||||
diff --git a/third_party/blink/renderer/core/workers/worklet_global_scope.h b/third_party/blink/renderer/core/workers/worklet_global_scope.h
|
||||
index c7dd62900f0de48ab992a7c99058f5b6d98212cf..47ceea11ec9db6b67cef6945d165f46c868f4ca5 100644
|
||||
--- a/third_party/blink/renderer/core/workers/worklet_global_scope.h
|
||||
+++ b/third_party/blink/renderer/core/workers/worklet_global_scope.h
|
||||
@@ -140,6 +140,13 @@ class CORE_EXPORT WorkletGlobalScope : public WorkerOrWorkletGlobalScope {
|
||||
// Returns the WorkletToken that uniquely identifies this worklet.
|
||||
virtual WorkletToken GetWorkletToken() const = 0;
|
||||
|
||||
+ // Electron: returns whether the creator frame had the
|
||||
+ // `nodeIntegrationInWorker` web preference enabled. Copied from
|
||||
+ // GlobalScopeCreationParams::worker_settings at construction time so the
|
||||
+ // value is readable on the worker thread without crossing back to the
|
||||
+ // main thread.
|
||||
+ bool NodeIntegrationInWorker() const { return node_integration_in_worker_; }
|
||||
+
|
||||
// Returns the ExecutionContextToken that uniquely identifies the parent
|
||||
// context that created this worklet. Note that this will always be a
|
||||
// LocalFrameToken.
|
||||
@@ -207,6 +214,11 @@ class CORE_EXPORT WorkletGlobalScope : public WorkerOrWorkletGlobalScope {
|
||||
// TODO(crbug.com/1206150): We need a spec for this capability.
|
||||
const bool parent_is_isolated_context_;
|
||||
|
||||
+ // Electron: snapshot of the creator frame's nodeIntegrationInWorker
|
||||
+ // WebPreference, copied out of GlobalScopeCreationParams::worker_settings
|
||||
+ // at construction time.
|
||||
+ const bool node_integration_in_worker_;
|
||||
+
|
||||
// This is the interface that handles generated code cache
|
||||
// requests both to fetch code cache when loading resources
|
||||
// and to store generated code cache to disk.
|
||||
|
||||
@@ -8,10 +8,10 @@ such as the background turning black when maximizing the window and
|
||||
dynamic background material settings not taking effect.
|
||||
|
||||
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
index e4da40256ce94d6a0896792a8ef2faa18e1fa5d2..3a5833fcc018f32e86c0a95a42937fb9ac6c5a40 100644
|
||||
index d1e06b675b19226cf3b78e1aada8d8f2d684fada..ce810555b8501797643987916a728cad8f5adaa5 100644
|
||||
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
@@ -167,6 +167,10 @@ void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
|
||||
@@ -184,6 +184,10 @@ void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ index e4da40256ce94d6a0896792a8ef2faa18e1fa5d2..3a5833fcc018f32e86c0a95a42937fb9
|
||||
|
||||
void DesktopWindowTreeHostWin::Init(const Widget::InitParams& params) {
|
||||
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
index 27322ef34edf3fa8bfbd20b1baddcaf3b7555618..b8d1fa863fd05ebc3ab8ac5ef8c4d81361ce45fe 100644
|
||||
index a40bd9f25fa07a553c011cf19f155f8158f4ae5f..ae2baec731b5fcd8be97f2177d23b860d67ab8bc 100644
|
||||
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
@@ -93,6 +93,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: John Kleinschmidt <jkleinsc@electronjs.org>
|
||||
Date: Sat, 14 Jun 2025 16:21:07 -0400
|
||||
Subject: Revert "[views] Remove DesktopWindowTreeHostWin::window_enlargement_"
|
||||
|
||||
This reverts commit 1771dbae6961e7bb7c22bbc6c77f84d90ef2be46.
|
||||
|
||||
Electron needs this patch to allow windows smaller than 64x64
|
||||
on Windows. We should refactor our code so that this patch isn't
|
||||
necessary.
|
||||
|
||||
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
|
||||
index 988866d79a5d1dbd366ebdbff0e8eb2c0c498168..5761ac48be0a64618be0a94606149dd944e46e27 100644
|
||||
--- a/testing/variations/fieldtrial_testing_config.json
|
||||
+++ b/testing/variations/fieldtrial_testing_config.json
|
||||
@@ -22626,6 +22626,21 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
+ "TransparentHwndEnlargement": [
|
||||
+ {
|
||||
+ "platforms": [
|
||||
+ "windows"
|
||||
+ ],
|
||||
+ "experiments": [
|
||||
+ {
|
||||
+ "name": "DisableTransparentHwndEnlargement",
|
||||
+ "disable_features": [
|
||||
+ "EnableTransparentHwndEnlargement"
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ],
|
||||
"TransportSecurityFileWriterScheduleAndroid": [
|
||||
{
|
||||
"platforms": [
|
||||
diff --git a/ui/views/views_features.cc b/ui/views/views_features.cc
|
||||
index 47077e16870889ef8f8c8b2adf58015bd5aff7fa..ba59e6e1609e61579bf49aca095490b083d72051 100644
|
||||
--- a/ui/views/views_features.cc
|
||||
+++ b/ui/views/views_features.cc
|
||||
@@ -30,6 +30,14 @@ BASE_FEATURE(kEnableInputProtection, base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
// crbug.com/370856871.
|
||||
BASE_FEATURE(kEnableTouchDragCursorSync, base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
|
||||
+// Enables enlargement of HWNDs to a minimum size of 64x64 to handle reported
|
||||
+// graphical glitches on certain hardware.
|
||||
+// TODO(crbug.com/401996981): Remove this once enlargement is confirmed to no
|
||||
+// longer be needed.
|
||||
+BASE_FEATURE(kEnableTransparentHwndEnlargement,
|
||||
+ "EnableTransparentHwndEnlargement",
|
||||
+ base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
+
|
||||
// Used to enable keyboard-accessible tooltips in Views UI, as opposed
|
||||
// to kKeyboardAccessibleTooltip in //ui/base/ui_base_features.cc.
|
||||
BASE_FEATURE(kKeyboardAccessibleTooltipInViews,
|
||||
diff --git a/ui/views/views_features.h b/ui/views/views_features.h
|
||||
index 888a16fb6213eceb131ae636dc643d7f2d5bcad9..6077412165081cd4abeaf0b061feb2f795ee8131 100644
|
||||
--- a/ui/views/views_features.h
|
||||
+++ b/ui/views/views_features.h
|
||||
@@ -15,6 +15,7 @@ namespace views::features {
|
||||
VIEWS_EXPORT BASE_DECLARE_FEATURE(kApplyInitialUrlToWebContents);
|
||||
VIEWS_EXPORT BASE_DECLARE_FEATURE(kEnableInputProtection);
|
||||
VIEWS_EXPORT BASE_DECLARE_FEATURE(kEnableTouchDragCursorSync);
|
||||
+VIEWS_EXPORT BASE_DECLARE_FEATURE(kEnableTransparentHwndEnlargement);
|
||||
VIEWS_EXPORT BASE_DECLARE_FEATURE(kKeyboardAccessibleTooltipInViews);
|
||||
|
||||
} // namespace views::features
|
||||
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
index e4da40256ce94d6a0896792a8ef2faa18e1fa5d2..d1e06b675b19226cf3b78e1aada8d8f2d684fada 100644
|
||||
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
@@ -85,6 +85,23 @@ namespace {
|
||||
// This constant controls how many pixels wide that border is.
|
||||
const int kMouseCaptureRegionBorder = 5;
|
||||
|
||||
+gfx::Size GetExpandedWindowSize(bool is_translucent, gfx::Size size) {
|
||||
+ if (!base::FeatureList::IsEnabled(
|
||||
+ features::kEnableTransparentHwndEnlargement) ||
|
||||
+ !is_translucent) {
|
||||
+ return size;
|
||||
+ }
|
||||
+
|
||||
+ // Some AMD drivers can't display windows that are less than 64x64 pixels,
|
||||
+ // so expand them to be at least that size. http://crbug.com/286609
|
||||
+ gfx::Size expanded(std::max(size.width(), 64), std::max(size.height(), 64));
|
||||
+ return expanded;
|
||||
+}
|
||||
+
|
||||
+void InsetBottomRight(gfx::Rect* rect, const gfx::Vector2d& vector) {
|
||||
+ rect->Inset(gfx::Insets::TLBR(0, 0, vector.y(), vector.x()));
|
||||
+}
|
||||
+
|
||||
// Updates the cursor clip region. Used for mouse locking.
|
||||
void UpdateMouseLockRegion(aura::Window* window, bool locked) {
|
||||
if (!locked) {
|
||||
@@ -342,9 +359,14 @@ bool DesktopWindowTreeHostWin::IsVisible() const {
|
||||
}
|
||||
|
||||
void DesktopWindowTreeHostWin::SetSize(const gfx::Size& size) {
|
||||
- const gfx::Size size_in_pixels =
|
||||
+ gfx::Size size_in_pixels =
|
||||
display::win::GetScreenWin()->DIPToScreenSize(GetHWND(), size);
|
||||
- message_handler_->SetSize(size_in_pixels);
|
||||
+ gfx::Size expanded =
|
||||
+ GetExpandedWindowSize(message_handler_->is_translucent(), size_in_pixels);
|
||||
+ window_enlargement_ =
|
||||
+ gfx::Vector2d(expanded.width() - size_in_pixels.width(),
|
||||
+ expanded.height() - size_in_pixels.height());
|
||||
+ message_handler_->SetSize(expanded);
|
||||
}
|
||||
|
||||
void DesktopWindowTreeHostWin::StackAbove(aura::Window* window) {
|
||||
@@ -359,30 +381,40 @@ void DesktopWindowTreeHostWin::StackAtTop() {
|
||||
}
|
||||
|
||||
void DesktopWindowTreeHostWin::CenterWindow(const gfx::Size& size) {
|
||||
- const gfx::Size size_in_pixels =
|
||||
+ gfx::Size size_in_pixels =
|
||||
display::win::GetScreenWin()->DIPToScreenSize(GetHWND(), size);
|
||||
- message_handler_->CenterWindow(size_in_pixels);
|
||||
+ gfx::Size expanded_size;
|
||||
+ expanded_size =
|
||||
+ GetExpandedWindowSize(message_handler_->is_translucent(), size_in_pixels);
|
||||
+ window_enlargement_ =
|
||||
+ gfx::Vector2d(expanded_size.width() - size_in_pixels.width(),
|
||||
+ expanded_size.height() - size_in_pixels.height());
|
||||
+ message_handler_->CenterWindow(expanded_size);
|
||||
}
|
||||
|
||||
void DesktopWindowTreeHostWin::GetWindowPlacement(
|
||||
gfx::Rect* bounds,
|
||||
ui::mojom::WindowShowState* show_state) const {
|
||||
message_handler_->GetWindowPlacement(bounds, show_state);
|
||||
+ InsetBottomRight(bounds, window_enlargement_);
|
||||
*bounds = display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), *bounds);
|
||||
}
|
||||
|
||||
gfx::Rect DesktopWindowTreeHostWin::GetWindowBoundsInScreen() const {
|
||||
gfx::Rect pixel_bounds = message_handler_->GetWindowBoundsInScreen();
|
||||
+ InsetBottomRight(&pixel_bounds, window_enlargement_);
|
||||
return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
|
||||
}
|
||||
|
||||
gfx::Rect DesktopWindowTreeHostWin::GetClientAreaBoundsInScreen() const {
|
||||
gfx::Rect pixel_bounds = message_handler_->GetClientAreaBoundsInScreen();
|
||||
+ InsetBottomRight(&pixel_bounds, window_enlargement_);
|
||||
return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
|
||||
}
|
||||
|
||||
gfx::Rect DesktopWindowTreeHostWin::GetRestoredBounds() const {
|
||||
gfx::Rect pixel_bounds = message_handler_->GetRestoredBounds();
|
||||
+ InsetBottomRight(&pixel_bounds, window_enlargement_);
|
||||
return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
|
||||
}
|
||||
|
||||
@@ -701,37 +733,44 @@ void DesktopWindowTreeHostWin::HideImpl() {
|
||||
// other get/set methods work in DIP.
|
||||
|
||||
gfx::Rect DesktopWindowTreeHostWin::GetBoundsInPixels() const {
|
||||
- const gfx::Rect bounds_px(message_handler_->GetClientAreaBounds());
|
||||
+ gfx::Rect bounds(message_handler_->GetClientAreaBounds());
|
||||
// If the window bounds were expanded we need to return the original bounds
|
||||
// To achieve this we do the reverse of the expansion, i.e. add the
|
||||
// window_expansion_top_left_delta_ to the origin and subtract the
|
||||
// window_expansion_bottom_right_delta_ from the width and height.
|
||||
- const gfx::Rect without_expansion_bounds_px(
|
||||
- bounds_px.x() + window_expansion_top_left_delta_.x(),
|
||||
- bounds_px.y() + window_expansion_top_left_delta_.y(),
|
||||
- bounds_px.width() - window_expansion_bottom_right_delta_.x(),
|
||||
- bounds_px.height() - window_expansion_bottom_right_delta_.y());
|
||||
- return without_expansion_bounds_px;
|
||||
+ gfx::Rect without_expansion(
|
||||
+ bounds.x() + window_expansion_top_left_delta_.x(),
|
||||
+ bounds.y() + window_expansion_top_left_delta_.y(),
|
||||
+ bounds.width() - window_expansion_bottom_right_delta_.x() -
|
||||
+ window_enlargement_.x(),
|
||||
+ bounds.height() - window_expansion_bottom_right_delta_.y() -
|
||||
+ window_enlargement_.y());
|
||||
+ return without_expansion;
|
||||
}
|
||||
|
||||
-void DesktopWindowTreeHostWin::SetBoundsInPixels(
|
||||
- const gfx::Rect& bounds_in_pixels) {
|
||||
+void DesktopWindowTreeHostWin::SetBoundsInPixels(const gfx::Rect& bounds) {
|
||||
// If the window bounds have to be expanded we need to subtract the
|
||||
// window_expansion_top_left_delta_ from the origin and add the
|
||||
// window_expansion_bottom_right_delta_ to the width and height
|
||||
- const gfx::Size old_content_size_px = GetBoundsInPixels().size();
|
||||
-
|
||||
- const gfx::Rect expanded_bounds_px(
|
||||
- bounds_in_pixels.x() - window_expansion_top_left_delta_.x(),
|
||||
- bounds_in_pixels.y() - window_expansion_top_left_delta_.y(),
|
||||
- bounds_in_pixels.width() + window_expansion_bottom_right_delta_.x(),
|
||||
- bounds_in_pixels.height() + window_expansion_bottom_right_delta_.y());
|
||||
-
|
||||
- // When `expanded_bounds_px` causes the window to be moved to a display with a
|
||||
+ gfx::Size old_content_size = GetBoundsInPixels().size();
|
||||
+
|
||||
+ gfx::Rect expanded(
|
||||
+ bounds.x() - window_expansion_top_left_delta_.x(),
|
||||
+ bounds.y() - window_expansion_top_left_delta_.y(),
|
||||
+ bounds.width() + window_expansion_bottom_right_delta_.x(),
|
||||
+ bounds.height() + window_expansion_bottom_right_delta_.y());
|
||||
+
|
||||
+ gfx::Rect new_expanded(
|
||||
+ expanded.origin(),
|
||||
+ GetExpandedWindowSize(message_handler_->is_translucent(),
|
||||
+ expanded.size()));
|
||||
+ window_enlargement_ =
|
||||
+ gfx::Vector2d(new_expanded.width() - expanded.width(),
|
||||
+ new_expanded.height() - expanded.height());
|
||||
+ // When |new_expanded| causes the window to be moved to a display with a
|
||||
// different DSF, HWNDMessageHandler::OnDpiChanged() will be called and the
|
||||
// window size will be scaled automatically.
|
||||
- message_handler_->SetBounds(expanded_bounds_px,
|
||||
- old_content_size_px != bounds_in_pixels.size());
|
||||
+ message_handler_->SetBounds(new_expanded, old_content_size != bounds.size());
|
||||
}
|
||||
|
||||
gfx::Rect
|
||||
@@ -943,18 +982,26 @@ int DesktopWindowTreeHostWin::GetNonClientComponent(
|
||||
|
||||
void DesktopWindowTreeHostWin::GetWindowMask(const gfx::Size& size_px,
|
||||
SkPath* path) {
|
||||
- Widget* widget = GetWidget();
|
||||
- if (!widget || !widget->non_client_view()) {
|
||||
- return;
|
||||
- }
|
||||
+ // Request the window mask for hwnd of `size_px`. The hwnd size must be
|
||||
+ // adjusted by `window_enlargement` to return to the client-expected window
|
||||
+ // size (see crbug.com/41047830).
|
||||
+ const gfx::Size adjusted_size_in_px =
|
||||
+ size_px - gfx::Size(window_enlargement_.x(), window_enlargement_.y());
|
||||
|
||||
- widget->non_client_view()->GetWindowMask(
|
||||
- display::win::GetScreenWin()->ScreenToDIPSize(GetHWND(), size_px), path);
|
||||
- // Convert path in DIPs to pixels.
|
||||
- if (!path->isEmpty()) {
|
||||
- const float scale =
|
||||
- display::win::GetScreenWin()->GetScaleFactorForHWND(GetHWND());
|
||||
- *path = path->makeTransform(SkMatrix::Scale(scale, scale));
|
||||
+ if (Widget* widget = GetWidget(); widget && widget->non_client_view()) {
|
||||
+ widget->non_client_view()->GetWindowMask(
|
||||
+ display::win::GetScreenWin()->ScreenToDIPSize(GetHWND(),
|
||||
+ adjusted_size_in_px),
|
||||
+ path);
|
||||
+ // Convert path in DIPs to pixels.
|
||||
+ if (!path->isEmpty()) {
|
||||
+ const float scale =
|
||||
+ display::win::GetScreenWin()->GetScaleFactorForHWND(GetHWND());
|
||||
+ *path = path->makeTransform(SkMatrix::Scale(scale, scale));
|
||||
+ }
|
||||
+ } else if (!window_enlargement_.IsZero()) {
|
||||
+ *path = SkPath::Rect(SkRect::MakeXYWH(0, 0, adjusted_size_in_px.width(),
|
||||
+ adjusted_size_in_px.height()));
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
index 27322ef34edf3fa8bfbd20b1baddcaf3b7555618..a40bd9f25fa07a553c011cf19f155f8158f4ae5f 100644
|
||||
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
@@ -175,7 +175,7 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
|
||||
void ShowImpl() override;
|
||||
void HideImpl() override;
|
||||
gfx::Rect GetBoundsInPixels() const override;
|
||||
- void SetBoundsInPixels(const gfx::Rect& bounds_in_pixels) override;
|
||||
+ void SetBoundsInPixels(const gfx::Rect& bounds) override;
|
||||
gfx::Rect GetBoundsInAcceleratedWidgetPixelCoordinates() override;
|
||||
gfx::Point GetLocationOnScreenInPixels() const override;
|
||||
void SetCapture() override;
|
||||
@@ -330,6 +330,12 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
|
||||
gfx::Vector2d window_expansion_top_left_delta_;
|
||||
gfx::Vector2d window_expansion_bottom_right_delta_;
|
||||
|
||||
+ // Windows are enlarged to be at least 64x64 pixels, so keep track of the
|
||||
+ // extra added here.
|
||||
+ // TODO(crbug.com/401996981): This is likely no longer necessary and should be
|
||||
+ // removed.
|
||||
+ gfx::Vector2d window_enlargement_;
|
||||
+
|
||||
// Whether the window close should be converted to a hide, and then actually
|
||||
// closed on the completion of the hide animation. This is cached because
|
||||
// the property is set on the contained window which has a shorter lifetime.
|
||||
@@ -8,9 +8,7 @@ from pathlib import Path
|
||||
|
||||
SRC_DIR = Path(__file__).resolve().parents[3]
|
||||
sys.path.append(os.path.join(SRC_DIR, 'third_party/electron_node/tools'))
|
||||
sys.path.append(str(Path(__file__).resolve().parents[1])) # electron/script/
|
||||
|
||||
from lib.util import get_out_dir
|
||||
import install
|
||||
|
||||
class LoadPythonDictionaryError(Exception):
|
||||
@@ -33,6 +31,13 @@ def LoadPythonDictionary(path):
|
||||
)
|
||||
return file_data
|
||||
|
||||
def get_out_dir():
|
||||
out_dir = 'Testing'
|
||||
override = os.environ.get('ELECTRON_OUT_DIR')
|
||||
if override is not None:
|
||||
out_dir = override
|
||||
return os.path.join(SRC_DIR, 'out', out_dir)
|
||||
|
||||
if __name__ == '__main__':
|
||||
node_root_dir = os.path.join(SRC_DIR, 'third_party/electron_node')
|
||||
out = {}
|
||||
|
||||
@@ -2889,8 +2889,8 @@ v8::Local<v8::Promise> WebContents::SavePage(
|
||||
return handle;
|
||||
}
|
||||
|
||||
auto* handler = new SavePageHandler{std::move(promise)};
|
||||
handler->Handle(full_file_path, save_type, web_contents());
|
||||
auto* handler = new SavePageHandler(web_contents(), std::move(promise));
|
||||
handler->Handle(full_file_path, save_type);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
|
||||
namespace electron::api {
|
||||
|
||||
SavePageHandler::SavePageHandler(gin_helper::Promise<void> promise)
|
||||
: promise_{std::move(promise)} {}
|
||||
SavePageHandler::SavePageHandler(content::WebContents* web_contents,
|
||||
gin_helper::Promise<void> promise)
|
||||
: web_contents_(web_contents), promise_(std::move(promise)) {}
|
||||
|
||||
SavePageHandler::~SavePageHandler() = default;
|
||||
|
||||
@@ -25,10 +26,9 @@ void SavePageHandler::OnDownloadCreated(content::DownloadManager* manager,
|
||||
}
|
||||
|
||||
bool SavePageHandler::Handle(const base::FilePath& full_path,
|
||||
const content::SavePageType& save_type,
|
||||
content::WebContents* web_contents) {
|
||||
const content::SavePageType& save_type) {
|
||||
auto* download_manager =
|
||||
web_contents->GetBrowserContext()->GetDownloadManager();
|
||||
web_contents_->GetBrowserContext()->GetDownloadManager();
|
||||
download_manager->AddObserver(this);
|
||||
// Chromium will create a 'foo_files' directory under the directory of saving
|
||||
// page 'foo.html' for holding other resource files of 'foo.html'.
|
||||
@@ -36,7 +36,7 @@ bool SavePageHandler::Handle(const base::FilePath& full_path,
|
||||
full_path.RemoveExtension().BaseName().value() +
|
||||
FILE_PATH_LITERAL("_files"));
|
||||
bool result =
|
||||
web_contents->SavePage(full_path, saved_main_directory_path, save_type);
|
||||
web_contents_->SavePage(full_path, saved_main_directory_path, save_type);
|
||||
download_manager->RemoveObserver(this);
|
||||
// If initialization fails which means fail to create |DownloadItem|, we need
|
||||
// to delete the |SavePageHandler| instance to avoid memory-leak.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#ifndef ELECTRON_SHELL_BROWSER_API_SAVE_PAGE_HANDLER_H_
|
||||
#define ELECTRON_SHELL_BROWSER_API_SAVE_PAGE_HANDLER_H_
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "components/download/public/common/download_item.h"
|
||||
#include "content/public/browser/download_manager.h"
|
||||
#include "content/public/browser/save_page_type.h"
|
||||
@@ -25,12 +26,12 @@ namespace electron::api {
|
||||
class SavePageHandler : private content::DownloadManager::Observer,
|
||||
private download::DownloadItem::Observer {
|
||||
public:
|
||||
explicit SavePageHandler(gin_helper::Promise<void> promise);
|
||||
SavePageHandler(content::WebContents* web_contents,
|
||||
gin_helper::Promise<void> promise);
|
||||
~SavePageHandler() override;
|
||||
|
||||
bool Handle(const base::FilePath& full_path,
|
||||
const content::SavePageType& save_type,
|
||||
content::WebContents* web_contents);
|
||||
const content::SavePageType& save_type);
|
||||
|
||||
private:
|
||||
void Destroy(download::DownloadItem* item);
|
||||
@@ -42,6 +43,7 @@ class SavePageHandler : private content::DownloadManager::Observer,
|
||||
// download::DownloadItem::Observer:
|
||||
void OnDownloadUpdated(download::DownloadItem* item) override;
|
||||
|
||||
raw_ptr<content::WebContents> web_contents_; // weak
|
||||
gin_helper::Promise<void> promise_;
|
||||
};
|
||||
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
|
||||
#include "net/ssl/ssl_cert_request_info.h"
|
||||
#include "net/ssl/ssl_private_key.h"
|
||||
#include "pdf/pdf_features.h"
|
||||
#include "printing/buildflags/buildflags.h"
|
||||
#include "services/device/public/cpp/geolocation/geolocation_system_permission_manager.h"
|
||||
#include "services/device/public/cpp/geolocation/location_provider.h"
|
||||
@@ -1592,46 +1591,6 @@ bool ElectronBrowserClient::ShouldEnableStrictSiteIsolation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ElectronBrowserClient::ShouldEnableSubframeZoom() {
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
return chrome_pdf::features::IsOopifPdfEnabled();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
std::optional<network::CrossOriginEmbedderPolicy>
|
||||
ElectronBrowserClient::MaybeOverrideLocalURLCrossOriginEmbedderPolicy(
|
||||
content::NavigationHandle* navigation_handle) {
|
||||
if (!chrome_pdf::features::IsOopifPdfEnabled() ||
|
||||
!navigation_handle->IsPdf()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
content::RenderFrameHost* pdf_extension = navigation_handle->GetParentFrame();
|
||||
if (!pdf_extension) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
content::RenderFrameHost* pdf_embedder = pdf_extension->GetParent();
|
||||
CHECK(pdf_embedder);
|
||||
return pdf_embedder->GetCrossOriginEmbedderPolicy();
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
|
||||
bool ElectronBrowserClient::DoesSiteRequireDedicatedProcess(
|
||||
content::BrowserContext* browser_context,
|
||||
const GURL& effective_site_url) {
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
return GetEnabledExtensionFromEffectiveURL(browser_context,
|
||||
effective_site_url) != nullptr;
|
||||
#else
|
||||
return content::ContentBrowserClient::DoesSiteRequireDedicatedProcess(
|
||||
browser_context, effective_site_url);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ElectronBrowserClient::BindHostReceiverForRenderer(
|
||||
content::RenderProcessHost* render_process_host,
|
||||
mojo::GenericPendingReceiver receiver) {
|
||||
|
||||
@@ -30,7 +30,6 @@ class FilePath;
|
||||
|
||||
namespace content {
|
||||
class ClientCertificateDelegate;
|
||||
class NavigationHandle;
|
||||
class PlatformNotificationService;
|
||||
class NavigationThrottleRegistry;
|
||||
class QuotaPermissionContext;
|
||||
@@ -83,14 +82,6 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
||||
// content::ContentBrowserClient:
|
||||
std::string GetApplicationLocale() override;
|
||||
bool ShouldEnableStrictSiteIsolation() override;
|
||||
bool ShouldEnableSubframeZoom() override;
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
std::optional<network::CrossOriginEmbedderPolicy>
|
||||
MaybeOverrideLocalURLCrossOriginEmbedderPolicy(
|
||||
content::NavigationHandle* navigation_handle) override;
|
||||
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
bool DoesSiteRequireDedicatedProcess(content::BrowserContext* browser_context,
|
||||
const GURL& effective_site_url) override;
|
||||
void BindHostReceiverForRenderer(
|
||||
content::RenderProcessHost* render_process_host,
|
||||
mojo::GenericPendingReceiver receiver) override;
|
||||
|
||||
@@ -71,7 +71,7 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent(
|
||||
std::move(transferrable_loader), original_url);
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
if (chrome_pdf::features::IsOopifPdfEnabled() &&
|
||||
if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfOopif) &&
|
||||
extension_id == extension_misc::kPdfExtensionId) {
|
||||
pdf::PdfViewerStreamManager::Create(web_contents);
|
||||
pdf::PdfViewerStreamManager::FromWebContents(web_contents)
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
#include "printing/printing_features.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include "ui/views/views_features.h"
|
||||
#endif
|
||||
|
||||
namespace electron {
|
||||
|
||||
void InitializeFeatureList() {
|
||||
@@ -67,6 +71,13 @@ void InitializeFeatureList() {
|
||||
blink::features::kDropInputEventsWhilePaintHolding.name;
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// Refs https://issues.chromium.org/issues/401996981
|
||||
// TODO(deepak1556): Remove this once test added in
|
||||
// https://github.com/electron/electron/pull/12904
|
||||
// can work without this feature.
|
||||
enable_features += std::string(",") +
|
||||
views::features::kEnableTransparentHwndEnlargement.name;
|
||||
|
||||
// See https://chromium-review.googlesource.com/c/chromium/src/+/7204292
|
||||
// This feature causes the following sandbox failure on Windows:
|
||||
// sandbox\policy\win\sandbox_win.cc:777 Sandbox cannot access executable
|
||||
|
||||
@@ -31,6 +31,11 @@
|
||||
#include "shell/browser/ui/views/frameless_view.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include "ui/display/win/screen_win.h"
|
||||
#include "ui/views/views_features.h"
|
||||
#endif
|
||||
|
||||
#if defined(USE_OZONE)
|
||||
#include "ui/base/ui_base_features.h"
|
||||
#include "ui/ozone/public/ozone_platform.h"
|
||||
@@ -66,6 +71,31 @@ struct Converter<electron::NativeWindow::TitleBarStyle> {
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
gfx::Size GetExpandedWindowSize(const NativeWindow* window,
|
||||
bool transparent,
|
||||
gfx::Size size) {
|
||||
if (!base::FeatureList::IsEnabled(
|
||||
views::features::kEnableTransparentHwndEnlargement) ||
|
||||
!transparent) {
|
||||
return size;
|
||||
}
|
||||
|
||||
gfx::Size min_size = display::win::GetScreenWin()->ScreenToDIPSize(
|
||||
window->GetAcceleratedWidget(), gfx::Size{64, 64});
|
||||
|
||||
// Some AMD drivers can't display windows that are less than 64x64 pixels,
|
||||
// so expand them to be at least that size. http://crbug.com/286609
|
||||
gfx::Size expanded(std::max(size.width(), min_size.width()),
|
||||
std::max(size.height(), min_size.height()));
|
||||
return expanded;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
NativeWindow::NativeWindow(const int32_t base_window_id,
|
||||
const gin_helper::Dictionary& options,
|
||||
NativeWindow* parent)
|
||||
@@ -368,7 +398,15 @@ gfx::Size NativeWindow::GetContentMinimumSize() const {
|
||||
}
|
||||
|
||||
gfx::Size NativeWindow::GetContentMaximumSize() const {
|
||||
return GetContentSizeConstraints().GetMaximumSize();
|
||||
const auto size_constraints = GetContentSizeConstraints();
|
||||
gfx::Size maximum_size = size_constraints.GetMaximumSize();
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
if (size_constraints.HasMaximumSize())
|
||||
maximum_size = GetExpandedWindowSize(this, transparent(), maximum_size);
|
||||
#endif
|
||||
|
||||
return maximum_size;
|
||||
}
|
||||
|
||||
void NativeWindow::SetSheetOffset(const double offsetX, const double offsetY) {
|
||||
|
||||
@@ -158,43 +158,45 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
|
||||
windowSize.width() - contentSize.width() + extraSize.width();
|
||||
double extraHeightPlusFrame = titleBarHeight + extraSize.height();
|
||||
|
||||
auto widthForHeight = [&](double h) {
|
||||
return (h - extraHeightPlusFrame) * aspectRatio + extraWidthPlusFrame;
|
||||
};
|
||||
auto heightForWidth = [&](double w) {
|
||||
return (w - extraWidthPlusFrame) / aspectRatio + extraHeightPlusFrame;
|
||||
};
|
||||
|
||||
newSize.width = roundf(widthForHeight(frameSize.height));
|
||||
newSize.height = roundf(heightForWidth(newSize.width));
|
||||
newSize.width =
|
||||
roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio +
|
||||
extraWidthPlusFrame);
|
||||
newSize.height =
|
||||
roundf((newSize.width - extraWidthPlusFrame) / aspectRatio +
|
||||
extraHeightPlusFrame);
|
||||
|
||||
// Clamp to minimum width/height while ensuring aspect ratio remains.
|
||||
NSSize minSize = [window minSize];
|
||||
NSSize zeroSize =
|
||||
shell_->has_frame() ? NSMakeSize(0, titleBarHeight) : NSZeroSize;
|
||||
if (!NSEqualSizes(minSize, zeroSize)) {
|
||||
double minWidthForAspectRatio =
|
||||
(minSize.height - titleBarHeight) * aspectRatio;
|
||||
bool atMinHeight =
|
||||
minSize.height > zeroSize.height && newSize.height <= minSize.height;
|
||||
newSize.width = atMinHeight ? widthForHeight(minSize.height)
|
||||
newSize.width = atMinHeight ? minWidthForAspectRatio
|
||||
: std::max(newSize.width, minSize.width);
|
||||
|
||||
double minHeightForAspectRatio = minSize.width / aspectRatio;
|
||||
bool atMinWidth =
|
||||
minSize.width > zeroSize.width && newSize.width <= minSize.width;
|
||||
newSize.height = atMinWidth ? heightForWidth(minSize.width)
|
||||
newSize.height = atMinWidth ? minHeightForAspectRatio
|
||||
: std::max(newSize.height, minSize.height);
|
||||
}
|
||||
|
||||
// Clamp to maximum width/height while ensuring aspect ratio remains.
|
||||
NSSize maxSize = [window maxSize];
|
||||
if (!NSEqualSizes(maxSize, NSMakeSize(FLT_MAX, FLT_MAX))) {
|
||||
double maxWidthForAspectRatio = maxSize.height * aspectRatio;
|
||||
bool atMaxHeight =
|
||||
maxSize.height < FLT_MAX && newSize.height >= maxSize.height;
|
||||
newSize.width = atMaxHeight ? widthForHeight(maxSize.height)
|
||||
newSize.width = atMaxHeight ? maxWidthForAspectRatio
|
||||
: std::min(newSize.width, maxSize.width);
|
||||
|
||||
double maxHeightForAspectRatio = maxSize.width / aspectRatio;
|
||||
bool atMaxWidth =
|
||||
maxSize.width < FLT_MAX && newSize.width >= maxSize.width;
|
||||
newSize.height = atMaxWidth ? heightForWidth(maxSize.width)
|
||||
newSize.height = atMaxWidth ? maxHeightForAspectRatio
|
||||
: std::min(newSize.height, maxSize.height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,19 +106,29 @@ int WinFrameView::NonClientHitTest(const gfx::Point& point) {
|
||||
if (SUCCEEDED(DwmGetWindowAttribute(
|
||||
views::HWNDForWidget(frame()), DWMWA_CAPTION_BUTTON_BOUNDS,
|
||||
&button_bounds, sizeof(button_bounds)))) {
|
||||
gfx::Rect button_bounds_px(button_bounds);
|
||||
// There is a small one-pixel strip right above the caption buttons in
|
||||
// which the resize border "peeks" through. Inset in physical pixels
|
||||
// before converting to DIPs so the resize strip remains exposed at
|
||||
// fractional scale factors.
|
||||
button_bounds_px.Inset(gfx::Insets::TLBR(1, 0, 0, 0));
|
||||
|
||||
const gfx::RectF button_bounds_in_dips =
|
||||
gfx::ConvertRectToDips(button_bounds_px, display::win::GetDPIScale());
|
||||
// GetMirroredRect() requires an integer rect. Use ToEnclosedRect() so
|
||||
// the top inset is preserved (rounded up) at fractional scale factors.
|
||||
gfx::RectF button_bounds_in_dips = gfx::ConvertRectToDips(
|
||||
gfx::Rect(button_bounds), display::win::GetDPIScale());
|
||||
// TODO(crbug.com/1131681): GetMirroredRect() requires an integer rect,
|
||||
// but the size in DIPs may not be an integer with a fractional device
|
||||
// scale factor. If we want to keep using integers, the choice to use
|
||||
// ToFlooredRectDeprecated() seems to be doing the wrong thing given the
|
||||
// comment below about insetting 1 DIP instead of 1 physical pixel. We
|
||||
// should probably use ToEnclosedRect() and then we could have inset 1
|
||||
// physical pixel here.
|
||||
gfx::Rect buttons =
|
||||
GetMirroredRect(gfx::ToEnclosedRect(button_bounds_in_dips));
|
||||
GetMirroredRect(gfx::ToFlooredRectDeprecated(button_bounds_in_dips));
|
||||
|
||||
// There is a small one-pixel strip right above the caption buttons in
|
||||
// which the resize border "peeks" through.
|
||||
constexpr int kCaptionButtonTopInset = 1;
|
||||
// The sizing region at the window edge above the caption buttons is
|
||||
// 1 px regardless of scale factor. If we inset by 1 before converting
|
||||
// to DIPs, the precision loss might eliminate this region entirely. The
|
||||
// best we can do is to inset after conversion. This guarantees we'll
|
||||
// show the resize cursor when resizing is possible. The cost of which
|
||||
// is also maybe showing it over the portion of the DIP that isn't the
|
||||
// outermost pixel.
|
||||
buttons.Inset(gfx::Insets::TLBR(0, kCaptionButtonTopInset, 0, 0));
|
||||
if (buttons.Contains(point))
|
||||
return HTNOWHERE;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
#include "shell/common/crash_keys.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/containers/circular_deque.h"
|
||||
#include "base/environment.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/strings/strcat.h"
|
||||
@@ -28,22 +28,17 @@ namespace electron::crash_keys {
|
||||
|
||||
namespace {
|
||||
|
||||
// Do NOT replace with base::circular_deque. CrashKeyString wraps a
|
||||
// crashpad::Annotation that holds self-referential pointers and registers
|
||||
// in a process-global linked list; relocating elements (as circular_deque
|
||||
// does on growth) corrupts that list and hangs the crashpad handler.
|
||||
// std::deque never relocates existing elements. See #50795.
|
||||
auto& GetExtraCrashKeys() {
|
||||
constexpr size_t kMaxCrashKeyValueSize = 20320;
|
||||
static_assert(kMaxCrashKeyValueSize < crashpad::Annotation::kValueMaxSize,
|
||||
"max crash key value length above what crashpad supports");
|
||||
using CrashKeyString = crash_reporter::CrashKeyString<kMaxCrashKeyValueSize>;
|
||||
static base::NoDestructor<std::deque<CrashKeyString>> extra_keys;
|
||||
static base::NoDestructor<base::circular_deque<CrashKeyString>> extra_keys;
|
||||
return *extra_keys;
|
||||
}
|
||||
|
||||
auto& GetExtraCrashKeyNames() {
|
||||
static base::NoDestructor<std::deque<std::string>> crash_key_names;
|
||||
static base::NoDestructor<base::circular_deque<std::string>> crash_key_names;
|
||||
return *crash_key_names;
|
||||
}
|
||||
|
||||
|
||||
@@ -529,14 +529,7 @@ NodeBindings::NodeBindings(BrowserEnvironment browser_env)
|
||||
uv_loop_{InitEventLoop(browser_env, &worker_loop_)} {}
|
||||
|
||||
NodeBindings::~NodeBindings() {
|
||||
// Quit the embed thread.
|
||||
embed_closed_ = true;
|
||||
uv_sem_post(&embed_sem_);
|
||||
|
||||
WakeupEmbedThread();
|
||||
|
||||
// Wait for everything to be done.
|
||||
uv_thread_join(&embed_thread_);
|
||||
StopPolling();
|
||||
|
||||
// Clear uv.
|
||||
uv_sem_destroy(&embed_sem_);
|
||||
@@ -547,6 +540,26 @@ NodeBindings::~NodeBindings() {
|
||||
stop_and_close_uv_loop(uv_loop_);
|
||||
}
|
||||
|
||||
void NodeBindings::StopPolling() {
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
// Tell the embed thread to quit.
|
||||
embed_closed_ = true;
|
||||
|
||||
// The embed thread alternates between uv_sem_wait (waiting for UvRunOnce
|
||||
// to finish) and PollEvents (waiting for I/O). Wake it from both.
|
||||
uv_sem_post(&embed_sem_);
|
||||
WakeupEmbedThread();
|
||||
|
||||
// Wait for it to exit.
|
||||
uv_thread_join(&embed_thread_);
|
||||
|
||||
// Allow PrepareEmbedThread + StartPolling to restart.
|
||||
embed_closed_ = false;
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
node::IsolateData* NodeBindings::isolate_data(
|
||||
v8::Local<v8::Context> context) const {
|
||||
if (context->GetNumberOfEmbedderDataFields() <=
|
||||
@@ -933,12 +946,21 @@ void NodeBindings::PrepareEmbedThread() {
|
||||
if (initialized_)
|
||||
return;
|
||||
|
||||
// Add dummy handle for libuv, otherwise libuv would quit when there is
|
||||
// nothing to do.
|
||||
uv_async_init(uv_loop_, dummy_uv_handle_.get(), nullptr);
|
||||
// The async handle and semaphore live for the lifetime of this
|
||||
// NodeBindings instance (destroyed in ~NodeBindings), but the embed
|
||||
// thread itself may be stopped and restarted via StopPolling /
|
||||
// PrepareEmbedThread for pooled worklet contexts. Only init the
|
||||
// handles once.
|
||||
if (!embed_thread_prepared_) {
|
||||
// Add dummy handle for libuv, otherwise libuv would quit when there is
|
||||
// nothing to do.
|
||||
uv_async_init(uv_loop_, dummy_uv_handle_.get(), nullptr);
|
||||
|
||||
// Start worker that will interrupt main loop when having uv events.
|
||||
uv_sem_init(&embed_sem_, 0);
|
||||
embed_thread_prepared_ = true;
|
||||
}
|
||||
|
||||
// Start worker that will interrupt main loop when having uv events.
|
||||
uv_sem_init(&embed_sem_, 0);
|
||||
uv_thread_create(&embed_thread_, EmbedThreadRunner, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,12 @@ class NodeBindings {
|
||||
// Notify embed thread to start polling after environment is loaded.
|
||||
void StartPolling();
|
||||
|
||||
// Stop the embed thread and polling without destroying handles or the loop.
|
||||
// After this call, PrepareEmbedThread + StartPolling can restart them.
|
||||
// Used by pooled worklets that need to pause the embed thread during
|
||||
// environment teardown but reuse the same NodeBindings for the next context.
|
||||
void StopPolling();
|
||||
|
||||
node::IsolateData* isolate_data(v8::Local<v8::Context> context) const;
|
||||
|
||||
// Gets/sets the environment to wrap uv loop.
|
||||
@@ -225,6 +231,11 @@ class NodeBindings {
|
||||
// Indicates whether polling thread has been created.
|
||||
bool initialized_ = false;
|
||||
|
||||
// Whether PrepareEmbedThread has initialized the semaphore and async handle.
|
||||
// Unlike |initialized_|, this is never reset — the handles live until the
|
||||
// destructor.
|
||||
bool embed_thread_prepared_ = false;
|
||||
|
||||
// Indicates whether the app code has finished loading
|
||||
// for ESM this is async after the module is loaded
|
||||
bool app_code_loaded_ = false;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" // nogncheck
|
||||
#include "third_party/blink/renderer/core/workers/worker_global_scope.h" // nogncheck
|
||||
#include "third_party/blink/renderer/core/workers/worker_settings.h" // nogncheck
|
||||
#include "third_party/blink/renderer/core/workers/worklet_global_scope.h" // nogncheck
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -206,11 +207,20 @@ bool WorkerHasNodeIntegration(blink::ExecutionContext* ec) {
|
||||
// owing to an inability to customize sandbox policies in these workers
|
||||
// given that they're run out-of-process.
|
||||
// Also avoid creating a Node.js environment for worklet global scope
|
||||
// created on the main thread.
|
||||
// created on the main thread — those share the page's V8 context where
|
||||
// Node is already wired up.
|
||||
if (ec->IsServiceWorkerGlobalScope() || ec->IsSharedWorkerGlobalScope() ||
|
||||
ec->IsMainThreadWorkletGlobalScope())
|
||||
return false;
|
||||
|
||||
// Off-main-thread worklets (AudioWorklet, PaintWorklet, AnimationWorklet,
|
||||
// SharedStorageWorklet) have their own dedicated worker thread but do not
|
||||
// derive from WorkerGlobalScope, so check for them separately and read the
|
||||
// flag from WorkletGlobalScope, which copies it out of the same
|
||||
// WorkerSettings as dedicated workers do.
|
||||
if (auto* wlgs = blink::DynamicTo<blink::WorkletGlobalScope>(ec))
|
||||
return wlgs->NodeIntegrationInWorker();
|
||||
|
||||
auto* wgs = blink::DynamicTo<blink::WorkerGlobalScope>(ec);
|
||||
if (!wgs)
|
||||
return false;
|
||||
@@ -233,9 +243,9 @@ void ElectronRendererClient::WorkerScriptReadyForEvaluationOnWorkerThread(
|
||||
return;
|
||||
|
||||
auto* current = WebWorkerObserver::GetCurrent();
|
||||
if (current)
|
||||
return;
|
||||
WebWorkerObserver::Create()->WorkerScriptReadyForEvaluation(context);
|
||||
if (!current)
|
||||
current = WebWorkerObserver::Create();
|
||||
current->WorkerScriptReadyForEvaluation(context);
|
||||
}
|
||||
|
||||
void ElectronRendererClient::WillDestroyWorkerContextOnWorkerThread(
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
#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"
|
||||
#include "third_party/blink/renderer/core/execution_context/execution_context.h" // nogncheck
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -23,6 +24,23 @@ namespace {
|
||||
static base::NoDestructor<base::ThreadLocalOwnedPointer<WebWorkerObserver>>
|
||||
lazy_tls;
|
||||
|
||||
// Returns true if `context` belongs to a worklet that runs on a thread
|
||||
// pooled by Blink's WorkletThreadHolder, where the worker thread can be
|
||||
// reused for multiple worklet contexts. For these scopes the
|
||||
// WebWorkerObserver and its NodeBindings must outlive the v8::Context so
|
||||
// the next pooled context can reuse them — Node.js cannot be re-initialized
|
||||
// on the same thread (the allocator shim only loads once). See callers of
|
||||
// blink::WorkletThreadHolder in third_party/blink for the authoritative
|
||||
// list.
|
||||
bool IsPooledWorkletContext(v8::Local<v8::Context> context) {
|
||||
auto* ec = blink::ExecutionContext::From(context);
|
||||
if (!ec)
|
||||
return false;
|
||||
return ec->IsAudioWorkletGlobalScope() || ec->IsPaintWorkletGlobalScope() ||
|
||||
ec->IsAnimationWorkletGlobalScope() ||
|
||||
ec->IsSharedStorageWorkletGlobalScope();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
@@ -48,6 +66,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 +139,191 @@ 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;
|
||||
|
||||
const bool is_pooled_worklet = IsPooledWorkletContext(context);
|
||||
|
||||
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>());
|
||||
// We are mid-teardown and about to destroy the worker's
|
||||
// node::Environment, so we cannot let an exception thrown by an
|
||||
// 'exit' listener propagate back into Blink (it would assert in
|
||||
// V8::FromJustIsNothing on the next call into V8). Log it and
|
||||
// explicitly reset the TryCatch so the destructor doesn't rethrow.
|
||||
if (try_catch.HasCaught()) {
|
||||
if (auto message = try_catch.Message(); !message.IsEmpty()) {
|
||||
std::string str;
|
||||
if (gin::ConvertFromV8(isolate, message->Get(), &str))
|
||||
LOG(ERROR) << "Exception thrown from worker 'exit' handler: "
|
||||
<< str;
|
||||
}
|
||||
try_catch.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent UvRunOnce from using the environment after it's destroyed.
|
||||
node_bindings_->set_uv_env(nullptr);
|
||||
|
||||
// Stop the embed thread before destroying environments. The embed
|
||||
// thread's PollEvents and FreeEnvironment's uv_run both compete for
|
||||
// completions on the same libuv event loop; on Windows (IOCP) this
|
||||
// race can deadlock. Joining the embed thread first eliminates the
|
||||
// contention so FreeEnvironment's uv_run can drain handles cleanly.
|
||||
// For pooled worklets the thread is restarted in
|
||||
// InitializeNewEnvironment via PrepareEmbedThread + StartPolling.
|
||||
node_bindings_->StopPolling();
|
||||
|
||||
// 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);
|
||||
|
||||
// For non-pooled worker contexts (e.g., dedicated workers) Blink does
|
||||
// not reuse the worker thread, so tear down the observer completely.
|
||||
//
|
||||
// For pooled worklet contexts (AudioWorklet, PaintWorklet,
|
||||
// AnimationWorklet, SharedStorageWorklet — see
|
||||
// blink::WorkletThreadHolder) the same NodeBindings must be reused
|
||||
// for the next context on the thread because Node.js cannot be
|
||||
// re-initialized on the same thread. Keep the observer alive and let
|
||||
// the next WorkerScriptReadyForEvaluation call
|
||||
// InitializeNewEnvironment, which restarts the embed thread via
|
||||
// PrepareEmbedThread + StartPolling.
|
||||
if (!is_pooled_worklet) {
|
||||
lazy_tls->Set(nullptr); // destroys *this; do not access members below
|
||||
return;
|
||||
}
|
||||
} 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5641,6 +5641,21 @@ describe('BrowserWindow module', () => {
|
||||
expectBoundsEqual(w.getSize(), [400, 300]);
|
||||
});
|
||||
|
||||
ifit(process.platform !== 'darwin')('works for a window smaller than 64x64', () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
frame: false,
|
||||
resizable: false,
|
||||
transparent: true
|
||||
});
|
||||
w.setContentSize(60, 60);
|
||||
expectBoundsEqual(w.getContentSize(), [60, 60]);
|
||||
w.setContentSize(30, 30);
|
||||
expectBoundsEqual(w.getContentSize(), [30, 30]);
|
||||
w.setContentSize(10, 10);
|
||||
expectBoundsEqual(w.getContentSize(), [10, 10]);
|
||||
});
|
||||
|
||||
ifit(process.platform === 'win32')('do not change window with frame bounds when maximized', () => {
|
||||
const w = new BrowserWindow({
|
||||
show: true,
|
||||
|
||||
@@ -250,34 +250,6 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
|
||||
expect(crash.addedThenRemoved).to.be.undefined();
|
||||
});
|
||||
|
||||
// Regression: base::circular_deque relocates elements on growth,
|
||||
// corrupting crashpad::Annotation's self-referential pointers and
|
||||
// causing missing crash keys or a hung handler. See crash_keys.cc.
|
||||
it('does not corrupt the crashpad annotation list after deque reallocation', async function () {
|
||||
// Tight timeout so a hanging handler fails fast instead of waiting
|
||||
// for the mocha default of 120s.
|
||||
this.timeout(45000);
|
||||
const { port, waitForCrash } = await startServer();
|
||||
runCrashApp('renderer-dynamic-keys', port);
|
||||
const crash = await Promise.race([
|
||||
waitForCrash(),
|
||||
new Promise<never>((_resolve, reject) => {
|
||||
global.setTimeout(
|
||||
() => reject(new Error('crashpad handler hung walking corrupted annotation list; crash upload did not arrive within 30s')),
|
||||
30000
|
||||
);
|
||||
})
|
||||
]);
|
||||
expect(crash.process_type).to.equal('renderer');
|
||||
const missing: string[] = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
if ((crash as any)[`dyn-key-${i}`] !== `val-${i}`) {
|
||||
missing.push(`dyn-key-${i}`);
|
||||
}
|
||||
}
|
||||
expect(missing, `missing dynamic crash keys: ${missing.join(', ')}`).to.be.empty();
|
||||
});
|
||||
|
||||
it('contains v8 crash keys when a v8 crash occurs', async () => {
|
||||
const { remotely } = await startRemoteControlApp();
|
||||
const { port, waitForCrash } = await startServer();
|
||||
|
||||
@@ -1625,6 +1625,27 @@ describe('chromium features', () => {
|
||||
expect(data).to.equal('function function function function function');
|
||||
});
|
||||
|
||||
it('AudioWorklet keeps node integration across pooled worker threads', async () => {
|
||||
// Regression test for https://github.com/electron/electron/issues/41263.
|
||||
// Blink pools the AudioWorklet backing thread (Chromium CL:5270028) so
|
||||
// the Nth+ AudioWorklet on a page reuses the same thread; the page
|
||||
// creates several AudioWorklet contexts in sequence and asserts node
|
||||
// integration is wired up in every one of them.
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true,
|
||||
contextIsolation: false
|
||||
}
|
||||
});
|
||||
|
||||
w.loadURL(`file://${fixturesPath}/pages/audio-worklet.html`);
|
||||
const [, results] = await once(ipcMain, 'audio-worklet-result');
|
||||
expect(results).to.be.an('array').with.length.greaterThan(0);
|
||||
for (const r of results) expect(r).to.equal('ok');
|
||||
});
|
||||
|
||||
describe('SharedWorker', () => {
|
||||
it('can work', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
|
||||
13
spec/fixtures/apps/crash/main.js
vendored
13
spec/fixtures/apps/crash/main.js
vendored
@@ -51,19 +51,6 @@ app.whenReady().then(() => {
|
||||
});
|
||||
w.loadURL(`about:blank?set_extra=${setExtraParameters ? 1 : 0}`);
|
||||
w.webContents.on('render-process-gone', () => process.exit(0));
|
||||
} else if (crashType === 'renderer-dynamic-keys') {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.webContents.on('render-process-gone', () => process.exit(0));
|
||||
w.webContents.on('did-finish-load', () => {
|
||||
w.webContents.executeJavaScript(`
|
||||
const { crashReporter } = require('electron');
|
||||
for (let i = 0; i < 50; i++) {
|
||||
crashReporter.addExtraParameter('dyn-key-' + i, 'val-' + i);
|
||||
}
|
||||
process.crash();
|
||||
`);
|
||||
});
|
||||
w.loadURL('about:blank');
|
||||
} else if (crashType === 'node') {
|
||||
const crashPath = path.join(__dirname, 'node-crash.js');
|
||||
const child = childProcess.fork(crashPath, { silent: true });
|
||||
|
||||
44
spec/fixtures/pages/audio-worklet.html
vendored
Normal file
44
spec/fixtures/pages/audio-worklet.html
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
<html>
|
||||
<body>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
// Create a number of AudioContext + AudioWorklet pairs in sequence so
|
||||
// that Blink's WorkletThreadHolder pools and reuses the underlying
|
||||
// worker thread (Chromium CL:5270028). For each context we ask the
|
||||
// worklet to report whether `require` is a function and post that back
|
||||
// via its MessagePort. The bug being guarded is that the Nth+ pooled
|
||||
// worklet would silently lose its Node.js environment, so the test
|
||||
// must run enough iterations to exercise thread reuse.
|
||||
const NUM_CONTEXTS = 6;
|
||||
|
||||
async function runOne(index) {
|
||||
const audioCtx = new AudioContext();
|
||||
try {
|
||||
await audioCtx.audioWorklet.addModule('../workers/audio_worklet_node.js');
|
||||
const node = new AudioWorkletNode(audioCtx, 'node-integration-probe');
|
||||
const reply = new Promise((resolve) => {
|
||||
node.port.onmessage = (e) => resolve(e.data);
|
||||
});
|
||||
node.port.postMessage('probe');
|
||||
node.connect(audioCtx.destination);
|
||||
return await reply;
|
||||
} finally {
|
||||
await audioCtx.close();
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const results = [];
|
||||
for (let i = 0; i < NUM_CONTEXTS; i++) {
|
||||
try {
|
||||
results.push(await runOne(i));
|
||||
} catch (err) {
|
||||
results.push(`error: ${err && err.message ? err.message : err}`);
|
||||
}
|
||||
}
|
||||
ipcRenderer.send('audio-worklet-result', results);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
28
spec/fixtures/workers/audio_worklet_node.js
vendored
Normal file
28
spec/fixtures/workers/audio_worklet_node.js
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Reports whether the Node.js environment is wired up inside this
|
||||
// AudioWorklet's global scope. Used by spec/fixtures/pages/audio-worklet.html
|
||||
// to verify that nodeIntegrationInWorker keeps working when Blink reuses a
|
||||
// pooled worker thread for multiple AudioWorklet contexts.
|
||||
class NodeIntegrationProbeProcessor extends AudioWorkletProcessor {
|
||||
constructor () {
|
||||
super();
|
||||
this.port.onmessage = () => {
|
||||
let info;
|
||||
try {
|
||||
// require should be a function and `node:timers` should resolve.
|
||||
const ok = typeof require === 'function' &&
|
||||
typeof require('node:timers').setImmediate === 'function' &&
|
||||
typeof process === 'object';
|
||||
info = ok ? 'ok' : 'missing';
|
||||
} catch (err) {
|
||||
info = `throw: ${err && err.message ? err.message : err}`;
|
||||
}
|
||||
this.port.postMessage(info);
|
||||
};
|
||||
}
|
||||
|
||||
process () {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('node-integration-probe', NodeIntegrationProbeProcessor);
|
||||
11
yarn.lock
11
yarn.lock
@@ -590,7 +590,7 @@ __metadata:
|
||||
"@types/semver": "npm:^7.5.8"
|
||||
"@types/stream-json": "npm:^1.7.8"
|
||||
"@types/temp": "npm:^0.9.4"
|
||||
"@xmldom/xmldom": "npm:^0.8.12"
|
||||
"@xmldom/xmldom": "npm:^0.8.11"
|
||||
buffer: "npm:^6.0.3"
|
||||
chalk: "npm:^4.1.0"
|
||||
check-for-leaks: "npm:^1.2.1"
|
||||
@@ -2503,14 +2503,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xmldom/xmldom@npm:^0.8.12":
|
||||
version: 0.8.12
|
||||
resolution: "@xmldom/xmldom@npm:0.8.12"
|
||||
checksum: 10c0/b733c84292d1bee32ef21a05aba8f9063456b51a54068d0b4a1abf5545156ee0b9894b7ae23775b5881b11c35a8a03871d1b514fb7e1b11654cdbee57e1c2707
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@xmldom/xmldom@npm:^0.8.8":
|
||||
"@xmldom/xmldom@npm:^0.8.11, @xmldom/xmldom@npm:^0.8.8":
|
||||
version: 0.8.11
|
||||
resolution: "@xmldom/xmldom@npm:0.8.11"
|
||||
checksum: 10c0/e768623de72c95d3dae6b5da8e33dda0d81665047811b5498d23a328d45b13feb5536fe921d0308b96a4a8dd8addf80b1f6ef466508051c0b581e63e0dc74ed5
|
||||
|
||||
Reference in New Issue
Block a user