Merge branch 'master' into roller/chromium/master
This commit is contained in:
commit
5d13820441
28 changed files with 226 additions and 103 deletions
|
@ -1 +1 @@
|
|||
14.0.0-nightly.20210323
|
||||
14.0.0-nightly.20210330
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -264,7 +264,7 @@ void Menu::OnMenuWillClose() {
|
|||
}
|
||||
|
||||
void Menu::OnMenuWillShow() {
|
||||
Pin(v8::Isolate::GetCurrent());
|
||||
Pin(JavascriptEnvironment::GetIsolate());
|
||||
Emit("menu-will-show");
|
||||
}
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace electron {
|
||||
void InitializeFeatureList();
|
||||
}
|
||||
void InitializeFieldTrials();
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_FEATURE_LIST_H_
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue