mirror of
https://github.com/electron/electron.git
synced 2026-03-19 03:02:02 -04:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de61f6c5e8 | ||
|
|
90f85f2bf4 | ||
|
|
60951cdca9 | ||
|
|
a3022df30f | ||
|
|
996fbfd6bc | ||
|
|
79d1e32281 |
@@ -191,12 +191,21 @@ jobs:
|
||||
run: |
|
||||
cd src/out/Default
|
||||
unzip -:o dist.zip
|
||||
#- name: Import & Trust Self-Signed Codesigning Cert on MacOS
|
||||
# if: ${{ inputs.target-platform == 'macos' && inputs.target-arch == 'x64' }}
|
||||
# run: |
|
||||
# sudo security authorizationdb write com.apple.trust-settings.admin allow
|
||||
# cd src/electron
|
||||
# ./script/codesign/generate-identity.sh
|
||||
- name: Import & Trust Self-Signed Codesigning Cert on MacOS
|
||||
if: ${{ inputs.target-platform == 'macos' }}
|
||||
run: |
|
||||
cd src/electron
|
||||
./script/codesign/generate-identity.sh
|
||||
# Only sign on x64 — arm64 builds are already ad-hoc signed, and re-signing
|
||||
# with an untrusted cert breaks macOS system integrations (e.g. dock bounce).
|
||||
# Autoupdater tests sign their own fixture copies via signApp().
|
||||
- name: Sign Electron.app for macOS tests
|
||||
if: ${{ inputs.target-platform == 'macos' && inputs.target-arch == 'x64' }}
|
||||
run: |
|
||||
identity=$(src/electron/script/codesign/get-trusted-identity.sh)
|
||||
if [ -n "$identity" ]; then
|
||||
codesign -s "$identity" --deep --force src/out/Default/Electron.app
|
||||
fi
|
||||
|
||||
- name: Run Electron Tests
|
||||
shell: bash
|
||||
|
||||
2
DEPS
2
DEPS
@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'144.0.7559.225',
|
||||
'144.0.7559.236',
|
||||
'node_version':
|
||||
'v24.14.0',
|
||||
'nan_version':
|
||||
|
||||
@@ -111,7 +111,8 @@ app.whenReady().then(() => {
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `details` Event\<\>
|
||||
* `reason` _Windows_ string (optional) - The reason the notification was closed. This can be 'userCanceled', 'applicationHidden', or 'timedOut'.
|
||||
|
||||
Emitted when the notification is closed by manual intervention from the user.
|
||||
|
||||
|
||||
@@ -115,6 +115,8 @@ filenames = {
|
||||
"shell/browser/win/scoped_hstring.h",
|
||||
"shell/common/api/electron_api_native_image_win.cc",
|
||||
"shell/common/application_info_win.cc",
|
||||
"shell/common/command_line_util_win.cc",
|
||||
"shell/common/command_line_util_win.h",
|
||||
"shell/common/language_util_win.cc",
|
||||
"shell/common/node_bindings_win.cc",
|
||||
"shell/common/node_bindings_win.h",
|
||||
|
||||
@@ -9,10 +9,10 @@ potentially prevent a window from being created.
|
||||
TODO(loc): this patch is currently broken.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
index edaf9a7b2efc5ed7f4e946d720de54d5001f44d4..7d27b076c1947d2cd08364f87286ed6d9f460cdc 100644
|
||||
index ce64151028cc30c81292326ee73126cb8415aec5..3feca12a6185afef139a0cb4a8148b5a3ca9e32f 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -9867,6 +9867,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
@@ -9868,6 +9868,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
last_committed_origin_, params->window_container_type,
|
||||
params->target_url, params->referrer.To<Referrer>(),
|
||||
params->frame_name, params->disposition, *params->features,
|
||||
|
||||
@@ -15,10 +15,10 @@ Note that we also need to manually update embedder's
|
||||
`api::WebContents::IsFullscreenForTabOrPending` value.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
index 7d27b076c1947d2cd08364f87286ed6d9f460cdc..55d1609ba46abe9433d22a894fc2b498735ff778 100644
|
||||
index 3feca12a6185afef139a0cb4a8148b5a3ca9e32f..393808a7959d684fe4e46f86eff687ec15258fa9 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -8973,6 +8973,17 @@ void RenderFrameHostImpl::EnterFullscreen(
|
||||
@@ -8974,6 +8974,17 @@ void RenderFrameHostImpl::EnterFullscreen(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
script/codesign/cleanup-identity.sh
Executable file
21
script/codesign/cleanup-identity.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Removes the codesigning keychain created by generate-identity.sh.
|
||||
# Safe to run even if generate-identity.sh was never run (each step
|
||||
# is guarded).
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
KEYCHAIN="electron-codesign.keychain-db"
|
||||
|
||||
# delete-keychain also removes it from the search list
|
||||
if security list-keychains -d user | grep -q "$KEYCHAIN"; then
|
||||
security delete-keychain "$KEYCHAIN"
|
||||
echo "Deleted keychain: $KEYCHAIN"
|
||||
else
|
||||
echo "Keychain not found, nothing to delete"
|
||||
fi
|
||||
|
||||
# Clean up working directory
|
||||
rm -rf "$(dirname $0)"/.working
|
||||
echo "Cleanup complete"
|
||||
@@ -3,6 +3,8 @@
|
||||
set -eo pipefail
|
||||
|
||||
dir="$(dirname $0)"/.working
|
||||
KEYCHAIN="electron-codesign.keychain-db"
|
||||
KEYCHAIN_TEMP="$(openssl rand -hex 12)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$dir"
|
||||
@@ -18,30 +20,16 @@ mkdir -p "$dir"
|
||||
|
||||
# Generate Certs
|
||||
openssl req -new -newkey rsa:2048 -x509 -days 7300 -nodes -config "$(dirname $0)"/codesign.cnf -extensions extended -batch -out "$dir"/certificate.cer -keyout "$dir"/certificate.key
|
||||
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$dir"/certificate.cer
|
||||
sudo security import "$dir"/certificate.key -A -k /Library/Keychains/System.keychain
|
||||
|
||||
# restart(reload) taskgated daemon
|
||||
sudo pkill -f /usr/libexec/taskgated
|
||||
# macOS 15+ blocks modifications to the system keychain via SIP/TCC,
|
||||
# so we use a custom user-scoped keychain instead.
|
||||
# Refs https://github.com/electron/electron/issues/48182
|
||||
security create-keychain -p "$KEYCHAIN_TEMP" "$KEYCHAIN"
|
||||
security set-keychain-settings -t 3600 -u "$KEYCHAIN"
|
||||
security unlock-keychain -p "$KEYCHAIN_TEMP" "$KEYCHAIN"
|
||||
|
||||
# need once
|
||||
sudo security authorizationdb write system.privilege.taskport allow
|
||||
# need once
|
||||
DevToolsSecurity -enable
|
||||
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
|
||||
security import "$dir"/certificate.cer -k "$KEYCHAIN" -T /usr/bin/codesign
|
||||
security import "$dir"/certificate.key -k "$KEYCHAIN" -T /usr/bin/codesign -A
|
||||
|
||||
# openssl req -newkey rsa:2048 -nodes -keyout "$dir"/private.pem -x509 -days 1 -out "$dir"/certificate.pem -extensions extended -config "$(dirname $0)"/codesign.cnf
|
||||
# openssl x509 -inform PEM -in "$dir"/certificate.pem -outform DER -out "$dir"/certificate.cer
|
||||
# openssl x509 -pubkey -noout -in "$dir"/certificate.pem > "$dir"/public.key
|
||||
# rm -f "$dir"/certificate.pem
|
||||
|
||||
# Import Certs
|
||||
# security import "$dir"/certificate.cer -k $KEY_CHAIN
|
||||
# security import "$dir"/private.pem -k $KEY_CHAIN
|
||||
# security import "$dir"/public.key -k $KEY_CHAIN
|
||||
|
||||
# Generate Trust Settings
|
||||
# TODO: Remove NPX
|
||||
npm_config_yes=true npx ts-node "$(dirname $0)"/gen-trust.ts "$dir"/certificate.cer "$dir"/trust.xml
|
||||
|
||||
# Import Trust Settings
|
||||
sudo security trust-settings-import -d "$dir/trust.xml"
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_TEMP" "$KEYCHAIN"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
valid_certs=$(security find-identity -p codesigning -v)
|
||||
valid_certs=$(security find-identity -p codesigning)
|
||||
if [[ $valid_certs == *"1)"* ]]; then
|
||||
first_valid_cert=$(echo $valid_certs | sed 's/ \".*//' | sed 's/.* //')
|
||||
echo $first_valid_cert
|
||||
|
||||
@@ -189,8 +189,23 @@ void Notification::NotificationFailed(const std::string& error) {
|
||||
|
||||
void Notification::NotificationDestroyed() {}
|
||||
|
||||
void Notification::NotificationClosed() {
|
||||
Emit("close");
|
||||
void Notification::NotificationClosed(const std::string& reason) {
|
||||
if (reason.empty()) {
|
||||
Emit("close");
|
||||
} else {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
gin_helper::internal::Event* event =
|
||||
gin_helper::internal::Event::New(isolate);
|
||||
v8::Local<v8::Object> event_object =
|
||||
event->GetWrapper(isolate).ToLocalChecked();
|
||||
|
||||
gin_helper::Dictionary dict(isolate, event_object);
|
||||
dict.Set("reason", reason);
|
||||
|
||||
EmitWithoutEvent("close", event_object);
|
||||
}
|
||||
}
|
||||
|
||||
void Notification::Close() {
|
||||
|
||||
@@ -50,7 +50,7 @@ class Notification final : public gin_helper::DeprecatedWrappable<Notification>,
|
||||
void NotificationReplied(const std::string& reply) override;
|
||||
void NotificationDisplayed() override;
|
||||
void NotificationDestroyed() override;
|
||||
void NotificationClosed() override;
|
||||
void NotificationClosed(const std::string& reason) override;
|
||||
void NotificationFailed(const std::string& error) override;
|
||||
|
||||
// gin_helper::Wrappable
|
||||
|
||||
@@ -83,13 +83,17 @@ void WebContentsView::ApplyBorderRadius() {
|
||||
|
||||
int WebContentsView::NonClientHitTest(const gfx::Point& point) {
|
||||
if (api_web_contents_) {
|
||||
auto* iwc = api_web_contents_->inspectable_web_contents();
|
||||
if (!iwc)
|
||||
return HTNOWHERE;
|
||||
// Convert the point to the contents view's coordinate space rather than
|
||||
// the InspectableWebContentsView's coordinate space, because the draggable
|
||||
// region is relative to the web content area. When DevTools is docked
|
||||
// (e.g. to the left), the contents view is offset within the parent,
|
||||
// so we need to account for that offset.
|
||||
auto* inspectable_view =
|
||||
api_web_contents_->inspectable_web_contents()->GetView();
|
||||
auto* inspectable_view = iwc->GetView();
|
||||
if (!inspectable_view)
|
||||
return HTNOWHERE;
|
||||
auto* contents_view = inspectable_view->GetContentsView();
|
||||
gfx::Point local_point(point);
|
||||
views::View::ConvertPointFromWidget(contents_view, &local_point);
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "shell/browser/ui/win/jump_list.h"
|
||||
#include "shell/browser/window_list.h"
|
||||
#include "shell/common/application_info.h"
|
||||
#include "shell/common/command_line_util_win.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "shell/common/gin_converters/image_converter.h"
|
||||
#include "shell/common/gin_converters/login_item_settings_converter.h"
|
||||
@@ -79,13 +80,22 @@ bool GetProtocolLaunchPath(gin::Arguments* args, std::wstring* exe) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Strip surrounding double quotes before re-quoting with AddQuoteForArg.
|
||||
if (exe->size() >= 2 && exe->front() == L'"' && exe->back() == L'"') {
|
||||
*exe = exe->substr(1, exe->size() - 2);
|
||||
}
|
||||
|
||||
// Read in optional args arg
|
||||
std::vector<std::wstring> launch_args;
|
||||
if (args->GetNext(&launch_args) && !launch_args.empty()) {
|
||||
std::wstring joined_args = base::JoinString(launch_args, L"\" \"");
|
||||
*exe = base::StrCat({L"\"", *exe, L"\" \"", joined_args, L"\" \"%1\""});
|
||||
std::wstring result = electron::AddQuoteForArg(*exe);
|
||||
for (const auto& arg : launch_args) {
|
||||
result += L' ';
|
||||
result += electron::AddQuoteForArg(arg);
|
||||
}
|
||||
*exe = base::StrCat({result, L" \"%1\""});
|
||||
} else {
|
||||
*exe = base::StrCat({L"\"", *exe, L"\" \"%1\""});
|
||||
*exe = base::StrCat({electron::AddQuoteForArg(*exe), L" \"%1\""});
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -153,9 +163,18 @@ bool FormatCommandLineString(std::wstring* exe,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Strip surrounding double quotes before re-quoting with AddQuoteForArg.
|
||||
if (exe->size() >= 2 && exe->front() == L'"' && exe->back() == L'"') {
|
||||
*exe = exe->substr(1, exe->size() - 2);
|
||||
}
|
||||
|
||||
*exe = electron::AddQuoteForArg(*exe);
|
||||
|
||||
if (!launch_args.empty()) {
|
||||
std::u16string joined_launch_args = base::JoinString(launch_args, u" ");
|
||||
*exe = base::StrCat({*exe, L" ", base::AsWStringView(joined_launch_args)});
|
||||
for (const auto& arg : launch_args) {
|
||||
*exe += L' ';
|
||||
*exe += electron::AddQuoteForArg(std::wstring(base::AsWStringView(arg)));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -46,9 +46,10 @@ void Notification::NotificationClicked() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Notification::NotificationDismissed(bool should_destroy) {
|
||||
void Notification::NotificationDismissed(bool should_destroy,
|
||||
const std::string& close_reason) {
|
||||
if (delegate())
|
||||
delegate()->NotificationClosed();
|
||||
delegate()->NotificationClosed(close_reason);
|
||||
|
||||
set_is_dismissed(true);
|
||||
|
||||
|
||||
@@ -76,7 +76,8 @@ class Notification {
|
||||
|
||||
// Should be called by derived classes.
|
||||
void NotificationClicked();
|
||||
void NotificationDismissed(bool should_destroy = true);
|
||||
void NotificationDismissed(bool should_destroy = true,
|
||||
const std::string& close_reason = "");
|
||||
void NotificationFailed(const std::string& error = "");
|
||||
|
||||
// delete this.
|
||||
|
||||
@@ -24,7 +24,7 @@ class NotificationDelegate {
|
||||
virtual void NotificationAction(int action_index, int selection_index = -1) {}
|
||||
|
||||
virtual void NotificationClick() {}
|
||||
virtual void NotificationClosed() {}
|
||||
virtual void NotificationClosed(const std::string& reason = "") {}
|
||||
virtual void NotificationDisplayed() {}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -60,7 +60,7 @@ class NotificationDelegateImpl final : public electron::NotificationDelegate {
|
||||
->DispatchNonPersistentClickEvent(notification_id_, base::DoNothing());
|
||||
}
|
||||
|
||||
void NotificationClosed() override {
|
||||
void NotificationClosed(const std::string& reason) override {
|
||||
content::NotificationEventDispatcher::GetInstance()
|
||||
->DispatchNonPersistentCloseEvent(notification_id_, base::DoNothing());
|
||||
}
|
||||
|
||||
@@ -800,9 +800,24 @@ IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
winui::Notifications::IToastNotification* sender,
|
||||
winui::Notifications::IToastDismissedEventArgs* e) {
|
||||
winui::Notifications::ToastDismissalReason reason;
|
||||
std::string reason_string;
|
||||
if (SUCCEEDED(e->get_Reason(&reason))) {
|
||||
switch (reason) {
|
||||
case winui::Notifications::ToastDismissalReason_UserCanceled:
|
||||
reason_string = "userCanceled";
|
||||
break;
|
||||
case winui::Notifications::ToastDismissalReason_ApplicationHidden:
|
||||
reason_string = "applicationHidden";
|
||||
break;
|
||||
case winui::Notifications::ToastDismissalReason_TimedOut:
|
||||
reason_string = "timedOut";
|
||||
break;
|
||||
}
|
||||
}
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE, base::BindOnce(&Notification::NotificationDismissed,
|
||||
notification_, false));
|
||||
notification_, false, reason_string));
|
||||
DebugLog("Notification dismissed");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "base/win/scoped_handle.h"
|
||||
#include "sandbox/win/src/nt_internals.h"
|
||||
#include "sandbox/win/src/win_utils.h"
|
||||
#include "shell/common/command_line_util_win.h"
|
||||
|
||||
namespace relauncher::internal {
|
||||
|
||||
@@ -50,49 +51,6 @@ HANDLE GetParentProcessHandle(base::ProcessHandle handle) {
|
||||
return ::OpenProcess(PROCESS_ALL_ACCESS, TRUE, ppid);
|
||||
}
|
||||
|
||||
StringType AddQuoteForArg(const StringType& arg) {
|
||||
// We follow the quoting rules of CommandLineToArgvW.
|
||||
// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
||||
std::wstring quotable_chars(L" \\\"");
|
||||
if (arg.find_first_of(quotable_chars) == std::wstring::npos) {
|
||||
// No quoting necessary.
|
||||
return arg;
|
||||
}
|
||||
|
||||
std::wstring out;
|
||||
out.push_back(L'"');
|
||||
for (size_t i = 0; i < arg.size(); ++i) {
|
||||
if (arg[i] == '\\') {
|
||||
// Find the extent of this run of backslashes.
|
||||
size_t start = i, end = start + 1;
|
||||
for (; end < arg.size() && arg[end] == '\\'; ++end) {
|
||||
}
|
||||
size_t backslash_count = end - start;
|
||||
|
||||
// Backslashes are escapes only if the run is followed by a double quote.
|
||||
// Since we also will end the string with a double quote, we escape for
|
||||
// either a double quote or the end of the string.
|
||||
if (end == arg.size() || arg[end] == '"') {
|
||||
// To quote, we need to output 2x as many backslashes.
|
||||
backslash_count *= 2;
|
||||
}
|
||||
for (size_t j = 0; j < backslash_count; ++j)
|
||||
out.push_back('\\');
|
||||
|
||||
// Advance i to one before the end to balance i++ in loop.
|
||||
i = end - 1;
|
||||
} else if (arg[i] == '"') {
|
||||
out.push_back('\\');
|
||||
out.push_back('"');
|
||||
} else {
|
||||
out.push_back(arg[i]);
|
||||
}
|
||||
}
|
||||
out.push_back('"');
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StringType GetWaitEventName(base::ProcessId pid) {
|
||||
@@ -105,7 +63,7 @@ StringType ArgvToCommandLineString(const StringVector& argv) {
|
||||
for (const StringType& arg : argv) {
|
||||
if (!command_line.empty())
|
||||
command_line += L' ';
|
||||
command_line += AddQuoteForArg(arg);
|
||||
command_line += electron::AddQuoteForArg(arg);
|
||||
}
|
||||
return command_line;
|
||||
}
|
||||
|
||||
@@ -270,34 +270,10 @@ void Relaunch(NSString* destinationPath) {
|
||||
}
|
||||
|
||||
bool Trash(NSString* path) {
|
||||
bool result = false;
|
||||
|
||||
if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_8) {
|
||||
result = [[NSFileManager defaultManager]
|
||||
trashItemAtURL:[NSURL fileURLWithPath:path]
|
||||
resultingItemURL:nil
|
||||
error:nil];
|
||||
}
|
||||
|
||||
// As a last resort try trashing with AppleScript.
|
||||
// This allows us to trash the app in macOS Sierra even when the app is
|
||||
// running inside an app translocation image.
|
||||
if (!result) {
|
||||
auto* code = R"str(
|
||||
set theFile to POSIX file "%@"
|
||||
tell application "Finder"
|
||||
move theFile to trash
|
||||
end tell
|
||||
)str";
|
||||
NSAppleScript* appleScript = [[NSAppleScript alloc]
|
||||
initWithSource:[NSString stringWithFormat:@(code), path]];
|
||||
NSDictionary* errorDict = nil;
|
||||
NSAppleEventDescriptor* scriptResult =
|
||||
[appleScript executeAndReturnError:&errorDict];
|
||||
result = (scriptResult != nil);
|
||||
}
|
||||
|
||||
return result;
|
||||
return [[NSFileManager defaultManager]
|
||||
trashItemAtURL:[NSURL fileURLWithPath:path]
|
||||
resultingItemURL:nil
|
||||
error:nil];
|
||||
}
|
||||
|
||||
bool DeleteOrTrash(NSString* path) {
|
||||
|
||||
@@ -343,6 +343,10 @@ InspectableWebContents::InspectableWebContents(
|
||||
}
|
||||
|
||||
InspectableWebContents::~InspectableWebContents() {
|
||||
// Explicitly destroy the view first to ensure that any callbacks triggered
|
||||
// during view teardown (like NonClientHitTest) does not access a
|
||||
// partially-destroyed view.
|
||||
view_.reset();
|
||||
// Unsubscribe from devtools and Clean up resources.
|
||||
if (GetDevToolsWebContents())
|
||||
WebContentsDestroyed();
|
||||
|
||||
54
shell/common/command_line_util_win.cc
Normal file
54
shell/common/command_line_util_win.cc
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2026 Microsoft GmbH.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/common/command_line_util_win.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace electron {
|
||||
|
||||
std::wstring AddQuoteForArg(const std::wstring& arg) {
|
||||
// We follow the quoting rules of CommandLineToArgvW.
|
||||
// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
||||
constexpr wchar_t kQuotableChars[] = L" \\\"";
|
||||
if (arg.find_first_of(kQuotableChars) == std::wstring::npos) {
|
||||
// No quoting necessary.
|
||||
return arg;
|
||||
}
|
||||
|
||||
std::wstring out;
|
||||
out.push_back(L'"');
|
||||
for (size_t i = 0; i < arg.size(); ++i) {
|
||||
if (arg[i] == '\\') {
|
||||
// Find the extent of this run of backslashes.
|
||||
size_t start = i, end = start + 1;
|
||||
for (; end < arg.size() && arg[end] == '\\'; ++end) {
|
||||
}
|
||||
size_t backslash_count = end - start;
|
||||
|
||||
// Backslashes are escapes only if the run is followed by a double quote.
|
||||
// Since we also will end the string with a double quote, we escape for
|
||||
// either a double quote or the end of the string.
|
||||
if (end == arg.size() || arg[end] == '"') {
|
||||
// To quote, we need to output 2x as many backslashes.
|
||||
backslash_count *= 2;
|
||||
}
|
||||
for (size_t j = 0; j < backslash_count; ++j)
|
||||
out.push_back('\\');
|
||||
|
||||
// Advance i to one before the end to balance i++ in loop.
|
||||
i = end - 1;
|
||||
} else if (arg[i] == '"') {
|
||||
out.push_back('\\');
|
||||
out.push_back('"');
|
||||
} else {
|
||||
out.push_back(arg[i]);
|
||||
}
|
||||
}
|
||||
out.push_back('"');
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
20
shell/common/command_line_util_win.h
Normal file
20
shell/common/command_line_util_win.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2026 Microsoft GmbH.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_COMMON_COMMAND_LINE_UTIL_WIN_H_
|
||||
#define ELECTRON_SHELL_COMMON_COMMAND_LINE_UTIL_WIN_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace electron {
|
||||
|
||||
// Quotes |arg| using CommandLineToArgvW-compatible quoting rules so that
|
||||
// the argument round-trips correctly through CreateProcess →
|
||||
// CommandLineToArgvW. If no quoting is necessary the string is returned
|
||||
// unchanged. See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
||||
std::wstring AddQuoteForArg(const std::wstring& arg);
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_COMMON_COMMAND_LINE_UTIL_WIN_H_
|
||||
@@ -56,29 +56,6 @@ ifdescribe(!process.mas)('autoUpdater module', function () {
|
||||
).to.throw('Expected options object to contain a \'url\' string property in setFeedUrl call');
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe(process.platform === 'darwin' && process.arch !== 'arm64')('on Mac', function () {
|
||||
it('emits an error when the application is unsigned', async () => {
|
||||
const errorEvent = once(autoUpdater, 'error') as Promise<[Error]>;
|
||||
autoUpdater.setFeedURL({ url: '' });
|
||||
const [error] = await errorEvent;
|
||||
expect(error.message).equal('Could not get code signature for running application');
|
||||
});
|
||||
|
||||
it('does not throw if default is the serverType', () => {
|
||||
// "Could not get code signature..." means the function got far enough to validate that serverType was OK.
|
||||
expect(() => autoUpdater.setFeedURL({ url: '', serverType: 'default' })).to.throw('Could not get code signature for running application');
|
||||
});
|
||||
|
||||
it('does not throw if json is the serverType', () => {
|
||||
// "Could not get code signature..." means the function got far enough to validate that serverType was OK.
|
||||
expect(() => autoUpdater.setFeedURL({ url: '', serverType: 'json' })).to.throw('Could not get code signature for running application');
|
||||
});
|
||||
|
||||
it('does throw if an unknown string is the serverType', () => {
|
||||
expect(() => autoUpdater.setFeedURL({ url: '', serverType: 'weow' as any })).to.throw('Expected serverType to be \'default\' or \'json\'');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('quitAndInstall', () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { AddressInfo } from 'node:net';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { copyMacOSFixtureApp, getCodesignIdentity, shouldRunCodesignTests, signApp, spawn } from './lib/codesign-helpers';
|
||||
import { copyMacOSFixtureApp, getCodesignIdentity, shouldRunCodesignTests, signApp, spawn, unsignApp } from './lib/codesign-helpers';
|
||||
import { withTempDirectory } from './lib/fs-helpers';
|
||||
import { ifdescribe, ifit } from './lib/spec-helpers';
|
||||
|
||||
@@ -146,6 +146,7 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
|
||||
ifit(process.arch !== 'arm64')('should fail to set the feed URL when the app is not signed', async () => {
|
||||
await withTempDirectory(async (dir) => {
|
||||
const appPath = await copyMacOSFixtureApp(dir);
|
||||
await unsignApp(appPath);
|
||||
const launchResult = await launchApp(appPath, ['http://myupdate']);
|
||||
console.log(launchResult);
|
||||
expect(launchResult.code).to.equal(1);
|
||||
@@ -153,6 +154,35 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
|
||||
});
|
||||
});
|
||||
|
||||
ifit(process.arch !== 'arm64')('should fail with code signature error when serverType is default and app is unsigned', async () => {
|
||||
await withTempDirectory(async (dir) => {
|
||||
const appPath = await copyMacOSFixtureApp(dir);
|
||||
await unsignApp(appPath);
|
||||
const launchResult = await launchApp(appPath, ['', 'default']);
|
||||
expect(launchResult.code).to.equal(1);
|
||||
expect(launchResult.out).to.include('Could not get code signature for running application');
|
||||
});
|
||||
});
|
||||
|
||||
ifit(process.arch !== 'arm64')('should fail with code signature error when serverType is json and app is unsigned', async () => {
|
||||
await withTempDirectory(async (dir) => {
|
||||
const appPath = await copyMacOSFixtureApp(dir);
|
||||
await unsignApp(appPath);
|
||||
const launchResult = await launchApp(appPath, ['', 'json']);
|
||||
expect(launchResult.code).to.equal(1);
|
||||
expect(launchResult.out).to.include('Could not get code signature for running application');
|
||||
});
|
||||
});
|
||||
|
||||
ifit(process.arch !== 'arm64')('should fail with serverType error when an invalid serverType is provided', async () => {
|
||||
await withTempDirectory(async (dir) => {
|
||||
const appPath = await copyMacOSFixtureApp(dir);
|
||||
const launchResult = await launchApp(appPath, ['', 'weow']);
|
||||
expect(launchResult.code).to.equal(1);
|
||||
expect(launchResult.out).to.include("Expected serverType to be 'default' or 'json'");
|
||||
});
|
||||
});
|
||||
|
||||
it('should cleanly set the feed URL when the app is signed', async () => {
|
||||
await withTempDirectory(async (dir) => {
|
||||
const appPath = await copyMacOSFixtureApp(dir);
|
||||
|
||||
11
spec/fixtures/auto-update/initial/index.js
vendored
11
spec/fixtures/auto-update/initial/index.js
vendored
@@ -6,13 +6,18 @@ process.on('uncaughtException', (err) => {
|
||||
const { autoUpdater } = require('electron');
|
||||
|
||||
const feedUrl = process.argv[1];
|
||||
const serverType = process.argv[2];
|
||||
|
||||
console.log('Setting Feed URL');
|
||||
|
||||
autoUpdater.setFeedURL({
|
||||
url: feedUrl
|
||||
});
|
||||
const opts = { url: feedUrl };
|
||||
if (serverType) {
|
||||
opts.serverType = serverType;
|
||||
}
|
||||
|
||||
autoUpdater.setFeedURL(opts);
|
||||
|
||||
console.log('Feed URL Set:', feedUrl);
|
||||
console.log('Server Type Set:', serverType);
|
||||
|
||||
process.exit(0);
|
||||
|
||||
26
spec/fixtures/crash-cases/frameless-windows-close/index.js
vendored
Normal file
26
spec/fixtures/crash-cases/frameless-windows-close/index.js
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
const { app, BrowserWindow } = require('electron');
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.whenReady().then(() => {
|
||||
let win = new BrowserWindow({
|
||||
frame: false,
|
||||
show: true
|
||||
});
|
||||
|
||||
win.loadURL('about:blank');
|
||||
|
||||
setTimeout(() => {
|
||||
win.close();
|
||||
}, 5000);
|
||||
|
||||
win.on('closed', () => {
|
||||
win = null;
|
||||
});
|
||||
});
|
||||
@@ -7,11 +7,8 @@ import * as path from 'node:path';
|
||||
const features = process._linkedBinding('electron_common_features');
|
||||
const fixturesPath = path.resolve(__dirname, '..', 'fixtures');
|
||||
|
||||
// Re-enable codesign tests for macOS x64
|
||||
// Refs https://github.com/electron/electron/issues/48182
|
||||
export const shouldRunCodesignTests =
|
||||
process.platform === 'darwin' &&
|
||||
!(process.env.CI) &&
|
||||
!process.mas &&
|
||||
!features.isComponentBuild();
|
||||
|
||||
@@ -83,3 +80,7 @@ export function spawn (cmd: string, args: string[], opts: any = {}) {
|
||||
export function signApp (appPath: string, identity: string) {
|
||||
return spawn('codesign', ['-s', identity, '--deep', '--force', appPath]);
|
||||
};
|
||||
|
||||
export function unsignApp (appPath: string) {
|
||||
return spawn('codesign', ['--remove-signature', '--deep', appPath]);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user