Merge branch 'master' into roller/chromium/master

This commit is contained in:
Jeremy Rose 2021-03-30 09:47:35 -07:00 committed by GitHub
commit 5d13820441
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 226 additions and 103 deletions

View file

@ -1 +1 @@
14.0.0-nightly.20210323
14.0.0-nightly.20210330

View file

@ -144,7 +144,6 @@ These individual tutorials expand on topics discussed in the guide above.
### Modules for the Renderer Process (Web Page):
* [contextBridge](api/context-bridge.md)
* [desktopCapturer](api/desktop-capturer.md)
* [ipcRenderer](api/ipc-renderer.md)
* [webFrame](api/web-frame.md)
@ -152,6 +151,7 @@ These individual tutorials expand on topics discussed in the guide above.
* [clipboard](api/clipboard.md)
* [crashReporter](api/crash-reporter.md)
* [desktopCapturer](api/desktop-capturer.md)
* [nativeImage](api/native-image.md)
* [shell](api/shell.md)

View file

@ -75,6 +75,12 @@ This switch can not be used in `app.commandLine.appendSwitch` since it is parsed
earlier than user's app is loaded, but you can set the `ELECTRON_ENABLE_LOGGING`
environment variable to achieve the same effect.
## --force-fieldtrials=`trials`
Field trials to be forcefully enabled or disabled.
For example: `WebRTC-Audio-Red-For-Opus/Enabled/`
### --host-rules=`rules`
A comma-separated list of `rules` that control how hostnames are mapped.

View file

@ -1131,6 +1131,7 @@ Ignore application menu shortcuts while this web contents is focused.
* `url` String - The _resolved_ version of the URL passed to `window.open()`. e.g. opening a window with `window.open('foo')` will yield something like `https://the-origin/the/current/path/foo`.
* `frameName` String - Name of the window provided in `window.open()`
* `features` String - Comma separated list of window features provided to `window.open()`.
Returns `{action: 'deny'} | {action: 'allow', overrideBrowserWindowOptions?: BrowserWindowConstructorOptions}` - `deny` cancels the creation of the new
window. `allow` will allow the new window to be created. Specifying `overrideBrowserWindowOptions` allows customization of the created window.
Returning an unrecognized value such as a null, undefined, or an object

View file

@ -1,6 +1,6 @@
{
"name": "electron",
"version": "14.0.0-nightly.20210323",
"version": "14.0.0-nightly.20210330",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {

View file

@ -122,10 +122,10 @@ index 8e53d65ddca7b54a6effd1767257a4d8239251d8..8a00bf59c728217069000b1f1ece72e1
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
+ auto remote_cert_verifier = std::make_unique<RemoteCertVerifier>(std::move(cert_verifier));
+ remote_cert_verifier_ = remote_cert_verifier.get();
+ cert_verifier = std::make_unique<net::CachingCertVerifier>(std::move(remote_cert_verifier));
}
+ cert_verifier = std::move(remote_cert_verifier);
builder.SetCertVerifier(IgnoreErrorsCertVerifier::MaybeWrapCertVerifier(
// Whether the cert verifier is remote or in-process, we should wrap it in
// caching and coalescing layers to avoid extra verifications and IPCs.
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 72885bc1d20a4da5ad4df3fb8185f05bcf6fbfba..06b1a0d550de946aa41efca2be4efde694cc24c7 100644
--- a/services/network/network_context.h

View file

@ -47,7 +47,7 @@ def get_repo_root(path):
def am(repo, patch_data, threeway=False, directory=None, exclude=None,
committer_name=None, committer_email=None):
committer_name=None, committer_email=None, keep_cr=True):
args = []
if threeway:
args += ['--3way']
@ -56,6 +56,10 @@ def am(repo, patch_data, threeway=False, directory=None, exclude=None,
if exclude is not None:
for path_pattern in exclude:
args += ['--exclude', path_pattern]
if keep_cr is True:
# Keep the CR of CRLF in case any patches target files with Windows line
# endings.
args += ['--keep-cr']
root_args = ['-C', repo]
if committer_name is not None:
@ -230,7 +234,9 @@ def split_patches(patch_data):
"""Split a concatenated series of patches into N separate patches"""
patches = []
patch_start = re.compile('^From [0-9a-f]+ ')
for line in patch_data.splitlines():
# Keep line endings in case any patches target files with CRLF.
keep_line_endings = True
for line in patch_data.splitlines(keep_line_endings):
if patch_start.match(line):
patches.append([])
patches[-1].append(line)
@ -246,13 +252,23 @@ def munge_subject_to_filename(subject):
def get_file_name(patch):
"""Return the name of the file to which the patch should be written"""
file_name = None
for line in patch:
if line.startswith('Patch-Filename: '):
return line[len('Patch-Filename: '):]
file_name = line[len('Patch-Filename: '):]
break
# If no patch-filename header, munge the subject.
for line in patch:
if line.startswith('Subject: '):
return munge_subject_to_filename(line[len('Subject: '):])
if not file_name:
for line in patch:
if line.startswith('Subject: '):
file_name = munge_subject_to_filename(line[len('Subject: '):])
break
return file_name.rstrip('\n')
def join_patch(patch):
"""Joins and formats patch contents"""
return ''.join(remove_patch_filename(patch)).rstrip('\n') + '\n'
def remove_patch_filename(patch):
@ -294,10 +310,8 @@ def export_patches(repo, out_dir, patch_range=None, dry_run=False):
for patch in patches:
filename = get_file_name(patch)
filepath = posixpath.join(out_dir, filename)
existing_patch = io.open(filepath, 'r', encoding='utf-8').read()
formatted_patch = (
'\n'.join(remove_patch_filename(patch)).rstrip('\n') + '\n'
)
existing_patch = io.open(filepath, 'rb').read()
formatted_patch = join_patch(patch)
if formatted_patch != existing_patch:
patch_count += 1
if patch_count > 0:
@ -322,12 +336,11 @@ def export_patches(repo, out_dir, patch_range=None, dry_run=False):
for patch in patches:
filename = get_file_name(patch)
file_path = posixpath.join(out_dir, filename)
formatted_patch = (
'\n'.join(remove_patch_filename(patch)).rstrip('\n') + '\n'
)
formatted_patch = join_patch(patch)
# Write in binary mode to retain mixed line endings on write.
with io.open(
file_path, 'w', newline='\n', encoding='utf-8'
file_path, 'wb'
) as f:
f.write(formatted_patch)
f.write(formatted_patch.encode('utf-8'))
pl.write(filename + '\n')

View file

@ -207,7 +207,7 @@ const LINTERS = [{
console.warn(`Patch file '${f}' has no description. Every patch must contain a justification for why the patch exists and the plan for its removal.`);
return false;
}
const trailingWhitespace = patchText.split('\n').filter(line => line.startsWith('+')).some(line => /\s+$/.test(line));
const trailingWhitespace = patchText.split(/\r?\n/).some(line => line.startsWith('+') && /\s+$/.test(line));
if (trailingWhitespace) {
console.warn(`Patch file '${f}' has trailing whitespace on some lines.`);
return false;

View file

@ -190,8 +190,9 @@ namespace {
#if defined(MAS_BUILD)
void GetUploadedReports(
v8::Isolate* isolate,
base::OnceCallback<void(v8::Local<v8::Value>)> callback) {
std::move(callback).Run(v8::Array::New(v8::Isolate::GetCurrent()));
std::move(callback).Run(v8::Array::New(isolate));
}
#else
scoped_refptr<UploadList> CreateCrashUploadList() {

View file

@ -84,7 +84,7 @@ DownloadItem* DownloadItem::FromDownloadItem(
DownloadItem::DownloadItem(v8::Isolate* isolate,
download::DownloadItem* download_item)
: download_item_(download_item) {
: download_item_(download_item), isolate_(isolate) {
download_item_->AddObserver(this);
download_item_->SetUserData(
kElectronApiDownloadItemKey,
@ -101,8 +101,8 @@ DownloadItem::~DownloadItem() {
bool DownloadItem::CheckAlive() const {
if (!download_item_) {
gin_helper::ErrorThrower(v8::Isolate::GetCurrent())
.ThrowError("DownloadItem used after being destroyed");
gin_helper::ErrorThrower(isolate_).ThrowError(
"DownloadItem used after being destroyed");
return false;
}
return true;
@ -200,10 +200,10 @@ const GURL& DownloadItem::GetURL() const {
return download_item_->GetURL();
}
v8::Local<v8::Value> DownloadItem::GetURLChain(v8::Isolate* isolate) const {
v8::Local<v8::Value> DownloadItem::GetURLChain() const {
if (!CheckAlive())
return v8::Local<v8::Value>();
return gin::ConvertToV8(isolate, download_item_->GetUrlChain());
return gin::ConvertToV8(isolate_, download_item_->GetUrlChain());
}
download::DownloadItem::DownloadState DownloadItem::GetState() const {

View file

@ -66,7 +66,7 @@ class DownloadItem : public gin::Wrappable<DownloadItem>,
std::string GetFilename() const;
std::string GetContentDisposition() const;
const GURL& GetURL() const;
v8::Local<v8::Value> GetURLChain(v8::Isolate*) const;
v8::Local<v8::Value> GetURLChain() const;
download::DownloadItem::DownloadState GetState() const;
bool IsDone() const;
void SetSaveDialogOptions(const file_dialog::DialogSettings& options);
@ -78,6 +78,8 @@ class DownloadItem : public gin::Wrappable<DownloadItem>,
file_dialog::DialogSettings dialog_options_;
download::DownloadItem* download_item_;
v8::Isolate* isolate_;
base::WeakPtrFactory<DownloadItem> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(DownloadItem);

View file

@ -264,7 +264,7 @@ void Menu::OnMenuWillClose() {
}
void Menu::OnMenuWillShow() {
Pin(v8::Isolate::GetCurrent());
Pin(JavascriptEnvironment::GetIsolate());
Emit("menu-will-show");
}

View file

@ -331,7 +331,8 @@ const void* kElectronApiSessionKey = &kElectronApiSessionKey;
gin::WrapperInfo Session::kWrapperInfo = {gin::kEmbedderNativeGin};
Session::Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context)
: network_emulation_token_(base::UnguessableToken::Create()),
: isolate_(isolate),
network_emulation_token_(base::UnguessableToken::Create()),
browser_context_(browser_context) {
// Observe DownloadManager to get download notifications.
content::BrowserContext::GetDownloadManager(browser_context)
@ -379,10 +380,9 @@ void Session::OnDownloadCreated(content::DownloadManager* manager,
if (item->IsSavePackageDownload())
return;
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::Locker locker(isolate);
v8::HandleScope handle_scope(isolate);
auto handle = DownloadItem::FromOrCreate(isolate, item);
v8::Locker locker(isolate_);
v8::HandleScope handle_scope(isolate_);
auto handle = DownloadItem::FromOrCreate(isolate_, item);
if (item->GetState() == download::DownloadItem::INTERRUPTED)
handle->SetSavePath(item->GetTargetFilePath());
content::WebContents* web_contents =
@ -425,8 +425,7 @@ v8::Local<v8::Promise> Session::ResolveProxy(gin::Arguments* args) {
}
v8::Local<v8::Promise> Session::GetCacheSize() {
auto* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Promise<int64_t> promise(isolate);
gin_helper::Promise<int64_t> promise(isolate_);
auto handle = promise.GetHandle();
content::BrowserContext::GetDefaultStoragePartition(browser_context_)
@ -449,8 +448,7 @@ v8::Local<v8::Promise> Session::GetCacheSize() {
}
v8::Local<v8::Promise> Session::ClearCache() {
auto* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Promise<void> promise(isolate);
gin_helper::Promise<void> promise(isolate_);
auto handle = promise.GetHandle();
content::BrowserContext::GetDefaultStoragePartition(browser_context_)
@ -558,8 +556,7 @@ v8::Local<v8::Promise> Session::SetProxy(gin::Arguments* args) {
}
v8::Local<v8::Promise> Session::ForceReloadProxyConfig() {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Promise<void> promise(isolate);
gin_helper::Promise<void> promise(isolate_);
auto handle = promise.GetHandle();
content::BrowserContext::GetDefaultStoragePartition(browser_context_)
@ -675,8 +672,7 @@ v8::Local<v8::Promise> Session::ClearHostResolverCache(gin::Arguments* args) {
}
v8::Local<v8::Promise> Session::ClearAuthCache() {
auto* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Promise<void> promise(isolate);
gin_helper::Promise<void> promise(isolate_);
v8::Local<v8::Promise> handle = promise.GetHandle();
content::BrowserContext::GetDefaultStoragePartition(browser_context_)
@ -763,15 +759,14 @@ void Session::CreateInterruptedDownload(const gin_helper::Dictionary& options) {
options.Get("lastModified", &last_modified);
options.Get("eTag", &etag);
options.Get("startTime", &start_time);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
if (path.empty() || url_chain.empty() || length == 0) {
isolate->ThrowException(v8::Exception::Error(gin::StringToV8(
isolate, "Must pass non-empty path, urlChain and length.")));
isolate_->ThrowException(v8::Exception::Error(gin::StringToV8(
isolate_, "Must pass non-empty path, urlChain and length.")));
return;
}
if (offset >= length) {
isolate->ThrowException(v8::Exception::Error(gin::StringToV8(
isolate, "Must pass an offset value less than length.")));
isolate_->ThrowException(v8::Exception::Error(gin::StringToV8(
isolate_, "Must pass an offset value less than length.")));
return;
}
auto* download_manager =
@ -797,8 +792,7 @@ std::vector<base::FilePath> Session::GetPreloads() const {
v8::Local<v8::Promise> Session::LoadExtension(
const base::FilePath& extension_path,
gin::Arguments* args) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Promise<const extensions::Extension*> promise(isolate);
gin_helper::Promise<const extensions::Extension*> promise(isolate_);
v8::Local<v8::Promise> handle = promise.GetHandle();
if (!extension_path.IsAbsolute()) {
@ -833,7 +827,7 @@ v8::Local<v8::Promise> Session::LoadExtension(
if (extension) {
if (!error_msg.empty()) {
node::Environment* env =
node::Environment::GetCurrent(v8::Isolate::GetCurrent());
node::Environment::GetCurrent(promise.isolate());
EmitWarning(env, error_msg, "ExtensionLoadWarning");
}
promise.Resolve(extension);
@ -856,11 +850,10 @@ v8::Local<v8::Value> Session::GetExtension(const std::string& extension_id) {
auto* registry = extensions::ExtensionRegistry::Get(browser_context());
const extensions::Extension* extension =
registry->GetInstalledExtension(extension_id);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
if (extension) {
return gin::ConvertToV8(isolate, extension);
return gin::ConvertToV8(isolate_, extension);
} else {
return v8::Null(isolate);
return v8::Null(isolate_);
}
}
@ -872,7 +865,7 @@ v8::Local<v8::Value> Session::GetAllExtensions() {
if (extension->location() != extensions::Manifest::COMPONENT)
extensions_vector.emplace_back(extension.get());
}
return gin::ConvertToV8(v8::Isolate::GetCurrent(), extensions_vector);
return gin::ConvertToV8(isolate_, extensions_vector);
}
void Session::OnExtensionLoaded(content::BrowserContext* browser_context,
@ -967,8 +960,7 @@ void Session::Preconnect(const gin_helper::Dictionary& options,
}
v8::Local<v8::Promise> Session::CloseAllConnections() {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Promise<void> promise(isolate);
gin_helper::Promise<void> promise(isolate_);
auto handle = promise.GetHandle();
content::BrowserContext::GetDefaultStoragePartition(browser_context_)
@ -1018,8 +1010,7 @@ void SetSpellCheckerDictionaryDownloadURL(gin_helper::ErrorThrower thrower,
}
v8::Local<v8::Promise> Session::ListWordsInSpellCheckerDictionary() {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Promise<std::set<std::string>> promise(isolate);
gin_helper::Promise<std::set<std::string>> promise(isolate_);
v8::Local<v8::Promise> handle = promise.GetHandle();
SpellcheckService* spellcheck =

View file

@ -178,6 +178,8 @@ class Session : public gin::Wrappable<Session>,
v8::Global<v8::Value> service_worker_context_;
v8::Global<v8::Value> web_request_;
v8::Isolate* isolate_;
// The client id to enable the network throttler.
base::UnguessableToken network_emulation_token_;

View file

@ -312,7 +312,8 @@ void SimpleURLLoaderWrapper::Pin() {
}
void SimpleURLLoaderWrapper::PinBodyGetter(v8::Local<v8::Value> body_getter) {
pinned_chunk_pipe_getter_.Reset(v8::Isolate::GetCurrent(), body_getter);
pinned_chunk_pipe_getter_.Reset(JavascriptEnvironment::GetIsolate(),
body_getter);
}
SimpleURLLoaderWrapper::~SimpleURLLoaderWrapper() {

View file

@ -278,6 +278,9 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {
base::FeatureList::ClearInstanceForTesting();
InitializeFeatureList();
// Initialize field trials.
InitializeFieldTrials();
// Initialize after user script environment creation.
fake_browser_process_->PostEarlyInitialization();
}

View file

@ -9,6 +9,7 @@
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial.h"
#include "content/public/common/content_features.h"
#include "electron/buildflags/buildflags.h"
#include "media/base/media_switches.h"
@ -49,4 +50,12 @@ void InitializeFeatureList() {
base::FeatureList::InitializeInstance(enable_features, disable_features);
}
void InitializeFieldTrials() {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
auto force_fieldtrials =
cmd_line->GetSwitchValueASCII(::switches::kForceFieldTrials);
base::FieldTrialList::CreateTrialsFromString(force_fieldtrials);
}
} // namespace electron

View file

@ -7,6 +7,7 @@
namespace electron {
void InitializeFeatureList();
}
void InitializeFieldTrials();
} // namespace electron
#endif // SHELL_BROWSER_FEATURE_LIST_H_

View file

@ -22,6 +22,7 @@
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_media_id.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/native_browser_view_mac.h"
#include "shell/browser/ui/cocoa/electron_native_widget_mac.h"
#include "shell/browser/ui/cocoa/electron_ns_window.h"
@ -278,10 +279,11 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
options.Get(options::kVisualEffectState, &visual_effect_state_);
if (options.Has(options::kFullscreenWindowTitle)) {
EmitWarning(node::Environment::GetCurrent(v8::Isolate::GetCurrent()),
"\"fullscreenWindowTitle\" option has been deprecated and is "
"no-op now.",
"electron");
EmitWarning(
node::Environment::GetCurrent(JavascriptEnvironment::GetIsolate()),
"\"fullscreenWindowTitle\" option has been deprecated and is "
"no-op now.",
"electron");
}
bool minimizable = true;
@ -1313,7 +1315,7 @@ void NativeWindowMac::SetVibrancy(const std::string& type) {
std::string dep_warn = " has been deprecated and removed as of macOS 10.15.";
node::Environment* env =
node::Environment::GetCurrent(v8::Isolate::GetCurrent());
node::Environment::GetCurrent(JavascriptEnvironment::GetIsolate());
NSVisualEffectMaterial vibrancyType;

View file

@ -442,6 +442,14 @@ void NativeWindowViews::Hide() {
if (!features::IsUsingOzonePlatform() && global_menu_bar_)
global_menu_bar_->OnWindowUnmapped();
#endif
#if defined(OS_WIN)
// When the window is removed from the taskbar via win.hide(),
// the thumbnail buttons need to be set up again.
// Ensure that when the window is hidden,
// the taskbar host is notified that it should re-add them.
taskbar_host_.SetThumbarButtonsAdded(false);
#endif
}
bool NativeWindowViews::IsVisible() {

View file

@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 14,0,0,20210323
PRODUCTVERSION 14,0,0,20210323
FILEVERSION 14,0,0,20210330
PRODUCTVERSION 14,0,0,20210330
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L

View file

@ -114,11 +114,12 @@ bool TaskbarHost::SetThumbarButtons(HWND window,
// Finally add them to taskbar.
HRESULT r;
if (thumbar_buttons_added_)
if (thumbar_buttons_added_) {
r = taskbar_->ThumbBarUpdateButtons(window, kMaxButtonsCount,
thumb_buttons);
else
} else {
r = taskbar_->ThumbBarAddButtons(window, kMaxButtonsCount, thumb_buttons);
}
thumbar_buttons_added_ = true;
last_buttons_ = buttons;

View file

@ -60,6 +60,8 @@ class TaskbarHost {
// Called by the window that there is a button in thumbar clicked.
bool HandleThumbarButtonEvent(int button_id);
void SetThumbarButtonsAdded(bool added) { thumbar_buttons_added_ = added; }
private:
// Initialize the taskbar object.
bool InitializeTaskbar();

View file

@ -41,6 +41,8 @@ namespace context_bridge {
const char* const kProxyFunctionPrivateKey = "electron_contextBridge_proxy_fn";
const char* const kSupportsDynamicPropertiesPrivateKey =
"electron_contextBridge_supportsDynamicProperties";
const char* const kOriginalFunctionPrivateKey =
"electron_contextBridge_original_fn";
} // namespace context_bridge
@ -179,9 +181,26 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
// the global handle at the right time.
if (value->IsFunction()) {
auto func = v8::Local<v8::Function>::Cast(value);
v8::MaybeLocal<v8::Value> maybe_original_fn = GetPrivate(
source_context, func, context_bridge::kOriginalFunctionPrivateKey);
{
v8::Context::Scope destination_scope(destination_context);
v8::Local<v8::Value> proxy_func;
// If this function has already been sent over the bridge,
// then it is being sent _back_ over the bridge and we can
// simply return the original method here for performance reasons
// For safety reasons we check if the destination context is the
// creation context of the original method. If it's not we proceed
// with the proxy logic
if (maybe_original_fn.ToLocal(&proxy_func) && proxy_func->IsFunction() &&
proxy_func.As<v8::Object>()->CreationContext() ==
destination_context) {
return v8::MaybeLocal<v8::Value>(proxy_func);
}
v8::Local<v8::Object> state =
v8::Object::New(destination_context->GetIsolate());
SetPrivate(destination_context, state,
@ -190,10 +209,12 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
context_bridge::kSupportsDynamicPropertiesPrivateKey,
gin::ConvertToV8(destination_context->GetIsolate(),
support_dynamic_properties));
v8::Local<v8::Value> proxy_func;
if (!v8::Function::New(destination_context, ProxyFunctionWrapper, state)
.ToLocal(&proxy_func))
return v8::MaybeLocal<v8::Value>();
SetPrivate(destination_context, proxy_func.As<v8::Object>(),
context_bridge::kOriginalFunctionPrivateKey, func);
object_cache->CacheProxiedObject(value, proxy_func);
return v8::MaybeLocal<v8::Value>(proxy_func);
}
@ -408,21 +429,31 @@ void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::MaybeLocal<v8::Value> maybe_return_value;
bool did_error = false;
std::string error_message;
v8::Local<v8::Value> error_message;
{
v8::TryCatch try_catch(args.isolate());
maybe_return_value = func->Call(func_owning_context, func,
proxied_args.size(), proxied_args.data());
if (try_catch.HasCaught()) {
did_error = true;
auto message = try_catch.Message();
v8::Local<v8::Value> exception = try_catch.Exception();
if (message.IsEmpty() ||
!gin::ConvertFromV8(args.isolate(), message->Get(),
&error_message)) {
error_message =
"An unknown exception occurred in the isolated context, an error "
"occurred but a valid exception was not thrown.";
const char* err_msg =
"An unknown exception occurred in the isolated context, an error "
"occurred but a valid exception was not thrown.";
if (!exception->IsNull() && exception->IsObject()) {
v8::MaybeLocal<v8::Value> maybe_message =
exception.As<v8::Object>()->Get(
func_owning_context,
gin::ConvertToV8(args.isolate(), "message"));
if (!maybe_message.ToLocal(&error_message) ||
!error_message->IsString()) {
error_message = gin::StringToV8(args.isolate(), err_msg);
}
} else {
error_message = gin::StringToV8(args.isolate(), err_msg);
}
}
}
@ -430,7 +461,7 @@ void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info) {
if (did_error) {
v8::Context::Scope calling_context_scope(calling_context);
args.isolate()->ThrowException(
v8::Exception::Error(gin::StringToV8(args.isolate(), error_message)));
v8::Exception::Error(error_message.As<v8::String>()));
return;
}

View file

@ -205,7 +205,7 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
void Completed(
const blink::WebVector<v8::Local<v8::Value>>& result) override {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Isolate* isolate = promise_.isolate();
if (!result.empty()) {
if (!result[0].IsEmpty()) {
v8::Local<v8::Value> value = result[0];

View file

@ -361,11 +361,8 @@ bool RendererClientBase::IsPluginHandledExternally(
bool RendererClientBase::IsOriginIsolatedPepperPlugin(
const base::FilePath& plugin_path) {
#if BUILDFLAG(ENABLE_PDF_VIEWER)
return plugin_path.value() == kPdfPluginPath;
#else
return false;
#endif
// Isolate all Pepper plugins, including the PDF plugin.
return true;
}
std::unique_ptr<blink::WebPrescientNetworking>

View file

@ -353,6 +353,31 @@ describe('contextBridge', () => {
expect(result).equal('return-value');
});
it('should not double-proxy functions when they are returned to their origin side of the bridge', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', (fn: any) => fn);
});
const result = await callWithBindings(async (root: any) => {
const fn = () => null;
return root.example(fn) === fn;
});
expect(result).equal(true);
});
it('should properly handle errors thrown in proxied functions', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', () => { throw new Error('oh no'); });
});
const result = await callWithBindings(async (root: any) => {
try {
root.example();
} catch (e) {
return e.message;
}
});
expect(result).equal('oh no');
});
it('should proxy methods that are callable multiple times', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {

View file

@ -2,6 +2,9 @@ import { BrowserWindow, Session, session } from 'electron/main';
import { expect } from 'chai';
import * as path from 'path';
import * as fs from 'fs';
import * as http from 'http';
import { AddressInfo } from 'net';
import { closeWindow } from './window-helpers';
import { emittedOnce } from './events-helpers';
import { ifit, ifdescribe, delay } from './spec-helpers';
@ -10,9 +13,7 @@ const features = process._linkedBinding('electron_common_features');
const v8Util = process._linkedBinding('electron_common_v8_util');
ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function () {
// TODO(zcbenz): Spellchecker loads really slow on ASan, we should provide
// a small testing dictionary to make the tests load faster.
this.timeout((process.env.IS_ASAN ? 700 : 20) * 1000);
this.timeout((process.env.IS_ASAN ? 200 : 20) * 1000);
let w: BrowserWindow;
@ -32,7 +33,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
// to detect spellchecker is to keep checking with a busy loop.
async function rightClickUntil (fn: (params: Electron.ContextMenuParams) => boolean) {
const now = Date.now();
const timeout = (process.env.IS_ASAN ? 600 : 10) * 1000;
const timeout = (process.env.IS_ASAN ? 180 : 10) * 1000;
let contextMenuParams = await rightClick();
while (!fn(contextMenuParams) && (Date.now() - now < timeout)) {
await delay(100);
@ -41,6 +42,26 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
return contextMenuParams;
}
// Setup a server to download hunspell dictionary.
const server = http.createServer((req, res) => {
// The provided is minimal dict for testing only, full list of words can
// be found at src/third_party/hunspell_dictionaries/xx_XX.dic.
fs.readFile(path.join(__dirname, '/../../third_party/hunspell_dictionaries/xx-XX-3-0.bdic'), function (err, data) {
if (err) {
console.error('Failed to read dictionary file');
res.writeHead(404);
res.end(JSON.stringify(err));
return;
}
res.writeHead(200);
res.end(data);
});
});
before((done) => {
server.listen(0, '127.0.0.1', () => done());
});
after(() => server.close());
beforeEach(async () => {
w = new BrowserWindow({
show: false,
@ -50,6 +71,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
contextIsolation: false
}
});
w.webContents.session.setSpellCheckerDictionaryDownloadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}/`);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.loadFile(path.resolve(__dirname, './fixtures/chromium/spellchecker.html'));
});
@ -62,7 +84,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
const shouldRun = process.platform !== 'win32';
ifit(shouldRun)('should detect correctly spelled words as correct', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautiful and lovely"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typography"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.selectionText.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('');
@ -70,10 +92,10 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
});
ifit(shouldRun)('should detect incorrectly spelled words as incorrect', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('Beautifulllll');
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
@ -81,24 +103,24 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
w.webContents.session.setSpellCheckerLanguages([]);
await delay(500);
w.webContents.session.setSpellCheckerLanguages(['en-US']);
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
const contextMenuParams = await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
expect(contextMenuParams.misspelledWord).to.eq('Beautifulllll');
expect(contextMenuParams.misspelledWord).to.eq('typograpy');
expect(contextMenuParams.dictionarySuggestions).to.have.length.of.at.least(1);
});
ifit(shouldRun)('should expose webFrame spellchecker correctly', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
const callWebFrameFn = (expr: string) => w.webContents.executeJavaScript('require("electron").webFrame.' + expr);
expect(await callWebFrameFn('isWordMisspelled("test")')).to.equal(false);
expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(true);
expect(await callWebFrameFn('getWordSuggestions("test")')).to.be.empty();
expect(await callWebFrameFn('getWordSuggestions("testt")')).to.not.be.empty();
expect(await callWebFrameFn('isWordMisspelled("typography")')).to.equal(false);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
expect(await callWebFrameFn('getWordSuggestions("typography")')).to.be.empty();
expect(await callWebFrameFn('getWordSuggestions("typograpy")')).to.not.be.empty();
});
describe('spellCheckerEnabled', () => {
@ -107,7 +129,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
});
ifit(shouldRun)('can be dynamically changed', async () => {
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "Beautifulllll asd asd"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
await rightClickUntil((contextMenuParams) => contextMenuParams.misspelledWord.length > 0);
@ -116,12 +138,17 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
w.webContents.session.spellCheckerEnabled = false;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.false();
expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(false);
// spellCheckerEnabled is sent to renderer asynchronously and there is
// no event notifying when it is finished, so wait a little while to
// ensure the setting has been changed in renderer.
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(false);
w.webContents.session.spellCheckerEnabled = true;
v8Util.runUntilIdle();
expect(w.webContents.session.spellCheckerEnabled).to.be.true();
expect(await callWebFrameFn('isWordMisspelled("testt")')).to.equal(true);
await delay(500);
expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
});
});