diff --git a/ELECTRON_VERSION b/ELECTRON_VERSION index 450026c06df2..2034e35f1cb4 100644 --- a/ELECTRON_VERSION +++ b/ELECTRON_VERSION @@ -1 +1 @@ -14.0.0-nightly.20210323 \ No newline at end of file +14.0.0-nightly.20210330 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index e94ca55e2456..9db83ed1516a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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) diff --git a/docs/api/command-line-switches.md b/docs/api/command-line-switches.md index fe8e7c75aebf..be9cac42b60d 100644 --- a/docs/api/command-line-switches.md +++ b/docs/api/command-line-switches.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. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 9cc07bca670f..7897a8c18e91 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -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 diff --git a/package.json b/package.json index b56a254ad0c4..13e18aaab070 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/patches/chromium/network_service_allow_remote_certificate_verification_logic.patch b/patches/chromium/network_service_allow_remote_certificate_verification_logic.patch index 172d3f0cc427..28f5d31e9ea2 100644 --- a/patches/chromium/network_service_allow_remote_certificate_verification_logic.patch +++ b/patches/chromium/network_service_allow_remote_certificate_verification_logic.patch @@ -122,10 +122,10 @@ index 8e53d65ddca7b54a6effd1767257a4d8239251d8..8a00bf59c728217069000b1f1ece72e1 #endif // BUILDFLAG(IS_CHROMEOS_ASH) + auto remote_cert_verifier = std::make_unique(std::move(cert_verifier)); + remote_cert_verifier_ = remote_cert_verifier.get(); -+ cert_verifier = std::make_unique(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 diff --git a/script/lib/git.py b/script/lib/git.py index cc82ec9c1076..0edcd98ea025 100644 --- a/script/lib/git.py +++ b/script/lib/git.py @@ -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') diff --git a/script/lint.js b/script/lint.js index 70f6a51693f7..a194aacb7b76 100755 --- a/script/lint.js +++ b/script/lint.js @@ -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; diff --git a/shell/browser/api/electron_api_crash_reporter.cc b/shell/browser/api/electron_api_crash_reporter.cc index bb7c8d677007..5e56f9603379 100644 --- a/shell/browser/api/electron_api_crash_reporter.cc +++ b/shell/browser/api/electron_api_crash_reporter.cc @@ -190,8 +190,9 @@ namespace { #if defined(MAS_BUILD) void GetUploadedReports( + v8::Isolate* isolate, base::OnceCallback)> callback) { - std::move(callback).Run(v8::Array::New(v8::Isolate::GetCurrent())); + std::move(callback).Run(v8::Array::New(isolate)); } #else scoped_refptr CreateCrashUploadList() { diff --git a/shell/browser/api/electron_api_download_item.cc b/shell/browser/api/electron_api_download_item.cc index 420fe79ac39a..e839c7ac8894 100644 --- a/shell/browser/api/electron_api_download_item.cc +++ b/shell/browser/api/electron_api_download_item.cc @@ -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 DownloadItem::GetURLChain(v8::Isolate* isolate) const { +v8::Local DownloadItem::GetURLChain() const { if (!CheckAlive()) return v8::Local(); - return gin::ConvertToV8(isolate, download_item_->GetUrlChain()); + return gin::ConvertToV8(isolate_, download_item_->GetUrlChain()); } download::DownloadItem::DownloadState DownloadItem::GetState() const { diff --git a/shell/browser/api/electron_api_download_item.h b/shell/browser/api/electron_api_download_item.h index e744e584fd65..d63c5ff498e0 100644 --- a/shell/browser/api/electron_api_download_item.h +++ b/shell/browser/api/electron_api_download_item.h @@ -66,7 +66,7 @@ class DownloadItem : public gin::Wrappable, std::string GetFilename() const; std::string GetContentDisposition() const; const GURL& GetURL() const; - v8::Local GetURLChain(v8::Isolate*) const; + v8::Local 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, file_dialog::DialogSettings dialog_options_; download::DownloadItem* download_item_; + v8::Isolate* isolate_; + base::WeakPtrFactory weak_factory_{this}; DISALLOW_COPY_AND_ASSIGN(DownloadItem); diff --git a/shell/browser/api/electron_api_menu.cc b/shell/browser/api/electron_api_menu.cc index c973fa714ef0..c992168e7789 100644 --- a/shell/browser/api/electron_api_menu.cc +++ b/shell/browser/api/electron_api_menu.cc @@ -264,7 +264,7 @@ void Menu::OnMenuWillClose() { } void Menu::OnMenuWillShow() { - Pin(v8::Isolate::GetCurrent()); + Pin(JavascriptEnvironment::GetIsolate()); Emit("menu-will-show"); } diff --git a/shell/browser/api/electron_api_session.cc b/shell/browser/api/electron_api_session.cc index 7b904da9d1d8..1b42e561c94a 100644 --- a/shell/browser/api/electron_api_session.cc +++ b/shell/browser/api/electron_api_session.cc @@ -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 Session::ResolveProxy(gin::Arguments* args) { } v8::Local Session::GetCacheSize() { - auto* isolate = JavascriptEnvironment::GetIsolate(); - gin_helper::Promise promise(isolate); + gin_helper::Promise promise(isolate_); auto handle = promise.GetHandle(); content::BrowserContext::GetDefaultStoragePartition(browser_context_) @@ -449,8 +448,7 @@ v8::Local Session::GetCacheSize() { } v8::Local Session::ClearCache() { - auto* isolate = JavascriptEnvironment::GetIsolate(); - gin_helper::Promise promise(isolate); + gin_helper::Promise promise(isolate_); auto handle = promise.GetHandle(); content::BrowserContext::GetDefaultStoragePartition(browser_context_) @@ -558,8 +556,7 @@ v8::Local Session::SetProxy(gin::Arguments* args) { } v8::Local Session::ForceReloadProxyConfig() { - v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); - gin_helper::Promise promise(isolate); + gin_helper::Promise promise(isolate_); auto handle = promise.GetHandle(); content::BrowserContext::GetDefaultStoragePartition(browser_context_) @@ -675,8 +672,7 @@ v8::Local Session::ClearHostResolverCache(gin::Arguments* args) { } v8::Local Session::ClearAuthCache() { - auto* isolate = JavascriptEnvironment::GetIsolate(); - gin_helper::Promise promise(isolate); + gin_helper::Promise promise(isolate_); v8::Local 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 Session::GetPreloads() const { v8::Local Session::LoadExtension( const base::FilePath& extension_path, gin::Arguments* args) { - v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); - gin_helper::Promise promise(isolate); + gin_helper::Promise promise(isolate_); v8::Local handle = promise.GetHandle(); if (!extension_path.IsAbsolute()) { @@ -833,7 +827,7 @@ v8::Local 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 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 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 Session::CloseAllConnections() { - v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); - gin_helper::Promise promise(isolate); + gin_helper::Promise promise(isolate_); auto handle = promise.GetHandle(); content::BrowserContext::GetDefaultStoragePartition(browser_context_) @@ -1018,8 +1010,7 @@ void SetSpellCheckerDictionaryDownloadURL(gin_helper::ErrorThrower thrower, } v8::Local Session::ListWordsInSpellCheckerDictionary() { - v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); - gin_helper::Promise> promise(isolate); + gin_helper::Promise> promise(isolate_); v8::Local handle = promise.GetHandle(); SpellcheckService* spellcheck = diff --git a/shell/browser/api/electron_api_session.h b/shell/browser/api/electron_api_session.h index e76d67c0cb93..317c4a2985c7 100644 --- a/shell/browser/api/electron_api_session.h +++ b/shell/browser/api/electron_api_session.h @@ -178,6 +178,8 @@ class Session : public gin::Wrappable, v8::Global service_worker_context_; v8::Global web_request_; + v8::Isolate* isolate_; + // The client id to enable the network throttler. base::UnguessableToken network_emulation_token_; diff --git a/shell/browser/api/electron_api_url_loader.cc b/shell/browser/api/electron_api_url_loader.cc index 25058ec12b1c..c08e65b038bf 100644 --- a/shell/browser/api/electron_api_url_loader.cc +++ b/shell/browser/api/electron_api_url_loader.cc @@ -312,7 +312,8 @@ void SimpleURLLoaderWrapper::Pin() { } void SimpleURLLoaderWrapper::PinBodyGetter(v8::Local body_getter) { - pinned_chunk_pipe_getter_.Reset(v8::Isolate::GetCurrent(), body_getter); + pinned_chunk_pipe_getter_.Reset(JavascriptEnvironment::GetIsolate(), + body_getter); } SimpleURLLoaderWrapper::~SimpleURLLoaderWrapper() { diff --git a/shell/browser/electron_browser_main_parts.cc b/shell/browser/electron_browser_main_parts.cc index dde44eb23bc0..607f4e0b8fe3 100644 --- a/shell/browser/electron_browser_main_parts.cc +++ b/shell/browser/electron_browser_main_parts.cc @@ -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(); } diff --git a/shell/browser/feature_list.cc b/shell/browser/feature_list.cc index 9e1fbb5213ad..59bdd50f6f97 100644 --- a/shell/browser/feature_list.cc +++ b/shell/browser/feature_list.cc @@ -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 diff --git a/shell/browser/feature_list.h b/shell/browser/feature_list.h index 9048c6913d4f..3464bba213c5 100644 --- a/shell/browser/feature_list.h +++ b/shell/browser/feature_list.h @@ -7,6 +7,7 @@ namespace electron { void InitializeFeatureList(); -} +void InitializeFieldTrials(); +} // namespace electron #endif // SHELL_BROWSER_FEATURE_LIST_H_ diff --git a/shell/browser/native_window_mac.mm b/shell/browser/native_window_mac.mm index 618119a6eb48..99999d2ff125 100644 --- a/shell/browser/native_window_mac.mm +++ b/shell/browser/native_window_mac.mm @@ -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; diff --git a/shell/browser/native_window_views.cc b/shell/browser/native_window_views.cc index 61fbbc2067d6..1cc596f392d8 100644 --- a/shell/browser/native_window_views.cc +++ b/shell/browser/native_window_views.cc @@ -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() { diff --git a/shell/browser/resources/win/electron.rc b/shell/browser/resources/win/electron.rc index c54bbbb03616..bdad2b68447f 100644 --- a/shell/browser/resources/win/electron.rc +++ b/shell/browser/resources/win/electron.rc @@ -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 diff --git a/shell/browser/ui/win/taskbar_host.cc b/shell/browser/ui/win/taskbar_host.cc index 32fd51511367..59682f9bf481 100644 --- a/shell/browser/ui/win/taskbar_host.cc +++ b/shell/browser/ui/win/taskbar_host.cc @@ -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; diff --git a/shell/browser/ui/win/taskbar_host.h b/shell/browser/ui/win/taskbar_host.h index 2aef64e9054d..4d09f232e0e3 100644 --- a/shell/browser/ui/win/taskbar_host.h +++ b/shell/browser/ui/win/taskbar_host.h @@ -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(); diff --git a/shell/renderer/api/electron_api_context_bridge.cc b/shell/renderer/api/electron_api_context_bridge.cc index 64b13604fb9f..9e7f3e551749 100644 --- a/shell/renderer/api/electron_api_context_bridge.cc +++ b/shell/renderer/api/electron_api_context_bridge.cc @@ -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 PassValueToOtherContext( // the global handle at the right time. if (value->IsFunction()) { auto func = v8::Local::Cast(value); + v8::MaybeLocal maybe_original_fn = GetPrivate( + source_context, func, context_bridge::kOriginalFunctionPrivateKey); { v8::Context::Scope destination_scope(destination_context); + v8::Local 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()->CreationContext() == + destination_context) { + return v8::MaybeLocal(proxy_func); + } + v8::Local state = v8::Object::New(destination_context->GetIsolate()); SetPrivate(destination_context, state, @@ -190,10 +209,12 @@ v8::MaybeLocal PassValueToOtherContext( context_bridge::kSupportsDynamicPropertiesPrivateKey, gin::ConvertToV8(destination_context->GetIsolate(), support_dynamic_properties)); - v8::Local proxy_func; + if (!v8::Function::New(destination_context, ProxyFunctionWrapper, state) .ToLocal(&proxy_func)) return v8::MaybeLocal(); + SetPrivate(destination_context, proxy_func.As(), + context_bridge::kOriginalFunctionPrivateKey, func); object_cache->CacheProxiedObject(value, proxy_func); return v8::MaybeLocal(proxy_func); } @@ -408,21 +429,31 @@ void ProxyFunctionWrapper(const v8::FunctionCallbackInfo& info) { v8::MaybeLocal maybe_return_value; bool did_error = false; - std::string error_message; + v8::Local 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 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 maybe_message = + exception.As()->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& 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())); return; } diff --git a/shell/renderer/api/electron_api_web_frame.cc b/shell/renderer/api/electron_api_web_frame.cc index a5ba2e000f6f..3f2b2ed09818 100644 --- a/shell/renderer/api/electron_api_web_frame.cc +++ b/shell/renderer/api/electron_api_web_frame.cc @@ -205,7 +205,7 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback { void Completed( const blink::WebVector>& result) override { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Isolate* isolate = promise_.isolate(); if (!result.empty()) { if (!result[0].IsEmpty()) { v8::Local value = result[0]; diff --git a/shell/renderer/renderer_client_base.cc b/shell/renderer/renderer_client_base.cc index 1a93fda7b11f..7e6cf1b31b08 100644 --- a/shell/renderer/renderer_client_base.cc +++ b/shell/renderer/renderer_client_base.cc @@ -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 diff --git a/spec-main/api-context-bridge-spec.ts b/spec-main/api-context-bridge-spec.ts index 176711af2b9f..d08a5e664964 100644 --- a/spec-main/api-context-bridge-spec.ts +++ b/spec-main/api-context-bridge-spec.ts @@ -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', { diff --git a/spec-main/spellchecker-spec.ts b/spec-main/spellchecker-spec.ts index d8f5c6ccb2a7..63d565ecfa8f 100644 --- a/spec-main/spellchecker-spec.ts +++ b/spec-main/spellchecker-spec.ts @@ -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); }); });