mirror of
https://github.com/electron/electron.git
synced 2026-10-28 11:44:46 +08:00
* chore: bump chromium in DEPS to 158.0.8116.0 * 8527713: [Linux] Persist offered GlobalShortcuts portal shortcuts Upstream BindShortcuts() now looks commands up with find() and falls back to persisted offered shortcuts whose owner isn't loaded. Moved the pending bind group tracking into the found branch so unloaded shortcuts don't resolve a registration. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8527713 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * 8618876: Add compromised eMudhra intermediates to blocklist. Upstream now ships the same eight SPKI hashes in cert_verify_proc_blocklist.inc, so the Electron patch carrying them is no longer needed. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8618876 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore: update patches * 8615507: Use ChildProcessId in ContentBrowserClient::SelectClientCertificate() Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8615507 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * 8617940: Roll hunspell_dictionaries and update dictionary versions Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8617940 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * 8581764: DynamicPatching: Decouple //content from ChildModule Ref: https://chromium-review.googlesource.com/c/chromium/src/+/8581764 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: electron-claude[bot] <279139701+electron-claude[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2305 lines
81 KiB
C++
2305 lines
81 KiB
C++
// Copyright (c) 2013 GitHub, Inc.
|
|
// Use of this source code is governed by the MIT license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "shell/browser/api/electron_api_app.h"
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/containers/fixed_flat_map.h"
|
|
#include "base/containers/span.h"
|
|
#include "base/environment.h"
|
|
#include "base/files/file_path.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/functional/callback_helpers.h"
|
|
#include "base/notimplemented.h"
|
|
#include "base/path_service.h"
|
|
#include "base/process/process.h"
|
|
#include "base/system/sys_info.h"
|
|
#include "base/values.h"
|
|
#include "base/win/windows_version.h"
|
|
#include "chrome/browser/browser_process.h"
|
|
#include "chrome/browser/icon_manager.h"
|
|
#include "chrome/common/chrome_features.h"
|
|
#include "chrome/common/chrome_paths.h"
|
|
#include "components/prefs/value_map_pref_store.h"
|
|
#include "components/proxy_config/proxy_config_dictionary.h"
|
|
#include "components/proxy_config/proxy_config_pref_names.h"
|
|
#include "components/proxy_config/proxy_prefs.h"
|
|
#include "content/browser/gpu/compositor_util.h" // nogncheck
|
|
#include "content/browser/gpu/gpu_data_manager_impl.h" // nogncheck
|
|
#include "content/public/browser/browser_accessibility_state.h"
|
|
#include "content/public/browser/browser_child_process_host.h"
|
|
#include "content/public/browser/browser_thread.h"
|
|
#include "content/public/browser/child_process_data.h"
|
|
#include "content/public/browser/client_certificate_delegate.h"
|
|
#include "content/public/browser/gpu_data_manager.h"
|
|
#include "content/public/browser/network_service_instance.h"
|
|
#include "content/public/browser/render_frame_host.h"
|
|
#include "content/public/common/child_process_id.h"
|
|
#include "content/public/common/content_switches.h"
|
|
#include "crypto/crypto_buildflags.h"
|
|
#include "electron/mas.h"
|
|
#include "media/audio/audio_manager.h"
|
|
#include "net/dns/public/dns_over_https_config.h"
|
|
#include "net/dns/public/dns_over_https_server_config.h"
|
|
#include "net/dns/public/insecure_dns_mode.h"
|
|
#include "net/dns/public/util.h"
|
|
#include "net/ssl/client_cert_identity.h"
|
|
#include "net/ssl/ssl_cert_request_info.h"
|
|
#include "net/ssl/ssl_private_key.h"
|
|
#include "sandbox/policy/switches.h"
|
|
#include "services/network/network_service.h"
|
|
#include "shell/app/command_line_args.h"
|
|
#include "shell/browser/api/electron_api_event_emitter.h"
|
|
#include "shell/browser/api/electron_api_menu.h"
|
|
#include "shell/browser/api/electron_api_utility_process.h"
|
|
#include "shell/browser/api/electron_api_web_contents.h"
|
|
#include "shell/browser/api/gpuinfo_manager.h"
|
|
#include "shell/browser/api/process_metric.h"
|
|
#include "shell/browser/app_package.h"
|
|
#include "shell/browser/browser_process_impl.h"
|
|
#include "shell/browser/electron_browser_main_parts.h"
|
|
#include "shell/browser/javascript_environment.h"
|
|
#include "shell/browser/net/resolve_proxy_helper.h"
|
|
#include "shell/browser/relauncher.h"
|
|
#include "shell/common/api/electron_api_command_line.h"
|
|
#include "shell/common/application_info.h"
|
|
#include "shell/common/callback_util.h"
|
|
#include "shell/common/electron_command_line.h"
|
|
#include "shell/common/electron_paths.h"
|
|
#include "shell/common/gin_converters/base_converter.h"
|
|
#include "shell/common/gin_converters/blink_converter.h"
|
|
#include "shell/common/gin_converters/callback_converter.h"
|
|
#include "shell/common/gin_converters/content_converter.h"
|
|
#include "shell/common/gin_converters/file_path_converter.h"
|
|
#include "shell/common/gin_converters/gurl_converter.h"
|
|
#include "shell/common/gin_converters/image_converter.h"
|
|
#include "shell/common/gin_converters/login_item_settings_converter.h"
|
|
#include "shell/common/gin_converters/net_converter.h"
|
|
#include "shell/common/gin_converters/value_converter.h"
|
|
#include "shell/common/gin_helper/dictionary.h"
|
|
#include "shell/common/gin_helper/error_thrower.h"
|
|
#include "shell/common/gin_helper/event.h"
|
|
#include "shell/common/gin_helper/handle.h"
|
|
#include "shell/common/gin_helper/object_template_builder.h"
|
|
#include "shell/common/gin_helper/promise.h"
|
|
#include "shell/common/gin_helper/wrappable_pointer_tags.h"
|
|
#include "shell/common/language_util.h"
|
|
#include "shell/common/node_includes.h"
|
|
#include "shell/common/options_switches.h"
|
|
#include "shell/common/thread_restrictions.h"
|
|
#include "shell/common/v8_util.h"
|
|
#include "ui/gfx/image/image.h"
|
|
#include "v8/include/cppgc/allocation.h"
|
|
#include "v8/include/v8-traced-handle.h"
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "base/win/win_util.h"
|
|
#include "shell/browser/notifications/win/windows_toast_activator.h"
|
|
#include "shell/browser/ui/win/jump_list.h"
|
|
#endif
|
|
|
|
#if BUILDFLAG(IS_MAC)
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include "base/apple/scoped_cftyperef.h"
|
|
#include "base/no_destructor.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "content/browser/mac_helpers.h"
|
|
#include "device/fido/strings/grit/fido_strings.h"
|
|
#include "shell/browser/electron_child_process_host_flags.h"
|
|
#include "shell/browser/ui/cocoa/electron_bundle_mover.h"
|
|
#include "shell/browser/webauthn/electron_authenticator_request_delegate.h"
|
|
#include "shell/common/process_util.h"
|
|
#include "ui/base/resource/resource_bundle.h"
|
|
#endif
|
|
|
|
#if BUILDFLAG(IS_LINUX)
|
|
#include "base/nix/scoped_xdg_activation_token_injector.h"
|
|
#include "base/nix/xdg_util.h"
|
|
#endif
|
|
|
|
using electron::Browser;
|
|
|
|
namespace gin {
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
template <>
|
|
struct Converter<electron::ProcessIntegrityLevel> {
|
|
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
|
electron::ProcessIntegrityLevel value) {
|
|
switch (value) {
|
|
case electron::ProcessIntegrityLevel::kUntrusted:
|
|
return StringToV8(isolate, "untrusted");
|
|
case electron::ProcessIntegrityLevel::kLow:
|
|
return StringToV8(isolate, "low");
|
|
case electron::ProcessIntegrityLevel::kMedium:
|
|
return StringToV8(isolate, "medium");
|
|
case electron::ProcessIntegrityLevel::kHigh:
|
|
return StringToV8(isolate, "high");
|
|
default:
|
|
return StringToV8(isolate, "unknown");
|
|
}
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct Converter<Browser::UserTask> {
|
|
static bool FromV8(v8::Isolate* isolate,
|
|
v8::Local<v8::Value> val,
|
|
Browser::UserTask* out) {
|
|
gin_helper::Dictionary dict;
|
|
if (!ConvertFromV8(isolate, val, &dict))
|
|
return false;
|
|
if (!dict.Get("program", &(out->program)) ||
|
|
!dict.Get("title", &(out->title)))
|
|
return false;
|
|
if (dict.Get("iconPath", &(out->icon_path)) &&
|
|
!dict.Get("iconIndex", &(out->icon_index)))
|
|
return false;
|
|
dict.Get("arguments", &(out->arguments));
|
|
dict.Get("description", &(out->description));
|
|
dict.Get("workingDirectory", &(out->working_dir));
|
|
return true;
|
|
}
|
|
};
|
|
|
|
using electron::JumpListCategory;
|
|
using electron::JumpListItem;
|
|
using electron::JumpListResult;
|
|
|
|
template <>
|
|
struct Converter<JumpListItem::Type> {
|
|
static bool FromV8(v8::Isolate* isolate,
|
|
v8::Local<v8::Value> val,
|
|
JumpListItem::Type* out) {
|
|
return FromV8WithLookup(isolate, val, Lookup, out);
|
|
}
|
|
|
|
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
|
JumpListItem::Type val) {
|
|
for (const auto& [name, item_val] : Lookup)
|
|
if (item_val == val)
|
|
return gin::ConvertToV8(isolate, name);
|
|
|
|
return v8::String::Empty(isolate);
|
|
}
|
|
|
|
private:
|
|
static constexpr auto Lookup =
|
|
base::MakeFixedFlatMap<std::string_view, JumpListItem::Type>({
|
|
{"file", JumpListItem::Type::kFile},
|
|
{"separator", JumpListItem::Type::kSeparator},
|
|
{"task", JumpListItem::Type::kTask},
|
|
});
|
|
};
|
|
|
|
template <>
|
|
struct Converter<JumpListItem> {
|
|
static bool FromV8(v8::Isolate* isolate,
|
|
v8::Local<v8::Value> val,
|
|
JumpListItem* out) {
|
|
gin_helper::Dictionary dict;
|
|
if (!ConvertFromV8(isolate, val, &dict))
|
|
return false;
|
|
|
|
if (!dict.Get("type", &(out->type)))
|
|
return false;
|
|
|
|
switch (out->type) {
|
|
case JumpListItem::Type::kTask:
|
|
if (!dict.Get("program", &(out->path)) ||
|
|
!dict.Get("title", &(out->title)))
|
|
return false;
|
|
|
|
if (dict.Get("iconPath", &(out->icon_path)) &&
|
|
!dict.Get("iconIndex", &(out->icon_index)))
|
|
return false;
|
|
|
|
dict.Get("args", &(out->arguments));
|
|
dict.Get("description", &(out->description));
|
|
dict.Get("workingDirectory", &(out->working_dir));
|
|
return true;
|
|
|
|
case JumpListItem::Type::kSeparator:
|
|
return true;
|
|
|
|
case JumpListItem::Type::kFile:
|
|
return dict.Get("path", &(out->path));
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
|
const JumpListItem& val) {
|
|
auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
|
|
dict.Set("type", val.type);
|
|
|
|
switch (val.type) {
|
|
case JumpListItem::Type::kTask:
|
|
dict.Set("program", val.path);
|
|
dict.Set("args", val.arguments);
|
|
dict.Set("title", val.title);
|
|
dict.Set("iconPath", val.icon_path);
|
|
dict.Set("iconIndex", val.icon_index);
|
|
dict.Set("description", val.description);
|
|
dict.Set("workingDirectory", val.working_dir);
|
|
break;
|
|
|
|
case JumpListItem::Type::kSeparator:
|
|
break;
|
|
|
|
case JumpListItem::Type::kFile:
|
|
dict.Set("path", val.path);
|
|
break;
|
|
}
|
|
return dict.GetHandle();
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct Converter<JumpListCategory::Type> {
|
|
static bool FromV8(v8::Isolate* isolate,
|
|
v8::Local<v8::Value> val,
|
|
JumpListCategory::Type* out) {
|
|
return FromV8WithLookup(isolate, val, Lookup, out);
|
|
}
|
|
|
|
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
|
JumpListCategory::Type val) {
|
|
for (const auto& [name, type_val] : Lookup)
|
|
if (type_val == val)
|
|
return gin::ConvertToV8(isolate, name);
|
|
|
|
return v8::String::Empty(isolate);
|
|
}
|
|
|
|
private:
|
|
static constexpr auto Lookup =
|
|
base::MakeFixedFlatMap<std::string_view, JumpListCategory::Type>({
|
|
{"custom", JumpListCategory::Type::kCustom},
|
|
{"frequent", JumpListCategory::Type::kFrequent},
|
|
{"recent", JumpListCategory::Type::kRecent},
|
|
{"tasks", JumpListCategory::Type::kTasks},
|
|
});
|
|
};
|
|
|
|
template <>
|
|
struct Converter<JumpListCategory> {
|
|
static bool FromV8(v8::Isolate* isolate,
|
|
v8::Local<v8::Value> val,
|
|
JumpListCategory* out) {
|
|
gin_helper::Dictionary dict;
|
|
if (!ConvertFromV8(isolate, val, &dict))
|
|
return false;
|
|
|
|
if (dict.Get("name", &(out->name)) && out->name.empty())
|
|
return false;
|
|
|
|
if (!dict.Get("type", &(out->type))) {
|
|
if (out->name.empty())
|
|
out->type = JumpListCategory::Type::kTasks;
|
|
else
|
|
out->type = JumpListCategory::Type::kCustom;
|
|
}
|
|
|
|
if ((out->type == JumpListCategory::Type::kTasks) ||
|
|
(out->type == JumpListCategory::Type::kCustom)) {
|
|
if (!dict.Get("items", &(out->items)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// static
|
|
template <>
|
|
struct Converter<JumpListResult> {
|
|
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, JumpListResult val) {
|
|
std::string result_code;
|
|
switch (val) {
|
|
case JumpListResult::kSuccess:
|
|
result_code = "ok";
|
|
break;
|
|
|
|
case JumpListResult::kArgumentError:
|
|
result_code = "argumentError";
|
|
break;
|
|
|
|
case JumpListResult::kGenericError:
|
|
result_code = "error";
|
|
break;
|
|
|
|
case JumpListResult::kCustomCategorySeparatorError:
|
|
result_code = "invalidSeparatorError";
|
|
break;
|
|
|
|
case JumpListResult::kMissingFileTypeRegistrationError:
|
|
result_code = "fileTypeRegistrationError";
|
|
break;
|
|
|
|
case JumpListResult::kCustomCategoryAccessDeniedError:
|
|
result_code = "customCategoryAccessDeniedError";
|
|
break;
|
|
}
|
|
return ConvertToV8(isolate, result_code);
|
|
}
|
|
};
|
|
#endif
|
|
|
|
template <>
|
|
struct Converter<content::CertificateRequestResultType> {
|
|
static bool FromV8(v8::Isolate* isolate,
|
|
v8::Local<v8::Value> val,
|
|
content::CertificateRequestResultType* out) {
|
|
bool b;
|
|
if (!ConvertFromV8(isolate, val, &b))
|
|
return false;
|
|
*out = b ? content::CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE
|
|
: content::CERTIFICATE_REQUEST_RESULT_TYPE_DENY;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct Converter<net::SecureDnsMode> {
|
|
static bool FromV8(v8::Isolate* isolate,
|
|
v8::Local<v8::Value> val,
|
|
net::SecureDnsMode* out) {
|
|
static constexpr auto Lookup =
|
|
base::MakeFixedFlatMap<std::string_view, net::SecureDnsMode>({
|
|
{"automatic", net::SecureDnsMode::kAutomatic},
|
|
{"off", net::SecureDnsMode::kOff},
|
|
{"secure", net::SecureDnsMode::kSecure},
|
|
});
|
|
return FromV8WithLookup(isolate, val, Lookup, out);
|
|
}
|
|
};
|
|
} // namespace gin
|
|
|
|
namespace electron::api {
|
|
|
|
gin::WrapperInfo App::kWrapperInfo =
|
|
electron::MakeWrapperInfo(electron::kElectronApp);
|
|
|
|
namespace {
|
|
|
|
IconLoader::IconSize GetIconSizeByString(const std::string& size) {
|
|
if (size == "small") {
|
|
return IconLoader::IconSize::SMALL;
|
|
} else if (size == "large") {
|
|
return IconLoader::IconSize::LARGE;
|
|
}
|
|
return IconLoader::IconSize::NORMAL;
|
|
}
|
|
|
|
// Return the path constant from string.
|
|
int GetPathConstant(std::string_view name) {
|
|
// clang-format off
|
|
constexpr auto Lookup = base::MakeFixedFlatMap<std::string_view, int>({
|
|
{"appData", DIR_APP_DATA},
|
|
#if !BUILDFLAG(IS_MAC)
|
|
{"assets", base::DIR_ASSETS},
|
|
#endif
|
|
#if BUILDFLAG(IS_POSIX)
|
|
{"cache", base::DIR_CACHE},
|
|
#else
|
|
{"cache", base::DIR_ROAMING_APP_DATA},
|
|
#endif
|
|
{"crashDumps", DIR_CRASH_DUMPS},
|
|
{"desktop", base::DIR_USER_DESKTOP},
|
|
{"documents", chrome::DIR_USER_DOCUMENTS},
|
|
{"downloads", chrome::DIR_DEFAULT_DOWNLOADS},
|
|
{"exe", base::FILE_EXE},
|
|
{"home", base::DIR_HOME},
|
|
{"logs", DIR_APP_LOGS},
|
|
{"module", base::FILE_MODULE},
|
|
{"music", chrome::DIR_USER_MUSIC},
|
|
{"pictures", chrome::DIR_USER_PICTURES},
|
|
#if BUILDFLAG(IS_WIN)
|
|
{"recent", electron::DIR_RECENT},
|
|
#endif
|
|
{"sessionData", DIR_SESSION_DATA},
|
|
{"temp", base::DIR_TEMP},
|
|
{"userCache", DIR_USER_CACHE},
|
|
{"userData", chrome::DIR_USER_DATA},
|
|
{"userDesktop", base::DIR_USER_DESKTOP},
|
|
{"videos", chrome::DIR_USER_VIDEOS},
|
|
});
|
|
// clang-format on
|
|
auto iter = Lookup.find(name);
|
|
return iter != Lookup.end() ? iter->second : -1;
|
|
}
|
|
|
|
bool NotificationCallbackWrapper(
|
|
const base::RepeatingCallback<
|
|
void(base::CommandLine command_line,
|
|
const base::FilePath& current_directory,
|
|
const std::vector<uint8_t> additional_data)>& callback,
|
|
base::CommandLine cmd,
|
|
const base::FilePath& cwd,
|
|
const std::vector<uint8_t> additional_data) {
|
|
#if BUILDFLAG(IS_LINUX)
|
|
// Set the global activation token sent as a command line switch by another
|
|
// electron app instance. This also removes the switch after use to prevent
|
|
// any side effects of leaving it in the command line after this point.
|
|
if (std::optional<std::string> token =
|
|
base::nix::GetXdgActivationTokenFromCommandLine(cmd)) {
|
|
base::nix::SetActivationToken(std::move(*token));
|
|
cmd.RemoveSwitch(base::nix::kXdgActivationTokenSwitch);
|
|
}
|
|
#endif
|
|
// Make sure the callback is called after app gets ready.
|
|
if (Browser::Get()->is_ready()) {
|
|
callback.Run(std::move(cmd), cwd, std::move(additional_data));
|
|
} else {
|
|
scoped_refptr<base::SingleThreadTaskRunner> task_runner(
|
|
base::SingleThreadTaskRunner::GetCurrentDefault());
|
|
|
|
// Make a copy of the span so that the data isn't lost.
|
|
task_runner->PostTask(
|
|
FROM_HERE, base::BindOnce(base::IgnoreResult(callback), std::move(cmd),
|
|
cwd, std::move(additional_data)));
|
|
}
|
|
// ProcessSingleton needs to know whether current process is quitting.
|
|
return !Browser::Get()->is_shutting_down();
|
|
}
|
|
|
|
void GotPrivateKey(std::shared_ptr<content::ClientCertificateDelegate> delegate,
|
|
scoped_refptr<net::X509Certificate> cert,
|
|
scoped_refptr<net::SSLPrivateKey> private_key) {
|
|
delegate->ContinueWithCertificate(cert, private_key);
|
|
}
|
|
|
|
void OnClientCertificateSelected(
|
|
v8::Isolate* const isolate,
|
|
std::shared_ptr<content::ClientCertificateDelegate> delegate,
|
|
std::shared_ptr<net::ClientCertIdentityList> identities,
|
|
gin::Arguments* const args) {
|
|
v8::Local<v8::Value> val;
|
|
if (!args->GetNext(&val) || val.IsEmpty() || val->IsNullOrUndefined()) {
|
|
delegate->ContinueWithCertificate(nullptr, nullptr);
|
|
return;
|
|
}
|
|
|
|
gin_helper::Dictionary cert_data;
|
|
if (!gin::ConvertFromV8(isolate, val, &cert_data)) {
|
|
args->ThrowTypeError("Must pass valid certificate object.");
|
|
return;
|
|
}
|
|
|
|
std::string data;
|
|
if (!cert_data.Get("data", &data))
|
|
return;
|
|
|
|
auto certs = net::X509Certificate::CreateCertificateListFromBytes(
|
|
base::as_byte_span(data), net::X509Certificate::FORMAT_AUTO);
|
|
if (!certs.empty()) {
|
|
scoped_refptr<net::X509Certificate> cert(certs[0].get());
|
|
for (auto& identity : *identities) {
|
|
scoped_refptr<net::X509Certificate> identity_cert =
|
|
identity->certificate();
|
|
// Since |cert| was recreated from |data|, it won't include any
|
|
// intermediates. That's fine for checking equality, but once a match is
|
|
// found then |identity_cert| should be used since it will include the
|
|
// intermediates which would otherwise be lost.
|
|
if (cert->EqualsExcludingChain(identity_cert.get())) {
|
|
net::ClientCertIdentity::SelfOwningAcquirePrivateKey(
|
|
std::move(identity), base::BindRepeating(&GotPrivateKey, delegate,
|
|
std::move(identity_cert)));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if BUILDFLAG(USE_NSS_CERTS)
|
|
int ImportIntoCertStore(CertificateManagerModel* model, base::Value options) {
|
|
std::string file_data, cert_path;
|
|
std::u16string password;
|
|
net::ScopedCERTCertificateList imported_certs;
|
|
int rv = -1;
|
|
|
|
if (const base::DictValue* dict = options.GetIfDict(); dict != nullptr) {
|
|
if (const std::string* str = dict->FindString("certificate"); str)
|
|
cert_path = *str;
|
|
|
|
if (const std::string* str = dict->FindString("password"); str)
|
|
password = base::UTF8ToUTF16(*str);
|
|
}
|
|
|
|
if (!cert_path.empty()) {
|
|
if (base::ReadFileToString(base::FilePath(cert_path), &file_data)) {
|
|
auto module = model->cert_db()->GetPrivateSlot();
|
|
rv = model->ImportFromPKCS12(module.get(), file_data, password, true,
|
|
&imported_certs);
|
|
if (imported_certs.size() > 1) {
|
|
auto it = imported_certs.begin();
|
|
++it; // skip first which would be the client certificate.
|
|
for (; it != imported_certs.end(); ++it)
|
|
rv &= model->SetCertTrust(it->get(), net::CA_CERT,
|
|
net::NSSCertDatabase::TRUSTED_SSL);
|
|
}
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
void OnIconDataAvailable(gin_helper::Promise<gfx::Image> promise,
|
|
gfx::Image icon) {
|
|
if (!icon.IsEmpty()) {
|
|
promise.Resolve(icon);
|
|
} else {
|
|
promise.RejectWithErrorMessage("Failed to get file icon.");
|
|
}
|
|
}
|
|
|
|
#if !BUILDFLAG(IS_WIN)
|
|
void SetAppUserModelIdNoOp() {}
|
|
#endif
|
|
|
|
} // namespace
|
|
|
|
App::App() {
|
|
static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get())
|
|
->set_delegate(this);
|
|
Browser::Get()->AddObserver(this);
|
|
|
|
auto unsafe_pid = content::ChildProcessId::FromUnsafeValue(
|
|
content::ChildProcessHost::kInvalidUniqueID);
|
|
auto process_metric = std::make_unique<electron::ProcessMetric>(
|
|
content::PROCESS_TYPE_BROWSER, base::GetCurrentProcessHandle(),
|
|
base::ProcessMetrics::CreateCurrentProcessMetrics());
|
|
app_metrics_[unsafe_pid] = std::move(process_metric);
|
|
}
|
|
|
|
App::~App() {
|
|
static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get())
|
|
->set_delegate(nullptr);
|
|
content::GpuDataManager::GetInstance()->RemoveObserver(this);
|
|
content::BrowserChildProcessObserver::Remove(this);
|
|
}
|
|
|
|
void App::OnBeforeQuit(bool* prevent_default) {
|
|
if (Emit("before-quit")) {
|
|
*prevent_default = true;
|
|
}
|
|
}
|
|
|
|
void App::OnWillQuit(bool* prevent_default) {
|
|
if (Emit("will-quit")) {
|
|
*prevent_default = true;
|
|
}
|
|
}
|
|
|
|
void App::OnWindowAllClosed() {
|
|
Emit("window-all-closed");
|
|
}
|
|
|
|
void App::OnQuit() {
|
|
const int exitCode = ElectronBrowserMainParts::Get()->GetExitCode();
|
|
Emit("quit", exitCode);
|
|
|
|
if (process_singleton_) {
|
|
ScopedAllowBlockingForElectron allow_blocking;
|
|
process_singleton_->Cleanup();
|
|
process_singleton_.reset();
|
|
}
|
|
}
|
|
|
|
void App::OnOpenFile(bool* prevent_default, const std::string& file_path) {
|
|
if (Emit("open-file", file_path)) {
|
|
*prevent_default = true;
|
|
}
|
|
}
|
|
|
|
void App::OnOpenURL(const std::string& url) {
|
|
Emit("open-url", url);
|
|
}
|
|
|
|
void App::OnActivate(bool has_visible_windows) {
|
|
Emit("activate", has_visible_windows);
|
|
}
|
|
|
|
void App::OnWillFinishLaunching() {
|
|
Menu::InstallDefaultApplicationMenu(JavascriptEnvironment::GetIsolate());
|
|
Emit("will-finish-launching");
|
|
}
|
|
|
|
void App::OnFinishLaunching(base::DictValue launch_info) {
|
|
#if BUILDFLAG(IS_LINUX)
|
|
// Set the application name for audio streams shown in external
|
|
// applications. Only affects pulseaudio currently.
|
|
media::AudioManager::SetGlobalAppName(Browser::Get()->GetName());
|
|
#endif
|
|
Emit("ready", base::Value(std::move(launch_info)));
|
|
}
|
|
|
|
void App::OnPreMainMessageLoopRun() {
|
|
content::BrowserChildProcessObserver::Add(this);
|
|
if (process_singleton_ && watch_singleton_socket_on_ready_) {
|
|
process_singleton_->StartWatching();
|
|
watch_singleton_socket_on_ready_ = false;
|
|
}
|
|
}
|
|
|
|
void App::OnPreCreateThreads() {
|
|
DCHECK(!content::GpuDataManager::Initialized());
|
|
content::GpuDataManager* manager = content::GpuDataManager::GetInstance();
|
|
manager->AddObserver(this);
|
|
|
|
if (disable_hw_acceleration_) {
|
|
manager->DisableHardwareAcceleration();
|
|
}
|
|
|
|
if (disable_domain_blocking_for_3DAPIs_) {
|
|
content::GpuDataManagerImpl::GetInstance()
|
|
->DisableDomainBlockingFor3DAPIsForTesting();
|
|
}
|
|
}
|
|
|
|
void App::OnAccessibilitySupportChanged() {
|
|
Emit("accessibility-support-changed", IsAccessibilitySupportEnabled());
|
|
}
|
|
|
|
#if BUILDFLAG(IS_MAC)
|
|
void App::OnWillContinueUserActivity(bool* prevent_default,
|
|
const std::string& type) {
|
|
if (Emit("will-continue-activity", type)) {
|
|
*prevent_default = true;
|
|
}
|
|
}
|
|
|
|
void App::OnDidFailToContinueUserActivity(const std::string& type,
|
|
const std::string& error) {
|
|
Emit("continue-activity-error", type, error);
|
|
}
|
|
|
|
void App::OnContinueUserActivity(bool* prevent_default,
|
|
const std::string& type,
|
|
base::DictValue user_info,
|
|
base::DictValue details) {
|
|
if (Emit("continue-activity", type, base::Value(std::move(user_info)),
|
|
base::Value(std::move(details)))) {
|
|
*prevent_default = true;
|
|
}
|
|
}
|
|
|
|
void App::OnUserActivityWasContinued(const std::string& type,
|
|
base::DictValue user_info) {
|
|
Emit("activity-was-continued", type, base::Value(std::move(user_info)));
|
|
}
|
|
|
|
void App::OnUpdateUserActivityState(bool* prevent_default,
|
|
const std::string& type,
|
|
base::DictValue user_info) {
|
|
if (Emit("update-activity-state", type, base::Value(std::move(user_info)))) {
|
|
*prevent_default = true;
|
|
}
|
|
}
|
|
|
|
void App::OnNewWindowForTab() {
|
|
Emit("new-window-for-tab");
|
|
}
|
|
|
|
void App::OnDidBecomeActive() {
|
|
Emit("did-become-active");
|
|
}
|
|
|
|
void App::OnDidResignActive() {
|
|
Emit("did-resign-active");
|
|
}
|
|
#endif
|
|
|
|
bool App::CanCreateWindow(
|
|
content::RenderFrameHost* opener,
|
|
const GURL& opener_url,
|
|
const GURL& opener_top_level_frame_url,
|
|
const url::Origin& source_origin,
|
|
content::mojom::WindowContainerType container_type,
|
|
const GURL& target_url,
|
|
const content::Referrer& referrer,
|
|
const std::string& frame_name,
|
|
WindowOpenDisposition disposition,
|
|
const blink::mojom::WindowFeatures& features,
|
|
const std::string& raw_features,
|
|
const scoped_refptr<network::ResourceRequestBody>& body,
|
|
bool user_gesture,
|
|
bool opener_suppressed,
|
|
bool* no_javascript_access) {
|
|
// Only reached for openers Electron did not create (no
|
|
// WebContentsPreferences); those may not open windows.
|
|
return false;
|
|
}
|
|
|
|
void App::AllowCertificateError(
|
|
content::WebContents* web_contents,
|
|
int cert_error,
|
|
const net::SSLInfo& ssl_info,
|
|
const GURL& request_url,
|
|
bool is_main_frame_request,
|
|
bool strict_enforcement,
|
|
base::OnceCallback<void(content::CertificateRequestResultType)> callback) {
|
|
auto adapted_callback =
|
|
electron::AdaptCallbackForRepeating(std::move(callback));
|
|
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();
|
|
std::string error = net::ErrorToString(cert_error);
|
|
v8::Local<v8::Value> cert = gin::ConvertToV8(isolate, ssl_info.cert);
|
|
v8::Local<v8::Value> callback_value =
|
|
gin::ConvertToV8(isolate, adapted_callback);
|
|
// The same event is emitted on the WebContents, then on app.
|
|
if (WebContents* api_web_contents = WebContents::From(web_contents)) {
|
|
api_web_contents->EmitWithoutEvent("certificate-error", event_object,
|
|
request_url, error, cert, callback_value,
|
|
is_main_frame_request);
|
|
}
|
|
EmitWithoutEvent("certificate-error", event_object, web_contents, request_url,
|
|
error, cert, callback_value, is_main_frame_request);
|
|
|
|
// Deny the certificate by default.
|
|
if (!event->GetDefaultPrevented())
|
|
adapted_callback.Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_DENY);
|
|
}
|
|
|
|
base::OnceClosure App::SelectClientCertificate(
|
|
content::BrowserContext* browser_context,
|
|
content::ChildProcessId process_id,
|
|
content::WebContents* web_contents,
|
|
net::SSLCertRequestInfo* cert_request_info,
|
|
net::ClientCertIdentityList identities,
|
|
std::unique_ptr<content::ClientCertificateDelegate> delegate) {
|
|
std::shared_ptr<content::ClientCertificateDelegate> shared_delegate(
|
|
delegate.release());
|
|
|
|
// Convert the ClientCertIdentityList to a CertificateList
|
|
// to avoid changes in the API.
|
|
auto client_certs = net::CertificateList();
|
|
for (const std::unique_ptr<net::ClientCertIdentity>& identity : identities)
|
|
client_certs.emplace_back(identity->certificate());
|
|
|
|
auto shared_identities =
|
|
std::make_shared<net::ClientCertIdentityList>(std::move(identities));
|
|
|
|
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();
|
|
std::string host_and_port = cert_request_info->host_and_port.ToString();
|
|
v8::Local<v8::Value> certs = gin::ConvertToV8(isolate, client_certs);
|
|
v8::Local<v8::Value> callback_value = gin::ConvertToV8(
|
|
isolate, base::BindOnce(&OnClientCertificateSelected, isolate,
|
|
shared_delegate, shared_identities));
|
|
// The same event is emitted on the WebContents, then on app. |web_contents|
|
|
// is null for requests that did not originate from a renderer (e.g.
|
|
// net.fetch / utilityProcess); app surfaces those with a null WebContents.
|
|
WebContents* api_web_contents =
|
|
web_contents ? WebContents::From(web_contents) : nullptr;
|
|
if (api_web_contents) {
|
|
api_web_contents->EmitWithoutEvent("select-client-certificate",
|
|
event_object, host_and_port, certs,
|
|
callback_value);
|
|
}
|
|
EmitWithoutEvent("select-client-certificate", event_object, web_contents,
|
|
host_and_port, certs, callback_value);
|
|
|
|
// Default to first certificate from the platform store. The JS callback may
|
|
// have already run synchronously and moved the identity out, so guard for it.
|
|
if (!event->GetDefaultPrevented() && (*shared_identities)[0]) {
|
|
scoped_refptr<net::X509Certificate> cert =
|
|
(*shared_identities)[0]->certificate();
|
|
net::ClientCertIdentity::SelfOwningAcquirePrivateKey(
|
|
std::move((*shared_identities)[0]),
|
|
base::BindRepeating(&GotPrivateKey, shared_delegate, std::move(cert)));
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void App::OnGpuInfoUpdate() {
|
|
Emit("gpu-info-update");
|
|
}
|
|
|
|
void App::BrowserChildProcessLaunchedAndConnected(
|
|
const content::ChildProcessData& data,
|
|
const base::Process& process) {
|
|
ChildProcessLaunched(data.process_type, data.GetChildProcessId(),
|
|
process.Handle(), data.metrics_name,
|
|
base::UTF16ToUTF8(data.name));
|
|
}
|
|
|
|
void App::BrowserChildProcessHostDisconnected(
|
|
const content::ChildProcessData& data) {
|
|
ChildProcessDisconnected(data.GetChildProcessId());
|
|
}
|
|
|
|
void App::BrowserChildProcessCrashed(
|
|
const content::ChildProcessData& data,
|
|
const content::ChildProcessTerminationInfo& info) {
|
|
ChildProcessDisconnected(data.GetChildProcessId());
|
|
EmitChildProcessGone(data, info);
|
|
}
|
|
|
|
void App::BrowserChildProcessKilled(
|
|
const content::ChildProcessData& data,
|
|
const content::ChildProcessTerminationInfo& info) {
|
|
ChildProcessDisconnected(data.GetChildProcessId());
|
|
EmitChildProcessGone(data, info);
|
|
}
|
|
|
|
void App::BrowserChildProcessLaunchFailed(
|
|
const content::ChildProcessData& data,
|
|
const content::ChildProcessTerminationInfo& info) {
|
|
EmitChildProcessGone(data, info);
|
|
}
|
|
|
|
void App::EmitChildProcessGone(
|
|
const content::ChildProcessData& data,
|
|
const content::ChildProcessTerminationInfo& info) {
|
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
auto details = gin_helper::Dictionary::CreateEmpty(isolate);
|
|
details.Set("type", content::GetProcessTypeNameInEnglish(data.process_type));
|
|
details.Set("reason", info.status);
|
|
details.Set("exitCode", info.exit_code);
|
|
#if BUILDFLAG(IS_WIN)
|
|
if (info.status == base::TERMINATION_STATUS_LAUNCH_FAILED) {
|
|
details.Set("systemErrorCode", static_cast<uint32_t>(info.last_error));
|
|
}
|
|
#endif
|
|
details.Set("serviceName", data.metrics_name);
|
|
if (!data.name.empty()) {
|
|
details.Set("name", data.name);
|
|
}
|
|
Emit("child-process-gone", details);
|
|
}
|
|
|
|
void App::RenderProcessReady(content::RenderProcessHost* host) {
|
|
ChildProcessLaunched(content::PROCESS_TYPE_RENDERER, host->GetID(),
|
|
host->GetProcess().Handle());
|
|
}
|
|
|
|
void App::RenderProcessExited(content::RenderProcessHost* host) {
|
|
ChildProcessDisconnected(host->GetID());
|
|
}
|
|
|
|
void App::ChildProcessLaunched(int process_type,
|
|
content::ChildProcessId pid,
|
|
base::ProcessHandle handle,
|
|
const std::string& service_name,
|
|
const std::string& name) {
|
|
#if BUILDFLAG(IS_MAC)
|
|
auto metrics = base::ProcessMetrics::CreateProcessMetrics(
|
|
handle, content::BrowserChildProcessHost::GetPortProvider());
|
|
#else
|
|
auto metrics = base::ProcessMetrics::CreateProcessMetrics(handle);
|
|
#endif
|
|
app_metrics_[pid] = std::make_unique<electron::ProcessMetric>(
|
|
process_type, handle, std::move(metrics), service_name, name);
|
|
}
|
|
|
|
void App::ChildProcessDisconnected(content::ChildProcessId pid) {
|
|
app_metrics_.erase(pid);
|
|
}
|
|
|
|
base::FilePath App::GetAppPath() const {
|
|
return app_path_;
|
|
}
|
|
|
|
v8::Local<v8::String> App::GetNameString(v8::Isolate* isolate) {
|
|
return name_string_.Get(isolate, Browser::Get()->GetName());
|
|
}
|
|
|
|
v8::Local<v8::String> App::GetVersionString(v8::Isolate* isolate) {
|
|
return version_string_.Get(isolate, Browser::Get()->GetVersion());
|
|
}
|
|
|
|
v8::Local<v8::Value> App::GetAppPathValue(v8::Isolate* isolate) {
|
|
if (app_path_value_.IsEmpty())
|
|
app_path_value_.Reset(isolate, gin::ConvertToV8(isolate, app_path_));
|
|
return app_path_value_.Get(isolate);
|
|
}
|
|
|
|
void App::SetAppPath(const base::FilePath& app_path) {
|
|
app_path_ = app_path;
|
|
app_path_value_.Reset();
|
|
}
|
|
|
|
void App::SetAppLogsPath(gin::Arguments* const args) {
|
|
base::FilePath path;
|
|
|
|
// if caller provided a path, it must be absolute
|
|
if (args->GetNext(&path) && !path.IsAbsolute()) {
|
|
args->ThrowTypeError("Path must be absolute");
|
|
return;
|
|
}
|
|
|
|
// if caller did not provide a path, then use a default one
|
|
if (path.empty()) {
|
|
path = GetDefaultAppLogPath();
|
|
}
|
|
|
|
ScopedAllowBlockingForElectron allow_blocking;
|
|
base::PathService::Override(DIR_APP_LOGS, path);
|
|
}
|
|
|
|
#if !BUILDFLAG(IS_MAC)
|
|
// static
|
|
// default to `${DIR_USER_DATA}/logs`
|
|
base::FilePath App::GetDefaultAppLogPath() {
|
|
base::FilePath path;
|
|
if (base::PathService::Get(chrome::DIR_USER_DATA, &path)) {
|
|
path = path.Append(base::FilePath::FromUTF8Unsafe("logs"));
|
|
}
|
|
return path;
|
|
}
|
|
#endif
|
|
|
|
// static
|
|
bool App::IsPackaged() {
|
|
auto env = base::Environment::Create();
|
|
if (env->HasVar("ELECTRON_FORCE_IS_PACKAGED"))
|
|
return true;
|
|
|
|
base::FilePath exe_path;
|
|
base::PathService::Get(base::FILE_EXE, &exe_path);
|
|
base::FilePath::StringType base_name =
|
|
base::ToLowerASCII(exe_path.BaseName().value());
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
return base_name != FILE_PATH_LITERAL("electron.exe");
|
|
#elif BUILDFLAG(IS_MAC)
|
|
static const base::NoDestructor<std::string> default_helper(
|
|
"electron helper" +
|
|
base::ToLowerASCII(content::kMacHelperSuffix_default));
|
|
static const base::NoDestructor<std::string> renderer_helper(
|
|
"electron helper" +
|
|
base::ToLowerASCII(content::kMacHelperSuffix_renderer));
|
|
static const base::NoDestructor<std::string> plugin_helper(
|
|
"electron helper" + base::ToLowerASCII(kElectronMacHelperSuffixPlugin));
|
|
if (IsRendererProcess()) {
|
|
return base_name != *renderer_helper;
|
|
} else if (IsUtilityProcess()) {
|
|
return base_name != *default_helper && base_name != *plugin_helper;
|
|
}
|
|
return base_name != FILE_PATH_LITERAL("electron");
|
|
#else
|
|
return base_name != FILE_PATH_LITERAL("electron");
|
|
#endif
|
|
}
|
|
|
|
base::FilePath App::GetPath(gin_helper::ErrorThrower thrower,
|
|
const std::string& name) {
|
|
base::FilePath path;
|
|
|
|
int key = GetPathConstant(name);
|
|
if (key < 0 || !base::PathService::Get(key, &path))
|
|
thrower.ThrowError("Failed to get '" + name + "' path");
|
|
|
|
return path;
|
|
}
|
|
|
|
void App::SetPath(gin_helper::ErrorThrower thrower,
|
|
const std::string& name,
|
|
const base::FilePath& path) {
|
|
if (!path.IsAbsolute()) {
|
|
thrower.ThrowError("Path must be absolute");
|
|
return;
|
|
}
|
|
|
|
int key = GetPathConstant(name);
|
|
if (key < 0 || !base::PathService::OverrideAndCreateIfNeeded(
|
|
key, path, /* is_absolute = */ true, /* create = */ false))
|
|
thrower.ThrowError("Failed to set path");
|
|
}
|
|
|
|
void App::SetDesktopName(const std::string& desktop_name) {
|
|
#if BUILDFLAG(IS_LINUX)
|
|
auto env = base::Environment::Create();
|
|
env->SetVar("CHROME_DESKTOP", desktop_name);
|
|
// The Linux application name, and so the user agent, comes from this file.
|
|
InvalidateApplicationUserAgent();
|
|
#endif
|
|
}
|
|
|
|
void App::SetActivationToken(const std::string& token) {
|
|
#if BUILDFLAG(IS_LINUX)
|
|
base::nix::SetActivationToken(token);
|
|
#endif
|
|
}
|
|
|
|
std::string App::GetLocale() {
|
|
return g_browser_process->GetApplicationLocale();
|
|
}
|
|
|
|
std::string App::GetSystemLocale(gin_helper::ErrorThrower thrower) const {
|
|
if (!Browser::Get()->is_ready()) {
|
|
thrower.ThrowError(
|
|
"app.getSystemLocale() can only be called "
|
|
"after app is ready");
|
|
return {};
|
|
}
|
|
return static_cast<BrowserProcessImpl*>(g_browser_process)->GetSystemLocale();
|
|
}
|
|
|
|
std::string App::GetLocaleCountryCode() {
|
|
std::string region;
|
|
#if BUILDFLAG(IS_WIN)
|
|
WCHAR locale_name[LOCALE_NAME_MAX_LENGTH] = {0};
|
|
|
|
if (GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SISO3166CTRYNAME,
|
|
locale_name, std::size(locale_name)) ||
|
|
GetLocaleInfoEx(LOCALE_NAME_SYSTEM_DEFAULT, LOCALE_SISO3166CTRYNAME,
|
|
locale_name, std::size(locale_name))) {
|
|
base::WideToUTF8(locale_name, wcslen(locale_name), ®ion);
|
|
}
|
|
#elif BUILDFLAG(IS_MAC)
|
|
base::apple::ScopedCFTypeRef<CFLocaleRef> locale(CFLocaleCopyCurrent());
|
|
auto value = static_cast<CFStringRef>(
|
|
CFLocaleGetValue(locale.get(), kCFLocaleCountryCode));
|
|
if (value != nil) {
|
|
char temporaryCString[3];
|
|
const CFIndex kCStringSize = sizeof(temporaryCString);
|
|
if (CFStringGetCString(value, temporaryCString, kCStringSize,
|
|
kCFStringEncodingUTF8)) {
|
|
region = temporaryCString;
|
|
}
|
|
}
|
|
#else
|
|
const char* locale_ptr = setlocale(LC_TIME, nullptr);
|
|
if (!locale_ptr)
|
|
locale_ptr = setlocale(LC_NUMERIC, nullptr);
|
|
if (locale_ptr) {
|
|
std::string locale = locale_ptr;
|
|
std::string::size_type rpos = locale.find('.');
|
|
if (rpos != std::string::npos)
|
|
locale = locale.substr(0, rpos);
|
|
rpos = locale.find('_');
|
|
if (rpos != std::string::npos && rpos + 1 < locale.size())
|
|
region = locale.substr(rpos + 1);
|
|
}
|
|
#endif
|
|
return region.size() == 2 ? region : std::string();
|
|
}
|
|
|
|
void App::OnSecondInstance(base::CommandLine cmd,
|
|
const base::FilePath& cwd,
|
|
const std::vector<uint8_t> additional_data) {
|
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::Value> data_value =
|
|
DeserializeV8Value(isolate, std::move(additional_data));
|
|
Emit("second-instance", cmd.argv(), cwd, data_value);
|
|
}
|
|
|
|
bool App::HasSingleInstanceLock() const {
|
|
if (process_singleton_)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool App::RequestSingleInstanceLock(gin::Arguments* args) {
|
|
if (HasSingleInstanceLock())
|
|
return true;
|
|
|
|
base::FilePath user_dir;
|
|
base::PathService::Get(chrome::DIR_USER_DATA, &user_dir);
|
|
// The user_dir may not have been created yet.
|
|
base::CreateDirectoryAndGetError(user_dir, nullptr);
|
|
|
|
auto cb = base::BindRepeating(&App::OnSecondInstance, base::Unretained(this));
|
|
|
|
blink::CloneableMessage additional_data_message;
|
|
args->GetNext(&additional_data_message);
|
|
// ProcessSingleton keeps a non-owning base::raw_span to this data, so it must
|
|
// outlive `process_singleton_`. Copy it into a member that is destroyed after
|
|
// `process_singleton_` to avoid a dangling span.
|
|
single_instance_additional_data_.assign(
|
|
additional_data_message.encoded_message.begin(),
|
|
additional_data_message.encoded_message.end());
|
|
#if BUILDFLAG(IS_WIN)
|
|
const std::string program_name = electron::Browser::Get()->GetName();
|
|
bool app_is_sandboxed =
|
|
IsSandboxEnabled(base::CommandLine::ForCurrentProcess());
|
|
process_singleton_ = std::make_unique<ProcessSingleton>(
|
|
program_name, user_dir, single_instance_additional_data_,
|
|
app_is_sandboxed, base::BindRepeating(NotificationCallbackWrapper, cb));
|
|
#else
|
|
process_singleton_ = std::make_unique<ProcessSingleton>(
|
|
user_dir, single_instance_additional_data_,
|
|
base::BindRepeating(NotificationCallbackWrapper, cb));
|
|
#endif
|
|
|
|
#if BUILDFLAG(IS_LINUX)
|
|
// Read the xdg-activation token and set it in the command line for the
|
|
// duration of the notification in order to ensure this is propagated to an
|
|
// already running electron app instance if it exists.
|
|
// The activation token received from the launching app is used later when
|
|
// activating the existing electron app instance window.
|
|
base::nix::ScopedXdgActivationTokenInjector activation_token_injector(
|
|
*base::CommandLine::ForCurrentProcess(), *base::Environment::Create());
|
|
#endif
|
|
switch (process_singleton_->NotifyOtherProcessOrCreate()) {
|
|
case ProcessSingleton::NotifyResult::PROCESS_NONE:
|
|
if (content::BrowserThread::IsThreadInitialized(
|
|
content::BrowserThread::IO)) {
|
|
process_singleton_->StartWatching();
|
|
} else {
|
|
watch_singleton_socket_on_ready_ = true;
|
|
}
|
|
return true;
|
|
case ProcessSingleton::NotifyResult::LOCK_ERROR:
|
|
case ProcessSingleton::NotifyResult::PROFILE_IN_USE:
|
|
case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED: {
|
|
ScopedAllowBlockingForElectron allow_blocking;
|
|
process_singleton_.reset();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void App::ReleaseSingleInstanceLock() {
|
|
if (process_singleton_) {
|
|
ScopedAllowBlockingForElectron allow_blocking;
|
|
process_singleton_->Cleanup();
|
|
process_singleton_.reset();
|
|
}
|
|
}
|
|
|
|
bool App::Relaunch(gin::Arguments* js_args) {
|
|
// Parse parameters.
|
|
bool override_argv = false;
|
|
base::FilePath exec_path;
|
|
relauncher::StringVector args;
|
|
relauncher::StringVector relauncher_args;
|
|
|
|
gin_helper::Dictionary options;
|
|
if (js_args->GetNext(&options)) {
|
|
bool has_exec_path = options.Get("execPath", &exec_path);
|
|
bool has_args = options.Get("args", &args);
|
|
if (has_exec_path || has_args)
|
|
override_argv = true;
|
|
#if BUILDFLAG(IS_WIN)
|
|
if (bool de_elevate = false;
|
|
options.Get("deElevate", &de_elevate) && de_elevate) {
|
|
relauncher_args.push_back(relauncher::internal::kRelauncherDeElevateArg);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (!override_argv) {
|
|
const relauncher::StringVector& argv =
|
|
electron::ElectronCommandLine::argv();
|
|
return relauncher::RelaunchApp(argv, relauncher_args);
|
|
}
|
|
|
|
relauncher::StringVector argv;
|
|
argv.reserve(1 + args.size());
|
|
|
|
if (exec_path.empty()) {
|
|
base::FilePath current_exe_path;
|
|
base::PathService::Get(base::FILE_EXE, ¤t_exe_path);
|
|
argv.push_back(current_exe_path.value());
|
|
} else {
|
|
argv.push_back(exec_path.value());
|
|
}
|
|
|
|
argv.insert(argv.end(), args.begin(), args.end());
|
|
|
|
return relauncher::RelaunchApp(argv, relauncher_args);
|
|
}
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
bool App::CanDeElevate() {
|
|
return base::win::UserAccountIsUnnecessarilyElevated();
|
|
}
|
|
#endif
|
|
|
|
void App::DisableHardwareAcceleration(gin_helper::ErrorThrower thrower) {
|
|
if (Browser::Get()->is_ready()) {
|
|
thrower.ThrowError(
|
|
"app.disableHardwareAcceleration() can only be called "
|
|
"before app is ready");
|
|
return;
|
|
}
|
|
|
|
// Append --disable-gpu to the command line so that all Chromium subsystems
|
|
// that check the switch (e.g. GpuProcessHost, viz compositor) respect the
|
|
// decision. The switch must be set before GpuDataManager is initialised
|
|
// because InitializeGpuModes() reads it to decide which GPU modes to add.
|
|
auto* command_line = base::CommandLine::ForCurrentProcess();
|
|
if (!command_line->HasSwitch(::switches::kDisableGpu))
|
|
command_line->AppendSwitch(::switches::kDisableGpu);
|
|
|
|
// If the GpuDataManager is already initialized, disable hardware
|
|
// acceleration immediately. Otherwise, set a flag to disable it in
|
|
// OnPreCreateThreads().
|
|
if (content::GpuDataManager::Initialized()) {
|
|
content::GpuDataManager::GetInstance()->DisableHardwareAcceleration();
|
|
} else {
|
|
disable_hw_acceleration_ = true;
|
|
}
|
|
}
|
|
|
|
bool App::IsHardwareAccelerationEnabled() {
|
|
if (content::GpuDataManager::Initialized())
|
|
return content::GpuDataManager::GetInstance()
|
|
->HardwareAccelerationEnabled();
|
|
return !disable_hw_acceleration_;
|
|
}
|
|
|
|
void App::DisableDomainBlockingFor3DAPIs(gin_helper::ErrorThrower thrower) {
|
|
if (Browser::Get()->is_ready()) {
|
|
thrower.ThrowError(
|
|
"app.disableDomainBlockingFor3DAPIs() can only be called "
|
|
"before app is ready");
|
|
return;
|
|
}
|
|
if (content::GpuDataManager::Initialized()) {
|
|
content::GpuDataManagerImpl::GetInstance()
|
|
->DisableDomainBlockingFor3DAPIsForTesting();
|
|
} else {
|
|
disable_domain_blocking_for_3DAPIs_ = true;
|
|
}
|
|
}
|
|
|
|
bool App::IsAccessibilitySupportEnabled() {
|
|
auto* ax_state = content::BrowserAccessibilityState::GetInstance();
|
|
auto mode = ax_state->GetAccessibilityMode();
|
|
return mode.has_mode(ui::kAXModeComplete.flags());
|
|
}
|
|
|
|
v8::Local<v8::Value> App::GetAccessibilitySupportFeatures() {
|
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
|
v8::EscapableHandleScope handle_scope(isolate);
|
|
auto* ax_state = content::BrowserAccessibilityState::GetInstance();
|
|
ui::AXMode mode = ax_state->GetAccessibilityMode();
|
|
|
|
std::vector<v8::Local<v8::Value>> features;
|
|
auto push = [&](const char* name) {
|
|
features.push_back(v8::String::NewFromUtf8(isolate, name).ToLocalChecked());
|
|
};
|
|
|
|
if (mode.has_mode(ui::AXMode::kNativeAPIs))
|
|
push("nativeAPIs");
|
|
if (mode.has_mode(ui::AXMode::kWebContents))
|
|
push("webContents");
|
|
if (mode.has_mode(ui::AXMode::kInlineTextBoxes))
|
|
push("inlineTextBoxes");
|
|
if (mode.has_mode(ui::AXMode::kExtendedProperties))
|
|
push("extendedProperties");
|
|
if (mode.has_mode(ui::AXMode::kHTML))
|
|
push("html");
|
|
if (mode.has_mode(ui::AXMode::kLabelImages))
|
|
push("labelImages");
|
|
if (mode.has_mode(ui::AXMode::kPDFPrinting))
|
|
push("pdfPrinting");
|
|
if (mode.has_mode(ui::AXMode::kScreenReader))
|
|
push("screenReader");
|
|
|
|
v8::Local<v8::Array> arr = v8::Array::New(isolate, features.size());
|
|
for (uint32_t i = 0; i < features.size(); ++i) {
|
|
arr->CreateDataProperty(isolate->GetCurrentContext(), i, features[i])
|
|
.Check();
|
|
}
|
|
return handle_scope.Escape(arr);
|
|
}
|
|
|
|
void App::SetAccessibilitySupportFeatures(
|
|
gin_helper::ErrorThrower thrower,
|
|
const std::vector<std::string>& features) {
|
|
if (!Browser::Get()->is_ready()) {
|
|
thrower.ThrowError(
|
|
"app.setAccessibilitySupportFeatures() can only be called after app "
|
|
"is ready");
|
|
return;
|
|
}
|
|
|
|
ui::AXMode mode;
|
|
for (const auto& f : features) {
|
|
if (f == "nativeAPIs") {
|
|
mode.set_mode(ui::AXMode::kNativeAPIs, true);
|
|
} else if (f == "webContents") {
|
|
mode.set_mode(ui::AXMode::kWebContents, true);
|
|
} else if (f == "inlineTextBoxes") {
|
|
mode.set_mode(ui::AXMode::kInlineTextBoxes, true);
|
|
} else if (f == "extendedProperties") {
|
|
mode.set_mode(ui::AXMode::kExtendedProperties, true);
|
|
} else if (f == "screenReader") {
|
|
mode.set_mode(ui::AXMode::kScreenReader, true);
|
|
} else if (f == "html") {
|
|
mode.set_mode(ui::AXMode::kHTML, true);
|
|
} else if (f == "labelImages") {
|
|
mode.set_mode(ui::AXMode::kLabelImages, true);
|
|
} else if (f == "pdfPrinting") {
|
|
mode.set_mode(ui::AXMode::kPDFPrinting, true);
|
|
} else {
|
|
thrower.ThrowError("Unknown accessibility feature: " + f);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (mode.is_mode_off()) {
|
|
scoped_accessibility_mode_.reset();
|
|
} else {
|
|
scoped_accessibility_mode_ =
|
|
content::BrowserAccessibilityState::GetInstance()
|
|
->CreateScopedModeForProcess(mode);
|
|
}
|
|
|
|
Browser::Get()->OnAccessibilitySupportChanged();
|
|
}
|
|
|
|
void App::SetAccessibilitySupportEnabled(gin_helper::ErrorThrower thrower,
|
|
bool enabled) {
|
|
if (!Browser::Get()->is_ready()) {
|
|
thrower.ThrowError(
|
|
"app.setAccessibilitySupportEnabled() can only be called "
|
|
"after app is ready");
|
|
return;
|
|
}
|
|
|
|
if (!enabled) {
|
|
scoped_accessibility_mode_.reset();
|
|
} else if (!scoped_accessibility_mode_) {
|
|
scoped_accessibility_mode_ =
|
|
content::BrowserAccessibilityState::GetInstance()
|
|
->CreateScopedModeForProcess(ui::kAXModeComplete);
|
|
}
|
|
Browser::Get()->OnAccessibilitySupportChanged();
|
|
}
|
|
|
|
v8::Local<v8::Value> App::GetLoginItemSettings(gin::Arguments* args) {
|
|
LoginItemSettings options;
|
|
args->GetNext(&options);
|
|
return Browser::Get()->GetLoginItemSettings(options);
|
|
}
|
|
|
|
#if BUILDFLAG(USE_NSS_CERTS)
|
|
void App::ImportCertificate(gin_helper::ErrorThrower thrower,
|
|
base::Value options,
|
|
net::CompletionOnceCallback callback) {
|
|
if (!options.is_dict()) {
|
|
thrower.ThrowTypeError("Expected options to be an object");
|
|
return;
|
|
}
|
|
|
|
if (!certificate_manager_model_) {
|
|
CertificateManagerModel::Create(base::BindOnce(
|
|
&App::OnCertificateManagerModelCreated, base::Unretained(this),
|
|
std::move(options), std::move(callback)));
|
|
return;
|
|
}
|
|
|
|
int rv =
|
|
ImportIntoCertStore(certificate_manager_model_.get(), std::move(options));
|
|
std::move(callback).Run(rv);
|
|
}
|
|
|
|
void App::OnCertificateManagerModelCreated(
|
|
base::Value options,
|
|
net::CompletionOnceCallback callback,
|
|
std::unique_ptr<CertificateManagerModel> model) {
|
|
certificate_manager_model_ = std::move(model);
|
|
int rv =
|
|
ImportIntoCertStore(certificate_manager_model_.get(), std::move(options));
|
|
std::move(callback).Run(rv);
|
|
}
|
|
#endif
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
v8::Local<v8::Value> App::GetJumpListSettings() {
|
|
JumpList jump_list(Browser::Get()->GetAppUserModelID());
|
|
|
|
int min_items = 10;
|
|
std::vector<JumpListItem> removed_items;
|
|
if (jump_list.Begin(&min_items, &removed_items)) {
|
|
// We don't actually want to change anything, so abort the transaction.
|
|
jump_list.Abort();
|
|
} else {
|
|
LOG(ERROR) << "Failed to begin Jump List transaction.";
|
|
}
|
|
|
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
|
auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
|
|
dict.Set("minItems", min_items);
|
|
dict.Set("removedItems", gin::ConvertToV8(isolate, removed_items));
|
|
return dict.GetHandle();
|
|
}
|
|
|
|
JumpListResult App::SetJumpList(v8::Isolate* const isolate,
|
|
v8::Local<v8::Value> val) {
|
|
std::vector<JumpListCategory> categories;
|
|
bool delete_jump_list = val->IsNull();
|
|
if (!delete_jump_list && !gin::ConvertFromV8(isolate, val, &categories)) {
|
|
gin_helper::ErrorThrower{isolate}.ThrowTypeError(
|
|
"Argument must be null or an array of categories");
|
|
return JumpListResult::kArgumentError;
|
|
}
|
|
|
|
JumpList jump_list(Browser::Get()->GetAppUserModelID());
|
|
|
|
if (delete_jump_list) {
|
|
return jump_list.Delete() ? JumpListResult::kSuccess
|
|
: JumpListResult::kGenericError;
|
|
}
|
|
|
|
// Start a transaction that updates the JumpList of this application.
|
|
if (!jump_list.Begin())
|
|
return JumpListResult::kGenericError;
|
|
|
|
JumpListResult result = jump_list.AppendCategories(categories);
|
|
// AppendCategories may have failed to add some categories, but it's better
|
|
// to have something than nothing so try to commit the changes anyway.
|
|
if (!jump_list.Commit()) {
|
|
LOG(ERROR) << "Failed to commit changes to custom Jump List.";
|
|
// It's more useful to return the earlier error code that might give
|
|
// some indication as to why the transaction actually failed, so don't
|
|
// overwrite it with a "generic error" code here.
|
|
if (result == JumpListResult::kSuccess)
|
|
result = JumpListResult::kGenericError;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#endif // BUILDFLAG(IS_WIN)
|
|
|
|
v8::Local<v8::Promise> App::GetFileIcon(const base::FilePath& path,
|
|
gin::Arguments* args) {
|
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
|
gin_helper::Promise<gfx::Image> promise(isolate);
|
|
v8::Local<v8::Promise> handle = promise.GetHandle();
|
|
base::FilePath normalized_path = path.NormalizePathSeparators();
|
|
|
|
IconLoader::IconSize icon_size;
|
|
gin_helper::Dictionary options;
|
|
if (!args->GetNext(&options)) {
|
|
icon_size = IconLoader::IconSize::NORMAL;
|
|
} else {
|
|
std::string icon_size_string;
|
|
options.Get("size", &icon_size_string);
|
|
icon_size = GetIconSizeByString(icon_size_string);
|
|
}
|
|
|
|
auto* icon_manager = ElectronBrowserMainParts::Get()->GetIconManager();
|
|
gfx::Image* icon =
|
|
icon_manager->LookupIconFromFilepath(normalized_path, icon_size, 1.0f);
|
|
if (icon) {
|
|
promise.Resolve(*icon);
|
|
} else {
|
|
icon_manager->LoadIcon(
|
|
normalized_path, icon_size, 1.0f,
|
|
base::BindOnce(&OnIconDataAvailable, std::move(promise)),
|
|
&cancelable_task_tracker_);
|
|
}
|
|
return handle;
|
|
}
|
|
|
|
std::vector<gin_helper::Dictionary> App::GetAppMetrics(v8::Isolate* isolate) {
|
|
std::vector<gin_helper::Dictionary> result;
|
|
result.reserve(app_metrics_.size());
|
|
int processor_count = base::SysInfo::NumberOfProcessors();
|
|
|
|
for (const auto& process_metric : app_metrics_) {
|
|
auto pid_dict = gin_helper::Dictionary::CreateEmpty(isolate);
|
|
auto cpu_dict = gin_helper::Dictionary::CreateEmpty(isolate);
|
|
|
|
// Default usage percentage to 0 for compatibility
|
|
double usagePercent = 0;
|
|
if (auto usage = process_metric.second->metrics->GetCumulativeCPUUsage();
|
|
usage.has_value()) {
|
|
cpu_dict.Set("cumulativeCPUUsage", usage->InSecondsF());
|
|
usagePercent =
|
|
process_metric.second->metrics->GetPlatformIndependentCPUUsage(
|
|
*usage);
|
|
}
|
|
|
|
cpu_dict.Set("percentCPUUsage", usagePercent / processor_count);
|
|
|
|
#if !BUILDFLAG(IS_WIN)
|
|
cpu_dict.Set("idleWakeupsPerSecond",
|
|
process_metric.second->metrics->GetIdleWakeupsPerSecond());
|
|
#else
|
|
// Chrome's underlying process_metrics.cc will throw a non-fatal warning
|
|
// that this method isn't implemented on Windows, so set it to 0 instead
|
|
// of calling it
|
|
cpu_dict.Set("idleWakeupsPerSecond", 0);
|
|
#endif
|
|
|
|
pid_dict.Set("cpu", cpu_dict);
|
|
pid_dict.Set("pid", process_metric.second->process.Pid());
|
|
pid_dict.Set("type", content::GetProcessTypeNameInEnglish(
|
|
process_metric.second->type));
|
|
pid_dict.Set("creationTime", process_metric.second->process.CreationTime()
|
|
.InMillisecondsFSinceUnixEpoch());
|
|
|
|
if (!process_metric.second->service_name.empty()) {
|
|
pid_dict.Set("serviceName", process_metric.second->service_name);
|
|
}
|
|
|
|
if (!process_metric.second->name.empty()) {
|
|
pid_dict.Set("name", process_metric.second->name);
|
|
}
|
|
|
|
auto memory_info = process_metric.second->GetMemoryInfo();
|
|
|
|
auto memory_dict = gin_helper::Dictionary::CreateEmpty(isolate);
|
|
memory_dict.Set("workingSetSize",
|
|
static_cast<double>(memory_info.working_set_size >> 10));
|
|
memory_dict.Set(
|
|
"peakWorkingSetSize",
|
|
static_cast<double>(memory_info.peak_working_set_size >> 10));
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
memory_dict.Set("privateBytes",
|
|
static_cast<double>(memory_info.private_bytes >> 10));
|
|
#endif
|
|
|
|
pid_dict.Set("memory", memory_dict);
|
|
|
|
#if BUILDFLAG(IS_MAC)
|
|
pid_dict.Set("sandboxed", process_metric.second->IsSandboxed());
|
|
#elif BUILDFLAG(IS_WIN)
|
|
auto integrity_level = process_metric.second->GetIntegrityLevel();
|
|
auto sandboxed = ProcessMetric::IsSandboxed(integrity_level);
|
|
pid_dict.Set("integrityLevel", integrity_level);
|
|
pid_dict.Set("sandboxed", sandboxed);
|
|
#endif
|
|
|
|
result.push_back(pid_dict);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
v8::Local<v8::Value> App::GetGPUFeatureStatus(v8::Isolate* isolate) {
|
|
return gin::ConvertToV8(isolate, content::GetFeatureStatus());
|
|
}
|
|
|
|
v8::Local<v8::Promise> App::GetGPUInfo(v8::Isolate* isolate,
|
|
const std::string& info_type) {
|
|
auto* const gpu_data_manager = content::GpuDataManagerImpl::GetInstance();
|
|
gin_helper::Promise<base::Value> promise(isolate);
|
|
v8::Local<v8::Promise> handle = promise.GetHandle();
|
|
if (info_type != "basic" && info_type != "complete") {
|
|
promise.RejectWithErrorMessage(
|
|
"Invalid info type. Use 'basic' or 'complete'");
|
|
return handle;
|
|
}
|
|
std::string reason;
|
|
if (!gpu_data_manager->GpuAccessAllowed(&reason)) {
|
|
promise.RejectWithErrorMessage("GPU access not allowed. Reason: " + reason);
|
|
return handle;
|
|
}
|
|
|
|
auto* const info_mgr = GPUInfoManager::GetInstance();
|
|
if (info_type == "complete") {
|
|
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
|
|
info_mgr->FetchCompleteInfo(std::move(promise));
|
|
#else
|
|
info_mgr->FetchBasicInfo(std::move(promise));
|
|
#endif
|
|
} else /* (info_type == "basic") */ {
|
|
info_mgr->FetchBasicInfo(std::move(promise));
|
|
}
|
|
return handle;
|
|
}
|
|
|
|
static void RemoveNoSandboxSwitch(base::CommandLine* command_line) {
|
|
if (command_line->HasSwitch(sandbox::policy::switches::kNoSandbox)) {
|
|
const base::CommandLine::CharType* noSandboxArg =
|
|
FILE_PATH_LITERAL("--no-sandbox");
|
|
base::CommandLine::StringVector modified_command_line;
|
|
for (auto& arg : command_line->argv()) {
|
|
if (arg.compare(noSandboxArg) != 0) {
|
|
modified_command_line.push_back(arg);
|
|
}
|
|
}
|
|
command_line->InitFromArgv(modified_command_line);
|
|
}
|
|
}
|
|
|
|
void App::EnableSandbox(gin_helper::ErrorThrower thrower) {
|
|
if (Browser::Get()->is_ready()) {
|
|
thrower.ThrowError(
|
|
"app.enableSandbox() can only be called "
|
|
"before app is ready");
|
|
return;
|
|
}
|
|
|
|
auto* command_line = base::CommandLine::ForCurrentProcess();
|
|
RemoveNoSandboxSwitch(command_line);
|
|
command_line->AppendSwitch(switches::kEnableSandbox);
|
|
}
|
|
|
|
v8::Local<v8::Promise> App::SetProxy(gin::Arguments* args) {
|
|
v8::Isolate* isolate = args->isolate();
|
|
gin_helper::Promise<void> promise(isolate);
|
|
v8::Local<v8::Promise> handle = promise.GetHandle();
|
|
|
|
gin_helper::Dictionary options;
|
|
args->GetNext(&options);
|
|
|
|
if (!Browser::Get()->is_ready()) {
|
|
promise.RejectWithErrorMessage(
|
|
"app.setProxy() can only be called after app is ready.");
|
|
return handle;
|
|
}
|
|
|
|
if (!g_browser_process->local_state()) {
|
|
promise.RejectWithErrorMessage(
|
|
"app.setProxy() failed due to internal error.");
|
|
return handle;
|
|
}
|
|
|
|
std::string mode, proxy_rules, bypass_list, pac_url;
|
|
|
|
options.Get("pacScript", &pac_url);
|
|
options.Get("proxyRules", &proxy_rules);
|
|
options.Get("proxyBypassRules", &bypass_list);
|
|
|
|
ProxyPrefs::ProxyMode proxy_mode = ProxyPrefs::MODE_FIXED_SERVERS;
|
|
if (!options.Get("mode", &mode)) {
|
|
// pacScript takes precedence over proxyRules.
|
|
if (!pac_url.empty()) {
|
|
proxy_mode = ProxyPrefs::MODE_PAC_SCRIPT;
|
|
}
|
|
} else if (!ProxyPrefs::StringToProxyMode(mode, &proxy_mode)) {
|
|
promise.RejectWithErrorMessage(
|
|
"Invalid mode, must be one of direct, auto_detect, pac_script, "
|
|
"fixed_servers or system");
|
|
return handle;
|
|
}
|
|
|
|
base::DictValue proxy_config;
|
|
switch (proxy_mode) {
|
|
case ProxyPrefs::MODE_DIRECT:
|
|
proxy_config = ProxyConfigDictionary::CreateDirect();
|
|
break;
|
|
case ProxyPrefs::MODE_SYSTEM:
|
|
proxy_config = ProxyConfigDictionary::CreateSystem();
|
|
break;
|
|
case ProxyPrefs::MODE_AUTO_DETECT:
|
|
proxy_config = ProxyConfigDictionary::CreateAutoDetect();
|
|
break;
|
|
case ProxyPrefs::MODE_PAC_SCRIPT:
|
|
proxy_config = ProxyConfigDictionary::CreatePacScript(pac_url, true);
|
|
break;
|
|
case ProxyPrefs::MODE_FIXED_SERVERS:
|
|
proxy_config =
|
|
ProxyConfigDictionary::CreateFixedServers(proxy_rules, bypass_list);
|
|
break;
|
|
default:
|
|
NOTIMPLEMENTED();
|
|
}
|
|
|
|
static_cast<BrowserProcessImpl*>(g_browser_process)
|
|
->in_memory_pref_store()
|
|
->SetValue(proxy_config::prefs::kProxy,
|
|
base::Value{std::move(proxy_config)},
|
|
WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
|
|
|
|
g_browser_process->system_network_context_manager()
|
|
->GetContext()
|
|
->ForceReloadProxyConfig(base::BindOnce(
|
|
gin_helper::Promise<void>::ResolvePromise, std::move(promise)));
|
|
|
|
return handle;
|
|
}
|
|
|
|
v8::Local<v8::Promise> App::ResolveProxy(gin::Arguments* args) {
|
|
v8::Isolate* isolate = args->isolate();
|
|
gin_helper::Promise<std::string> promise(isolate);
|
|
v8::Local<v8::Promise> handle = promise.GetHandle();
|
|
|
|
GURL url;
|
|
args->GetNext(&url);
|
|
|
|
static_cast<BrowserProcessImpl*>(g_browser_process)
|
|
->GetResolveProxyHelper()
|
|
->ResolveProxy(
|
|
url, base::BindOnce(gin_helper::Promise<std::string>::ResolvePromise,
|
|
std::move(promise)));
|
|
|
|
return handle;
|
|
}
|
|
|
|
void App::SetUserAgentFallback(const std::string& user_agent) {
|
|
ElectronBrowserClient::Get()->SetUserAgent(user_agent);
|
|
}
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
bool App::IsRunningUnderARM64Translation() const {
|
|
return base::win::OSInfo::IsRunningEmulatedOnArm64();
|
|
}
|
|
#endif
|
|
|
|
std::string App::GetUserAgentFallback() {
|
|
return ElectronBrowserClient::Get()->GetUserAgent();
|
|
}
|
|
|
|
#if BUILDFLAG(IS_MAC)
|
|
bool App::MoveToApplicationsFolder(gin_helper::ErrorThrower thrower,
|
|
gin::Arguments* args) {
|
|
return ElectronBundleMover::Move(thrower, args);
|
|
}
|
|
|
|
bool App::IsInApplicationsFolder() {
|
|
return ElectronBundleMover::IsCurrentAppInApplicationsFolder();
|
|
}
|
|
|
|
void App::ConfigureWebAuthn(gin_helper::ErrorThrower thrower,
|
|
gin::Arguments* args) {
|
|
gin_helper::Dictionary options;
|
|
if (!args->GetNext(&options)) {
|
|
thrower.ThrowTypeError("configureWebAuthn requires an options object");
|
|
return;
|
|
}
|
|
|
|
// Validate before applying so a TypeError leaves existing configuration
|
|
// untouched; null/undefined mean "not set".
|
|
std::optional<bool> platform_passkeys;
|
|
v8::Local<v8::Value> platform_passkeys_value;
|
|
if (options.Get("platformPasskeys", &platform_passkeys_value) &&
|
|
!platform_passkeys_value->IsNullOrUndefined()) {
|
|
if (!platform_passkeys_value->IsBoolean()) {
|
|
thrower.ThrowTypeError(
|
|
"configureWebAuthn: 'platformPasskeys' must be a boolean");
|
|
return;
|
|
}
|
|
platform_passkeys = platform_passkeys_value.As<v8::Boolean>()->Value();
|
|
}
|
|
|
|
gin_helper::Dictionary touch_id;
|
|
if (options.Get("touchID", &touch_id)) {
|
|
std::string keychain_access_group;
|
|
if (!touch_id.Get("keychainAccessGroup", &keychain_access_group) ||
|
|
keychain_access_group.empty()) {
|
|
thrower.ThrowTypeError(
|
|
"configureWebAuthn: 'touchID.keychainAccessGroup' must be a "
|
|
"non-empty string");
|
|
return;
|
|
}
|
|
|
|
// Optional: lets apps customize the macOS Touch ID prompt. The OS renders
|
|
// it as `"<App Name>" is trying to <promptReason>`. A `$1` placeholder, if
|
|
// present, is replaced with the relying party ID; otherwise the string is
|
|
// used verbatim. The default, IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON, is
|
|
// "verify your identity on $1".
|
|
std::string prompt_reason;
|
|
if (touch_id.Has("promptReason")) {
|
|
if (!touch_id.Get("promptReason", &prompt_reason) ||
|
|
prompt_reason.empty()) {
|
|
thrower.ThrowTypeError(
|
|
"configureWebAuthn: 'touchID.promptReason' must be a non-empty "
|
|
"string");
|
|
return;
|
|
}
|
|
}
|
|
|
|
ElectronWebAuthenticationDelegate::SetTouchIdKeychainAccessGroup(
|
|
std::move(keychain_access_group));
|
|
|
|
if (!prompt_reason.empty()) {
|
|
ui::ResourceBundle::GetSharedInstance().OverrideLocaleStringResource(
|
|
IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON,
|
|
base::UTF8ToUTF16(prompt_reason));
|
|
}
|
|
}
|
|
|
|
if (platform_passkeys.has_value()) {
|
|
ElectronWebAuthenticationDelegate::SetPlatformPasskeysEnabled(
|
|
*platform_passkeys);
|
|
}
|
|
}
|
|
|
|
int DockBounce(gin::Arguments* args) {
|
|
int request_id = -1;
|
|
std::string type = "informational";
|
|
args->GetNext(&type);
|
|
|
|
if (type == "critical")
|
|
request_id = Browser::Get()->DockBounce(Browser::BounceType::kCritical);
|
|
else if (type == "informational")
|
|
request_id =
|
|
Browser::Get()->DockBounce(Browser::BounceType::kInformational);
|
|
return request_id;
|
|
}
|
|
|
|
void App::DockSetMenu(electron::api::Menu* menu) {
|
|
Browser::Get()->DockSetMenu(menu->model());
|
|
dock_menu_ = menu;
|
|
}
|
|
|
|
v8::Local<v8::Value> App::DockGetMenu(v8::Isolate* isolate) {
|
|
v8::Local<v8::Object> menu;
|
|
if (dock_menu_ && dock_menu_->GetWrapper(isolate).ToLocal(&menu))
|
|
return menu;
|
|
return v8::Null(isolate);
|
|
}
|
|
|
|
v8::Local<v8::Value> App::GetDockAPI(v8::Isolate* isolate) {
|
|
if (dock_.IsEmptyThreadSafe()) {
|
|
// Initialize the Dock API, the methods are bound to "dock" which exists
|
|
// for the lifetime of "app"
|
|
auto browser = base::Unretained(Browser::Get());
|
|
auto dock_obj = gin_helper::Dictionary::CreateEmpty(isolate);
|
|
dock_obj.SetMethod<&DockBounce>("bounce");
|
|
dock_obj.SetMethod(
|
|
"cancelBounce",
|
|
base::BindRepeating(&Browser::DockCancelBounce, browser));
|
|
dock_obj.SetMethod(
|
|
"downloadFinished",
|
|
base::BindRepeating(&Browser::DockDownloadFinished, browser));
|
|
dock_obj.SetMethod(
|
|
"setBadge", base::BindRepeating(&Browser::DockSetBadgeText, browser));
|
|
dock_obj.SetMethod(
|
|
"getBadge", base::BindRepeating(&Browser::DockGetBadgeText, browser));
|
|
dock_obj.SetMethod("hide",
|
|
base::BindRepeating(&Browser::DockHide, browser));
|
|
dock_obj.SetMethod("show",
|
|
base::BindRepeating(&Browser::DockShow, browser));
|
|
dock_obj.SetMethod("isVisible",
|
|
base::BindRepeating(&Browser::DockIsVisible, browser));
|
|
dock_obj.SetMethod("setMenu", base::BindRepeating(&App::DockSetMenu,
|
|
base::Unretained(this)));
|
|
dock_obj.SetMethod("getMenu", base::BindRepeating(&App::DockGetMenu,
|
|
base::Unretained(this)));
|
|
dock_obj.SetMethod("setIcon",
|
|
base::BindRepeating(&Browser::DockSetIcon, browser));
|
|
|
|
dock_.Reset(isolate, dock_obj.GetHandle());
|
|
}
|
|
return dock_.Get(isolate);
|
|
}
|
|
#endif
|
|
|
|
void ConfigureHostResolver(v8::Isolate* isolate,
|
|
const gin_helper::Dictionary& opts) {
|
|
gin_helper::ErrorThrower thrower(isolate);
|
|
if (!Browser::Get()->is_ready()) {
|
|
thrower.ThrowError(
|
|
"configureHostResolver cannot be called before the app is ready");
|
|
return;
|
|
}
|
|
net::SecureDnsMode secure_dns_mode = net::SecureDnsMode::kOff;
|
|
std::string default_doh_templates;
|
|
net::DnsOverHttpsConfig doh_config;
|
|
if (!default_doh_templates.empty() &&
|
|
secure_dns_mode != net::SecureDnsMode::kOff) {
|
|
auto maybe_doh_config =
|
|
net::DnsOverHttpsConfig::FromString(default_doh_templates);
|
|
if (maybe_doh_config.has_value())
|
|
doh_config = maybe_doh_config.value();
|
|
}
|
|
|
|
bool enable_built_in_resolver =
|
|
base::FeatureList::IsEnabled(net::features::kAsyncDns);
|
|
bool enable_happy_eyeballs_v3 =
|
|
base::FeatureList::IsEnabled(net::features::kHappyEyeballsV3);
|
|
bool additional_dns_query_types_enabled = true;
|
|
|
|
if (opts.Has("enableBuiltInResolver") &&
|
|
!opts.Get("enableBuiltInResolver", &enable_built_in_resolver)) {
|
|
thrower.ThrowTypeError("enableBuiltInResolver must be a boolean");
|
|
return;
|
|
}
|
|
|
|
if (opts.Has("enableHappyEyeballs") &&
|
|
!opts.Get("enableHappyEyeballs", &enable_happy_eyeballs_v3)) {
|
|
thrower.ThrowTypeError("enableHappyEyeballs must be a boolean");
|
|
return;
|
|
}
|
|
|
|
if (opts.Has("secureDnsMode") &&
|
|
!opts.Get("secureDnsMode", &secure_dns_mode)) {
|
|
thrower.ThrowTypeError(
|
|
"secureDnsMode must be one of: off, automatic, secure");
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> secure_dns_server_strings;
|
|
if (opts.Has("secureDnsServers")) {
|
|
if (!opts.Get("secureDnsServers", &secure_dns_server_strings)) {
|
|
thrower.ThrowTypeError("secureDnsServers must be an array of strings");
|
|
return;
|
|
}
|
|
|
|
// Validate individual server templates prior to batch-assigning to
|
|
// doh_config.
|
|
std::vector<net::DnsOverHttpsServerConfig> servers;
|
|
for (const std::string& server_template : secure_dns_server_strings) {
|
|
std::optional<net::DnsOverHttpsServerConfig> server_config =
|
|
net::DnsOverHttpsServerConfig::FromString(server_template);
|
|
if (!server_config.has_value()) {
|
|
thrower.ThrowTypeError(std::string("not a valid DoH template: ") +
|
|
server_template);
|
|
return;
|
|
}
|
|
servers.push_back(*server_config);
|
|
}
|
|
doh_config = net::DnsOverHttpsConfig(std::move(servers));
|
|
}
|
|
|
|
if (opts.Has("enableAdditionalDnsQueryTypes") &&
|
|
!opts.Get("enableAdditionalDnsQueryTypes",
|
|
&additional_dns_query_types_enabled)) {
|
|
thrower.ThrowTypeError("enableAdditionalDnsQueryTypes must be a boolean");
|
|
return;
|
|
}
|
|
|
|
// Configure the stub resolver. This must be done after the system
|
|
// NetworkContext is created, but before anything has the chance to use it.
|
|
content::GetNetworkService()->ConfigureStubHostResolver(
|
|
enable_built_in_resolver ? net::InsecureDnsMode::kEnabledBuiltIn
|
|
: net::InsecureDnsMode::kDisabled,
|
|
enable_happy_eyeballs_v3, secure_dns_mode, doh_config,
|
|
additional_dns_query_types_enabled, {} /*fallback_doh_nameservers*/);
|
|
}
|
|
|
|
// static
|
|
App* App::Get() {
|
|
static base::NoDestructor<cppgc::Persistent<App>> instance([] {
|
|
v8::Isolate* const isolate = JavascriptEnvironment::GetIsolate();
|
|
return cppgc::Persistent<App>(cppgc::MakeGarbageCollected<App>(
|
|
isolate->GetCppHeap()->GetAllocationHandle()));
|
|
}());
|
|
return instance->Get();
|
|
}
|
|
|
|
const gin::WrapperInfo* App::wrapper_info() const {
|
|
return &kWrapperInfo;
|
|
}
|
|
|
|
void App::Trace(cppgc::Visitor* visitor) const {
|
|
gin::Wrappable<App>::Trace(visitor);
|
|
visitor->Trace(command_line_);
|
|
visitor->Trace(client_cert_password_handler_);
|
|
visitor->Trace(name_string_);
|
|
visitor->Trace(version_string_);
|
|
visitor->Trace(app_path_value_);
|
|
#if BUILDFLAG(IS_MAC)
|
|
visitor->Trace(dock_);
|
|
visitor->Trace(dock_menu_);
|
|
#endif
|
|
}
|
|
|
|
void App::SetClientCertRequestPasswordHandler(v8::Isolate* isolate,
|
|
v8::Local<v8::Value> handler) {
|
|
if (handler->IsFunction())
|
|
client_cert_password_handler_.Reset(isolate, handler.As<v8::Function>());
|
|
else
|
|
client_cert_password_handler_.Reset();
|
|
}
|
|
|
|
bool App::RequestClientCertPassword(
|
|
const std::string& hostname,
|
|
const std::string& token_name,
|
|
bool is_retry,
|
|
base::OnceCallback<void(const std::string&)> callback) {
|
|
if (client_cert_password_handler_.IsEmpty())
|
|
return false;
|
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
|
v8::HandleScope handle_scope(isolate);
|
|
v8::Local<v8::Context> context = isolate->GetCurrentContext();
|
|
auto params = gin_helper::Dictionary::CreateEmpty(isolate);
|
|
params.Set("hostname", hostname);
|
|
params.Set("tokenName", token_name);
|
|
params.Set("isRetry", is_retry);
|
|
v8::Local<v8::Value> argv[] = {params.GetHandle()};
|
|
auto password_callback =
|
|
electron::AdaptCallbackForRepeating(std::move(callback));
|
|
v8::Local<v8::Object> wrapper;
|
|
v8::Local<v8::Value> result;
|
|
v8::Local<v8::Promise::Resolver> resolver;
|
|
if (!GetWrapper(isolate).ToLocal(&wrapper) ||
|
|
!node::MakeCallback(isolate, wrapper,
|
|
client_cert_password_handler_.Get(isolate), 1, argv,
|
|
{0, 0})
|
|
.ToLocal(&result) ||
|
|
!v8::Promise::Resolver::New(context).ToLocal(&resolver) ||
|
|
resolver->Resolve(context, result).IsNothing()) {
|
|
password_callback.Run(std::string());
|
|
return true;
|
|
}
|
|
auto on_password = base::BindRepeating(
|
|
[](const base::RepeatingCallback<void(const std::string&)>& callback,
|
|
gin::Arguments* args) {
|
|
std::string password;
|
|
args->GetNext(&password);
|
|
callback.Run(password);
|
|
},
|
|
password_callback);
|
|
auto on_rejected = base::BindRepeating(
|
|
[](const base::RepeatingCallback<void(const std::string&)>& callback) {
|
|
callback.Run(std::string());
|
|
},
|
|
password_callback);
|
|
if (resolver->GetPromise()
|
|
->Then(context,
|
|
gin::ConvertToV8(isolate, on_password).As<v8::Function>(),
|
|
gin::ConvertToV8(isolate, on_rejected).As<v8::Function>())
|
|
.IsEmpty()) {
|
|
password_callback.Run(std::string());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
v8::Local<v8::Value> App::GetCommandLine(v8::Isolate* isolate) {
|
|
if (command_line_.IsEmptyThreadSafe()) {
|
|
auto command_line = gin_helper::Dictionary::CreateEmpty(isolate);
|
|
FillCommandLine(&command_line);
|
|
command_line_.Reset(isolate, command_line.GetHandle());
|
|
}
|
|
return command_line_.Get(isolate);
|
|
}
|
|
|
|
gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
|
|
auto browser = base::Unretained(Browser::Get());
|
|
return gin::Wrappable<App>::GetObjectTemplateBuilder(isolate)
|
|
.SetMethod("quit", base::BindRepeating(&Browser::Quit, browser))
|
|
.SetMethod("exit", base::BindRepeating(&Browser::Exit, browser))
|
|
.SetMethod("focus", base::BindRepeating(&Browser::Focus, browser))
|
|
.SetMethod("getVersion", &App::GetVersionString)
|
|
.SetMethod("setVersion",
|
|
base::BindRepeating(&Browser::SetVersion, browser))
|
|
.SetMethod("getName", &App::GetNameString)
|
|
.SetMethod("setName", base::BindRepeating(&Browser::SetName, browser))
|
|
.SetProperty("name", &App::GetNameString,
|
|
base::BindRepeating(&Browser::SetName, browser))
|
|
.SetMethod("isReady", base::BindRepeating(&Browser::is_ready, browser))
|
|
.SetMethod("whenReady", base::BindRepeating(&Browser::WhenReady, browser))
|
|
.SetMethod("addRecentDocument",
|
|
base::BindRepeating(&Browser::AddRecentDocument, browser))
|
|
.SetMethod("clearRecentDocuments",
|
|
base::BindRepeating(&Browser::ClearRecentDocuments, browser))
|
|
.SetMethod("getRecentDocuments",
|
|
base::BindRepeating(&Browser::GetRecentDocuments, browser))
|
|
#if !BUILDFLAG(IS_WIN)
|
|
.SetMethod("setAppUserModelId", &SetAppUserModelIdNoOp)
|
|
#else
|
|
.SetMethod("setAppUserModelId",
|
|
base::BindRepeating(&Browser::SetAppUserModelID, browser))
|
|
.SetMethod("setToastActivatorCLSID",
|
|
base::BindRepeating(&App::SetToastActivatorCLSID,
|
|
base::Unretained(this)))
|
|
.SetProperty("toastActivatorCLSID", &App::GetToastActivatorCLSID)
|
|
#endif
|
|
.SetMethod(
|
|
"isDefaultProtocolClient",
|
|
base::BindRepeating(&Browser::IsDefaultProtocolClient, browser))
|
|
.SetMethod(
|
|
"setAsDefaultProtocolClient",
|
|
base::BindRepeating(&Browser::SetAsDefaultProtocolClient, browser))
|
|
.SetMethod(
|
|
"removeAsDefaultProtocolClient",
|
|
base::BindRepeating(&Browser::RemoveAsDefaultProtocolClient, browser))
|
|
.SetMethod(
|
|
"getApplicationInfoForProtocol",
|
|
base::BindRepeating(&Browser::GetApplicationInfoForProtocol, browser))
|
|
.SetMethod(
|
|
"getApplicationNameForProtocol",
|
|
base::BindRepeating(&Browser::GetApplicationNameForProtocol, browser))
|
|
.SetMethod("setBadgeCount",
|
|
base::BindRepeating(&Browser::SetBadgeCount, browser))
|
|
.SetMethod("getBadgeCount",
|
|
base::BindRepeating(&Browser::badge_count, browser))
|
|
.SetProperty("badgeCount",
|
|
base::BindRepeating(&Browser::badge_count, browser),
|
|
base::BindRepeating(&Browser::SetBadgeCount, browser))
|
|
.SetMethod("getLoginItemSettings", &App::GetLoginItemSettings)
|
|
.SetMethod("setLoginItemSettings",
|
|
base::BindRepeating(&Browser::SetLoginItemSettings, browser))
|
|
.SetMethod("isEmojiPanelSupported",
|
|
base::BindRepeating(&Browser::IsEmojiPanelSupported, browser))
|
|
#if BUILDFLAG(IS_MAC)
|
|
.SetMethod("isActive", base::BindRepeating(&Browser::IsActive, browser))
|
|
.SetMethod("hide", base::BindRepeating(&Browser::Hide, browser))
|
|
.SetMethod("isHidden", base::BindRepeating(&Browser::IsHidden, browser))
|
|
.SetMethod("show", base::BindRepeating(&Browser::Show, browser))
|
|
.SetMethod("setUserActivity",
|
|
base::BindRepeating(&Browser::SetUserActivity, browser))
|
|
.SetMethod("getCurrentActivityType",
|
|
base::BindRepeating(&Browser::GetCurrentActivityType, browser))
|
|
.SetMethod(
|
|
"invalidateCurrentActivity",
|
|
base::BindRepeating(&Browser::InvalidateCurrentActivity, browser))
|
|
.SetMethod("resignCurrentActivity",
|
|
base::BindRepeating(&Browser::ResignCurrentActivity, browser))
|
|
.SetMethod("updateCurrentActivity",
|
|
base::BindRepeating(&Browser::UpdateCurrentActivity, browser))
|
|
.SetMethod("moveToApplicationsFolder", &App::MoveToApplicationsFolder)
|
|
.SetMethod("isInApplicationsFolder", &App::IsInApplicationsFolder)
|
|
.SetMethod("setActivationPolicy", &App::SetActivationPolicy)
|
|
.SetMethod("configureWebAuthn", &App::ConfigureWebAuthn)
|
|
.SetMethod("startAppShimServer", &App::StartAppShimServer)
|
|
.SetMethod("stopAppShimServer", &App::StopAppShimServer)
|
|
#endif
|
|
.SetMethod("setAboutPanelOptions",
|
|
base::BindRepeating(&Browser::SetAboutPanelOptions, browser))
|
|
.SetMethod("showAboutPanel",
|
|
base::BindRepeating(&Browser::ShowAboutPanel, browser))
|
|
#if BUILDFLAG(IS_MAC)
|
|
.SetMethod(
|
|
"isSecureKeyboardEntryEnabled",
|
|
base::BindRepeating(&Browser::IsSecureKeyboardEntryEnabled, browser))
|
|
.SetMethod(
|
|
"setSecureKeyboardEntryEnabled",
|
|
base::BindRepeating(&Browser::SetSecureKeyboardEntryEnabled, browser))
|
|
#endif
|
|
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
|
|
.SetMethod("showEmojiPanel",
|
|
base::BindRepeating(&Browser::ShowEmojiPanel, browser))
|
|
#endif
|
|
#if BUILDFLAG(IS_WIN)
|
|
.SetMethod("setUserTasks",
|
|
base::BindRepeating(&Browser::SetUserTasks, browser))
|
|
.SetMethod("getJumpListSettings", &App::GetJumpListSettings)
|
|
.SetMethod("setJumpList", &App::SetJumpList)
|
|
.SetMethod("canDeElevate", &App::CanDeElevate)
|
|
#endif
|
|
.SetProperty("isPackaged", &App::IsPackaged)
|
|
.SetMethod("setAppPath", &App::SetAppPath)
|
|
.SetMethod("getAppPath", &App::GetAppPathValue)
|
|
.SetMethod("setPath", &App::SetPath)
|
|
.SetMethod("getPath", &App::GetPath)
|
|
.SetMethod("setAppLogsPath", &App::SetAppLogsPath)
|
|
.SetMethod("setDesktopName", &App::SetDesktopName)
|
|
.SetMethod("setActivationToken", &App::SetActivationToken)
|
|
.SetMethod("getLocale", &App::GetLocale)
|
|
.SetMethod("getPreferredSystemLanguages", &GetPreferredLanguages)
|
|
.SetMethod("getSystemLocale", &App::GetSystemLocale)
|
|
.SetMethod("getLocaleCountryCode", &App::GetLocaleCountryCode)
|
|
#if BUILDFLAG(USE_NSS_CERTS)
|
|
.SetMethod("importCertificate", &App::ImportCertificate)
|
|
#endif
|
|
.SetMethod("hasSingleInstanceLock", &App::HasSingleInstanceLock)
|
|
.SetMethod("requestSingleInstanceLock", &App::RequestSingleInstanceLock)
|
|
.SetMethod("releaseSingleInstanceLock", &App::ReleaseSingleInstanceLock)
|
|
.SetMethod("relaunch", &App::Relaunch)
|
|
.SetMethod("isAccessibilitySupportEnabled",
|
|
&App::IsAccessibilitySupportEnabled)
|
|
.SetMethod("getAccessibilitySupportFeatures",
|
|
&App::GetAccessibilitySupportFeatures)
|
|
.SetMethod("setAccessibilitySupportFeatures",
|
|
&App::SetAccessibilitySupportFeatures)
|
|
.SetMethod("setAccessibilitySupportEnabled",
|
|
&App::SetAccessibilitySupportEnabled)
|
|
.SetProperty("accessibilitySupportEnabled",
|
|
&App::IsAccessibilitySupportEnabled,
|
|
&App::SetAccessibilitySupportEnabled)
|
|
.SetMethod("disableHardwareAcceleration",
|
|
&App::DisableHardwareAcceleration)
|
|
.SetMethod("isHardwareAccelerationEnabled",
|
|
&App::IsHardwareAccelerationEnabled)
|
|
.SetMethod("disableDomainBlockingFor3DAPIs",
|
|
&App::DisableDomainBlockingFor3DAPIs)
|
|
.SetMethod("getFileIcon", &App::GetFileIcon)
|
|
.SetMethod("getAppMetrics", &App::GetAppMetrics)
|
|
.SetMethod("getGPUFeatureStatus", &App::GetGPUFeatureStatus)
|
|
.SetMethod("getGPUInfo", &App::GetGPUInfo)
|
|
#if IS_MAS_BUILD()
|
|
.SetMethod("startAccessingSecurityScopedResource",
|
|
&App::StartAccessingSecurityScopedResource)
|
|
#endif
|
|
#if BUILDFLAG(IS_MAC)
|
|
.SetProperty("dock", &App::GetDockAPI)
|
|
#endif
|
|
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
|
|
.SetProperty("runningUnderARM64Translation",
|
|
&App::IsRunningUnderARM64Translation)
|
|
#endif
|
|
.SetProperty("userAgentFallback", &App::GetUserAgentFallback,
|
|
&App::SetUserAgentFallback)
|
|
.SetProperty("applicationMenu", &Menu::GetApplicationMenu,
|
|
&Menu::SetApplicationMenuFromJS)
|
|
.SetProperty("commandLine", &App::GetCommandLine)
|
|
.SetMethod("setClientCertRequestPasswordHandler",
|
|
&App::SetClientCertRequestPasswordHandler)
|
|
.SetMethod("configureHostResolver", &ConfigureHostResolver)
|
|
.SetMethod("enableSandbox", &App::EnableSandbox)
|
|
.SetMethod("setProxy", &App::SetProxy)
|
|
.SetMethod("resolveProxy", &App::ResolveProxy);
|
|
}
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
void App::SetToastActivatorCLSID(gin_helper::ErrorThrower thrower,
|
|
const std::string& id) {
|
|
std::wstring wide = base::UTF8ToWide(id);
|
|
CLSID parsed;
|
|
if (FAILED(::CLSIDFromString(wide.c_str(), &parsed))) {
|
|
if (!wide.empty() && wide.front() != L'{') {
|
|
std::wstring with_braces = L"{" + wide + L"}";
|
|
if (FAILED(::CLSIDFromString(with_braces.c_str(), &parsed))) {
|
|
thrower.ThrowError("Invalid CLSID format");
|
|
return;
|
|
}
|
|
wide = std::move(with_braces);
|
|
} else {
|
|
thrower.ThrowError("Invalid CLSID format");
|
|
return;
|
|
}
|
|
}
|
|
|
|
SetAppToastActivatorCLSID(wide);
|
|
}
|
|
|
|
v8::Local<v8::Value> App::GetToastActivatorCLSID(v8::Isolate* isolate) {
|
|
return gin::ConvertToV8(isolate,
|
|
base::WideToUTF8(GetAppToastActivatorCLSID()));
|
|
}
|
|
#endif
|
|
|
|
const char* App::GetHumanReadableName() const {
|
|
return "Electron / App";
|
|
}
|
|
|
|
} // namespace electron::api
|
|
|
|
namespace {
|
|
|
|
void Initialize(v8::Local<v8::Object> exports,
|
|
v8::Local<v8::Value> unused,
|
|
v8::Local<v8::Context> context,
|
|
void* priv) {
|
|
v8::Isolate* const isolate = electron::JavascriptEnvironment::GetIsolate();
|
|
gin_helper::Dictionary dict{isolate, exports};
|
|
electron::api::App* app = electron::api::App::Get();
|
|
v8::Local<v8::Object> wrapper;
|
|
if (app->GetWrapper(isolate).ToLocal(&wrapper)) {
|
|
// app is an EventEmitter.
|
|
std::ignore = wrapper->SetPrototype(
|
|
context, electron::GetEventEmitterPrototype(isolate));
|
|
}
|
|
dict.Set("app", app);
|
|
#if BUILDFLAG(IS_LINUX)
|
|
// For desktop-name.spec.
|
|
dict.SetMethod(
|
|
"defaultDesktopName",
|
|
base::BindRepeating([](std::optional<std::u16string> name) {
|
|
return electron::DefaultDesktopName(name.value_or(std::u16string()));
|
|
}));
|
|
#endif
|
|
}
|
|
|
|
} // namespace
|
|
|
|
NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_app, Initialize)
|