Browse Source

Merge branch 'master' into roller/chromium/master

Jeremy Rose 4 years ago
parent
commit
5d13820441

+ 1 - 1
ELECTRON_VERSION

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

+ 1 - 1
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)
 

+ 6 - 0
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.

+ 1 - 0
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

+ 1 - 1
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": {

+ 3 - 3
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<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

+ 28 - 15
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')
 

+ 1 - 1
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;

+ 2 - 1
shell/browser/api/electron_api_crash_reporter.cc

@@ -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() {

+ 5 - 5
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<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 {

+ 3 - 1
shell/browser/api/electron_api_download_item.h

@@ -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);

+ 1 - 1
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");
 }
 

+ 20 - 29
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<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 =

+ 2 - 0
shell/browser/api/electron_api_session.h

@@ -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_;
 

+ 2 - 1
shell/browser/api/electron_api_url_loader.cc

@@ -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() {

+ 3 - 0
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();
 }

+ 9 - 0
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

+ 2 - 1
shell/browser/feature_list.h

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

+ 7 - 5
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;
 

+ 8 - 0
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() {

+ 2 - 2
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

+ 3 - 2
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;

+ 2 - 0
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();

+ 42 - 11
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<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();
-
-        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.";
+        v8::Local<v8::Value> exception = try_catch.Exception();
+
+        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;
     }
 

+ 1 - 1
shell/renderer/api/electron_api_web_frame.cc

@@ -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];

+ 2 - 5
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<blink::WebPrescientNetworking>

+ 25 - 0
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', {

+ 44 - 17
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);
     });
   });