Compare commits

...

46 Commits

Author SHA1 Message Date
Electron Bot
4c25b619a6 Bump v6.1.3 2019-11-01 08:58:29 -07:00
trop[bot]
c2c25f1ee3 build: do not try to run non existent VSTS release builds (#20876) 2019-10-31 10:55:28 -04:00
John Kleinschmidt
8cf8c8fe63 docs: Update the sccache name (#20462) (#20862)
(cherry picked from commit b3e7657159)
2019-10-31 09:55:39 +09:00
Birunthan Mohanathas
61fdda3e8f fix: Disable compositor recycling only for attached views (6-1-x) (#20834)
Backport of #20829

Notes: Fix flicker when switching between `BrowserView`s
2019-10-30 16:55:30 -04:00
Milan Burda
4a5f89d5c7 fix: properly generate requestID in webContents.printToPDF() (#20769) (#20811) 2019-10-30 14:38:09 +09:00
Milan Burda
36fd0e9b37 fix: pass frameId to v8Util.setRemoteCallbackFreer() (#20732) (#20815) 2019-10-30 14:36:06 +09:00
Milan Burda
512736542b fix: send ELECTRON_BROWSER_CONTEXT_RELEASE asynchronously (#20632) (#20716) 2019-10-29 11:34:11 +09:00
Shelley Vohr
b144900c00 fix: prevent menu gc during popup (#20786) 2019-10-28 18:35:58 -07:00
Electron Bot
dabaa7557a Bump v6.1.2 2019-10-24 12:06:16 -07:00
trop[bot]
c6a794d738 chore: update build_bring_back_node_with_ltcg_configuration.patch (#20709)
* chore: update build_bring_back_node_with_ltcg_configuration.patch

set default value for node_with_ltcg=true

* fix: move ltcg definition to Release configuration
2019-10-23 16:33:38 -07:00
Electron Bot
aa863bc323 Bump v6.1.1 2019-10-23 11:13:59 -07:00
Jeremy Apthorp
e3a79d6c52 fix: properly free remote objects (6-1-x) (#20694) 2019-10-23 10:31:59 -07:00
loc
14ba3ac63e chore: remove unused member variable (#20690) 2019-10-23 09:12:18 +09:00
loc
6a07825c47 fix: don't hang on SendSync if ElectronBrowser receiver is destroyed (6-0-x) (#20547) 2019-10-22 14:29:10 -07:00
Electron Bot
70b5e673bb Bump v6.1.0 2019-10-21 15:45:49 -07:00
trop[bot]
6359db712a fix: backport libuv patch for fs.mkdir/mkdirSync on invlaid names (#20665)
Backports https://github.com/libuv/libuv/pull/2375
2019-10-21 15:44:05 -07:00
Samuel Attard
268cd3939c feat: add a new contextBridge module (#20639)
* feat: add a new contextBridge module (#20307)

* feat: add a new contextBridge module

* chore: fix docs linting

* feat: add support for function arguments being proxied

* chore: ensure that contextBridge can only be used when contextIsolation is enabled

* docs: getReverseBinding can be null

* docs: fix broken links in md file

* feat: add support for promises in function parameters

* fix: linting failure for explicit constructor

* Update atom_api_context_bridge.cc

* chore: update docs and API design as per feedback

* refactor: remove reverse bindings and handle GC'able functions across the bridge

* chore: only expose debugGC in testing builds

* fix: do not proxy promises as objects

* spec: add complete spec coverage for contextBridge

* spec: add tests for null/undefined and the anti-overwrite logic

* chore: fix linting

* spec: add complex nested back-and-forth function calling

* fix: expose contextBridge in sandboxed renderers

* refactor: improve security of default_app using the new contextBridge module

* s/bindAPIInMainWorld/exposeInMainWorld

* chore: sorry for this commit, its a big one, I fixed like everything and refactored a lot

* chore: remove PassedValueCache as it is unused now

Values transferred from context A to context B are now cachde in the RenderFramePersistenceStore

* chore: move to anonymous namespace

* refactor: remove PassValueToOtherContextWithCache

* chore: remove commented unused code blocks

* chore: remove .only

* chore: remote commented code

* refactor: extract RenderFramePersistenceStore

* spec: ensure it works with numbered keys

* fix: handle number keys correctly

* fix: sort out the linter

* spec: update default_app asar spec for removed file

* refactor: change signatures to return v8 objects directly rather than the mate dictionary handle

* refactor: use the v8 serializer to support cloneable buffers and other object types

* chore: fix linting

* fix: handle hash collisions with a linked list in the map

* fix: enforce a recursion limit on the context bridge

* chore: fix linting

* chore: remove TODO

* chore: adapt for PR feedback

* chore: remove .only

* chore: clean up docs and clean up the proxy map when objects are released

* chore: ensure we cache object values that are cloned through the V8 serializer

* docs: mark contextBridge as experimental (#20638)

* docs: mark contextBridge as experimental

This commit didn't make it to the original PR, quick addition here

* Update context-bridge.md

* chore: touch up the differences between master and 6-0-x

* chore: add v8 serializer converter, cherry picked from 2fad53e66b

* chore: support converting OnceCallback to V8 (#17941)

* chore: fixup tests

* chore: fix linting

* chore: add patch for mojo message constructor
2019-10-21 13:58:03 -07:00
Samuel Attard
3ca62d9432 chore: manually bump version to 6.1.0-beta.0 in prep for 6.1.0 2019-10-21 13:01:18 -07:00
Milan Burda
9901700a91 test: skip desktopCapturer / remote module tests when the features are disabled (#20566) (#20577) 2019-10-21 15:40:21 -04:00
Robo
d5b088bc26 fix: add patch to node for native module size issue on windows (#20614) (#20627) 2019-10-18 12:57:08 -04:00
trop[bot]
a12de693b0 ci: add macOS debug builds (#20572)
* ci: add macOS debug builds

* Fix mac debug builds

* Remove ninja status as it is not available in 6-0-x
2019-10-15 09:14:25 -07:00
trop[bot]
e257981a6d spec: allow "Yu Gothic" as a Japanese sans-serif font on Windows (#20569) 2019-10-14 12:38:01 -07:00
Milan Burda
ffb96acab0 fix: when building with enable_plugins=false (#20354) (#20508) 2019-10-10 14:12:28 +02:00
Robo
fd0b57f219 fix: backport chromium patches to fix touchpad scrolling on windows (#20488)
Backports https://chromium-review.googlesource.com/c/chromium/src/+/1689922
and https://chromium-review.googlesource.com/c/chromium/src/+/1753661
2019-10-09 13:41:06 -07:00
Electron Bot
1e50380fab Bump v6.0.12 2019-10-08 12:43:31 -07:00
trop[bot]
31ba6c203e fix: properly free IsolateData in node_main (#20475) 2019-10-08 15:33:32 -04:00
Shelley Vohr
03d16f37d2 fix: enable worker threads in ELECTRON_RUN_AS_NODE (#20457) 2019-10-08 11:59:28 -04:00
Robo
e501930d38 fix: fs.watch() behavior change in node >= 10.16.0 (#20429)
This reverts the patch from https://github.com/electron/node/pull/100
which never got merged due to reasons outlined in https://github.com/libuv/libuv/pull/2313

* Adds new patches that backports https://github.com/libuv/libuv/pull/2459
  and https://github.com/libuv/libuv/pull/2460

Based on https://github.com/nodejs/node/issues/29460
2019-10-07 13:05:14 -07:00
Joshua Westerheide
28eb7b0532 feat: add env variable to skip binary download on npm install (backport) (#20438)
* feat: add env variable to skip binary download on npm install

* docs: add "Skip binary download" section to install tutorial
2019-10-07 13:04:00 -04:00
Shelley Vohr
15e611a0ff docs: clarify dock.bounce usage (#20458) 2019-10-07 17:15:48 +02:00
trop[bot]
3176e323e4 fix: recentDocuments menu role on macOS (#20409) 2019-10-03 08:59:09 +02:00
Milan Burda
99a0581d0d fix: allow paths to asar archives to contain the .asar extension in directories (#20342) (#20402) 2019-10-02 18:03:18 +09:00
Electron Bot
4e417e21b8 Bump v6.0.11 2019-10-01 12:08:35 -07:00
trop[bot]
e472efbea2 fix: correctly crash when there is no crashReporter (#20396)
* fix: correctly crash when there is no crashReporter

* test: correctly crash when there is crashReporter
2019-10-01 15:06:23 -04:00
Birunthan Mohanathas
886e636b13 fix: Make the --disable-color-correct-rendering switch work again (backport) (#20358) 2019-09-30 10:46:41 -07:00
trop[bot]
0450ee9524 fix: correct 'Entire screen' to ' Entire Screen' (#20301) 2019-09-20 07:44:21 -07:00
trop[bot]
ce31dc3591 ci: actually kill leftover processes on WOA testing (#20292)
* ci: only kill WOA processes if they are running

(cherry picked from commit 844752cd97)

* ci: actually kill leftover processes on WOA testing

(cherry picked from commit a76d2d8e53)
2019-09-20 10:20:19 -04:00
Shelley Vohr
15c89c8262 fix: crash when exiting simple fullscreen on macOS (#20144) (#20282) 2019-09-19 14:29:35 -04:00
Shelley Vohr
dbd72b2265 docs: improve and add examples for clipboard (#20224) (#20283)
* docs: improve and add examples for clipboard

* address feedback from jkleinsc review
2019-09-19 09:57:09 -04:00
Electron Bot
10a9e9c043 Bump v6.0.10 2019-09-18 12:48:43 -07:00
trop[bot]
690271e38c build: add WOA node headers to checksum file (#20260) 2019-09-18 10:21:46 -07:00
trop[bot]
fc7ef4cc1c fix: strip chrome-sandbox typo (#20257) 2019-09-18 09:43:52 -07:00
Samuel Attard
f2d1abd0e3 fix: Add more checks in MojoCdmService. (#20219)
Applies b7b305f338%5E%21/
2019-09-17 13:53:04 -04:00
Samuel Attard
e3be323962 docs: remove dash that broke the return type parser (#20246) 2019-09-17 10:49:07 -04:00
Electron Bot
407747b48c Bump v6.0.9 2019-09-11 10:30:11 -07:00
trop[bot]
17b8b551ac build: handle arm64 node headers (#20194)
* build: handle arm64 node headers

(cherry picked from commit ff1f224d96)

* node.lib for arm64 needs to go to specific dir
2019-09-10 21:56:07 -04:00
109 changed files with 4676 additions and 649 deletions

View File

@@ -171,7 +171,7 @@ step-setup-env-for-build: &step-setup-env-for-build
echo 'export SCCACHE_PATH="'"$SCCACHE_PATH"'"' >> $BASH_ENV
if [ "$CIRCLE_PR_NUMBER" != "" ]; then
#if building a fork set readonly access to sccache
echo 'export SCCACHE_BUCKET="electronjs-sccache"' >> $BASH_ENV
echo 'export SCCACHE_BUCKET="electronjs-sccache-ci"' >> $BASH_ENV
echo 'export SCCACHE_TWO_TIER=true' >> $BASH_ENV
fi
fi
@@ -640,9 +640,11 @@ steps-electron-build-for-tests: &steps-electron-build-for-tests
- *step-depot-tools-add-to-path
- *step-setup-env-for-build
- *step-restore-brew-cache
- *step-get-more-space-on-mac
- *step-install-npm-deps-on-mac
- *step-fix-sync-on-mac
- *step-gn-gen-default
- *step-delete-git-directories
# Electron app
- *step-electron-build
@@ -1099,6 +1101,14 @@ jobs:
<<: *env-enable-sccache
<<: *steps-electron-build-for-tests
osx-debug:
<<: *machine-mac-large
environment:
<<: *env-mac-large
<<: *env-debug-build
<<: *env-enable-sccache
<<: *steps-electron-build-for-tests
osx-debug-gn-check:
<<: *machine-mac
environment:
@@ -1147,6 +1157,15 @@ jobs:
<<: *env-enable-sccache
<<: *steps-electron-build-for-tests
mas-debug:
<<: *machine-mac-large
environment:
<<: *env-mac-large
<<: *env-mas
<<: *env-debug-build
<<: *env-enable-sccache
<<: *steps-electron-build-for-tests
mas-debug-gn-check:
<<: *machine-mac
environment:
@@ -1470,9 +1489,14 @@ workflows:
requires:
- mac-checkout
- osx-debug:
requires:
- mac-checkout
- osx-debug-gn-check:
requires:
- mac-checkout
- osx-testing-gn-check:
requires:
- mac-checkout
@@ -1485,9 +1509,14 @@ workflows:
requires:
- mac-checkout
- mas-debug:
requires:
- mac-checkout
- mas-debug-gn-check:
requires:
- mac-checkout
- mas-testing-gn-check:
requires:
- mac-checkout

View File

@@ -657,9 +657,7 @@ static_library("electron_lib") {
}
if (enable_desktop_capturer) {
if (is_component_build && is_win) {
# On windows the implementation relies on unexported
# DxgiDuplicatorController class.
if (is_component_build && !is_linux) {
deps += [ "//third_party/webrtc/modules/desktop_capture" ]
}
sources += [

View File

@@ -1 +1 @@
6.0.8
6.1.3

View File

@@ -15,9 +15,8 @@
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/common/content_constants.h"
#include "content/public/common/pepper_plugin_info.h"
#include "electron/buildflags/buildflags.h"
#include "ppapi/shared_impl/ppapi_permissions.h"
#include "ppapi/buildflags/buildflags.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "url/url_constants.h"
@@ -35,6 +34,11 @@
#include "pdf/pdf.h"
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
#if BUILDFLAG(ENABLE_PLUGINS)
#include "content/public/common/pepper_plugin_info.h"
#include "ppapi/shared_impl/ppapi_permissions.h"
#endif // BUILDFLAG(ENABLE_PLUGINS)
namespace atom {
namespace {
@@ -141,6 +145,7 @@ void AddPepperFlashFromCommandLine(
}
#endif // BUILDFLAG(ENABLE_PEPPER_FLASH)
#if BUILDFLAG(ENABLE_PLUGINS)
void ComputeBuiltInPlugins(std::vector<content::PepperPluginInfo>* plugins) {
#if BUILDFLAG(ENABLE_PDF_VIEWER)
content::PepperPluginInfo pdf_info;
@@ -161,6 +166,7 @@ void ComputeBuiltInPlugins(std::vector<content::PepperPluginInfo>* plugins) {
plugins->push_back(pdf_info);
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
}
#endif // BUILDFLAG(ENABLE_PLUGINS)
void AppendDelimitedSwitchToVector(const base::StringPiece cmd_switch,
std::vector<std::string>* append_me) {
@@ -227,7 +233,9 @@ void AtomContentClient::AddPepperPlugins(
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
AddPepperFlashFromCommandLine(command_line, plugins);
#endif // BUILDFLAG(ENABLE_PEPPER_FLASH)
#if BUILDFLAG(ENABLE_PLUGINS)
ComputeBuiltInPlugins(plugins);
#endif // BUILDFLAG(ENABLE_PLUGINS)
}
void AtomContentClient::AddContentDecryptionModules(

View File

@@ -68,9 +68,14 @@ int NodeMain(int argc, char* argv[]) {
// Initialize gin::IsolateHolder.
JavascriptEnvironment gin_env(loop);
node::Environment* env = node::CreateEnvironment(
node::CreateIsolateData(gin_env.isolate(), loop, gin_env.platform()),
gin_env.context(), argc, argv, exec_argc, exec_argv, false);
node::IsolateData* isolate_data =
node::CreateIsolateData(gin_env.isolate(), loop, gin_env.platform());
CHECK_NE(nullptr, isolate_data);
node::Environment* env =
node::CreateEnvironment(isolate_data, gin_env.context(), argc, argv,
exec_argc, exec_argv, false);
CHECK_NE(nullptr, env);
// Enable support for v8 inspector.
NodeDebugger node_debugger(env);
@@ -118,6 +123,7 @@ int NodeMain(int argc, char* argv[]) {
v8::Isolate* isolate = env->isolate();
node::FreeEnvironment(env);
node::FreeIsolateData(isolate_data);
gin_env.platform()->DrainTasks(isolate);
gin_env.platform()->CancelPendingDelayedTasks(isolate);

View File

@@ -11,6 +11,7 @@
#include "atom/browser/api/atom_api_top_level_window.h"
#include "atom/browser/api/trackable_object.h"
#include "atom/browser/ui/atom_menu_model.h"
#include "atom/common/api/locker.h"
#include "base/callback.h"
namespace atom {

View File

@@ -53,15 +53,18 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
int y,
int positioning_item,
base::Closure callback) {
mate::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
if (!native_window)
return;
NSWindow* nswindow = native_window->GetNativeWindow().GetNativeNSWindow();
auto close_callback = base::Bind(
&MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback);
popup_controllers_[window_id] = base::scoped_nsobject<AtomMenuController>([
[AtomMenuController alloc] initWithModel:model()
useDefaultAccelerator:NO]);
popup_controllers_[window_id] = base::scoped_nsobject<AtomMenuController>(
[[AtomMenuController alloc] initWithModel:model()
useDefaultAccelerator:NO]);
NSMenu* menu = [popup_controllers_[window_id] menu];
NSView* view = [nswindow contentView];
@@ -135,9 +138,9 @@ void MenuMac::OnClosed(int32_t window_id, base::Closure callback) {
// static
void Menu::SetApplicationMenu(Menu* base_menu) {
MenuMac* menu = static_cast<MenuMac*>(base_menu);
base::scoped_nsobject<AtomMenuController> menu_controller([
[AtomMenuController alloc] initWithModel:menu->model_.get()
useDefaultAccelerator:YES]);
base::scoped_nsobject<AtomMenuController> menu_controller(
[[AtomMenuController alloc] initWithModel:menu->model_.get()
useDefaultAccelerator:YES]);
NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
[currentRunLoop cancelPerformSelector:@selector(setMainMenu:)

View File

@@ -26,6 +26,9 @@ void MenuViews::PopupAt(TopLevelWindow* window,
int y,
int positioning_item,
const base::Closure& callback) {
mate::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
auto* native_window = static_cast<NativeWindowViews*>(window->window());
if (!native_window)
return;

View File

@@ -35,7 +35,6 @@
#include "atom/common/color_util.h"
#include "atom/common/mouse_util.h"
#include "atom/common/native_mate_converters/blink_converter.h"
#include "atom/common/native_mate_converters/callback.h"
#include "atom/common/native_mate_converters/content_converter.h"
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "atom/common/native_mate_converters/gfx_converter.h"
@@ -43,6 +42,7 @@
#include "atom/common/native_mate_converters/image_converter.h"
#include "atom/common/native_mate_converters/net_converter.h"
#include "atom/common/native_mate_converters/network_converter.h"
#include "atom/common/native_mate_converters/once_callback.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "atom/common/node_includes.h"
@@ -85,6 +85,7 @@
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
#include "net/url_request/url_request_context.h"
#include "ppapi/buildflags/buildflags.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
#include "third_party/blink/public/platform/web_cursor_info.h"
@@ -833,10 +834,12 @@ void WebContents::RenderProcessGone(base::TerminationStatus status) {
void WebContents::PluginCrashed(const base::FilePath& plugin_path,
base::ProcessId plugin_pid) {
#if BUILDFLAG(ENABLE_PLUGINS)
content::WebPluginInfo info;
auto* plugin_service = content::PluginService::GetInstance();
plugin_service->GetPluginInfoByPath(plugin_path, &info);
Emit("plugin-crashed", info.name, info.version);
#endif // BUILDFLAG(ENABLE_PLUIGNS)
}
void WebContents::MediaStartedPlaying(const MediaPlayerInfo& video_type,

View File

@@ -157,7 +157,7 @@ class NativeWindowMac : public NativeWindow {
AtomTouchBar* touch_bar() const { return touch_bar_.get(); }
bool zoom_to_page_width() const { return zoom_to_page_width_; }
bool fullscreen_window_title() const { return fullscreen_window_title_; }
bool simple_fullscreen() const { return always_simple_fullscreen_; }
bool always_simple_fullscreen() const { return always_simple_fullscreen_; }
protected:
// views::WidgetDelegate:

View File

@@ -17,9 +17,9 @@
<key>CFBundleIconFile</key>
<string>electron.icns</string>
<key>CFBundleVersion</key>
<string>6.0.8</string>
<string>6.1.3</string>
<key>CFBundleShortVersionString</key>
<string>6.0.8</string>
<string>6.1.3</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 6,0,8,0
PRODUCTVERSION 6,0,8,0
FILEVERSION 6,1,3,0
PRODUCTVERSION 6,1,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "GitHub, Inc."
VALUE "FileDescription", "Electron"
VALUE "FileVersion", "6.0.8"
VALUE "FileVersion", "6.1.3"
VALUE "InternalName", "electron.exe"
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
VALUE "OriginalFilename", "electron.exe"
VALUE "ProductName", "Electron"
VALUE "ProductVersion", "6.0.8"
VALUE "ProductVersion", "6.1.3"
VALUE "SquirrelAwareVersion", "1"
END
END

View File

@@ -126,11 +126,13 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
if (!menu_)
return;
// Locate & retain the recent documents menu item
if (!recentDocumentsMenuItem_) {
// Locate & retain the recent documents menu item
recentDocumentsMenuItem_.reset(
[[[[[NSApp mainMenu] itemWithTitle:@"Electron"] submenu]
itemWithTitle:@"Open Recent"] retain]);
base::string16 title = base::ASCIIToUTF16("Open Recent");
NSString* openTitle = l10n_util::FixUpWindowsStyleLabel(title);
recentDocumentsMenuItem_.reset([[[[[NSApp mainMenu]
itemWithTitle:@"Electron"] submenu] itemWithTitle:openTitle] retain]);
}
model_ = model;
@@ -193,8 +195,17 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
// Replaces the item's submenu instance with the singleton recent documents
// menu. Previously replaced menu items will be recovered.
- (void)replaceSubmenuShowingRecentDocuments:(NSMenuItem*)item {
NSMenu* recentDocumentsMenu =
[[[recentDocumentsMenuItem_ submenu] retain] autorelease];
NSMenu* recentDocumentsMenu = [recentDocumentsMenuItem_ submenu];
if (!recentDocumentsMenu) {
base::string16 title = base::ASCIIToUTF16("Clear Menu");
NSString* clearTitle = l10n_util::FixUpWindowsStyleLabel(title);
recentDocumentsMenu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
[recentDocumentsMenu
addItem:[[[NSMenuItem alloc]
initWithTitle:clearTitle
action:@selector(clearRecentDocuments:)
keyEquivalent:@""] autorelease]];
}
// Remove menu items in recent documents back to swap menu
[self moveMenuItems:recentDocumentsMenu to:recentDocumentsMenuSwap_];
@@ -211,6 +222,9 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
// Replace submenu
[item setSubmenu:recentDocumentsMenu];
DCHECK_EQ([item action], @selector(submenuAction:));
DCHECK_EQ([item target], recentDocumentsMenu);
// Remember the new menu item that carries the recent documents menu
recentDocumentsMenuItem_.reset([item retain]);
}

View File

@@ -174,8 +174,14 @@ bool ScopedDisableResize::disable_resize_ = false;
}
- (void)toggleFullScreenMode:(id)sender {
if (shell_->simple_fullscreen())
shell_->SetSimpleFullScreen(!shell_->IsSimpleFullScreen());
bool is_simple_fs = shell_->IsSimpleFullScreen();
bool always_simple_fs = shell_->always_simple_fullscreen();
// If we're in simple fullscreen mode and trying to exit it
// we need to ensure we exit it properly to prevent a crash
// with NSWindowStyleMaskTitled mode
if (is_simple_fs || always_simple_fs)
shell_->SetSimpleFullScreen(!is_simple_fs);
else
[super toggleFullScreen:sender];
}

View File

@@ -7,6 +7,7 @@
#include <vector>
#include "atom/common/asar/archive.h"
#include "atom/common/asar/asar_util.h"
#include "atom/common/native_mate_converters/callback.h"
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "atom/common/node_includes.h"
@@ -127,12 +128,27 @@ void InitAsarSupport(v8::Isolate* isolate, v8::Local<v8::Value> require) {
&asar_init_params, &asar_init_args, nullptr);
}
v8::Local<v8::Value> SplitPath(v8::Isolate* isolate,
const base::FilePath& path) {
mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate);
base::FilePath asar_path, file_path;
if (asar::GetAsarArchivePath(path, &asar_path, &file_path, true)) {
dict.Set("isAsar", true);
dict.Set("asarPath", asar_path);
dict.Set("filePath", file_path);
} else {
dict.Set("isAsar", false);
}
return dict.GetHandle();
}
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
mate::Dictionary dict(context->GetIsolate(), exports);
dict.SetMethod("createArchive", &Archive::Create);
dict.SetMethod("splitPath", &SplitPath);
dict.SetMethod("initAsarSupport", &InitAsarSupport);
}

View File

@@ -16,19 +16,23 @@ namespace atom {
// static
void RemoteCallbackFreer::BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
int frame_id,
const std::string& context_id,
int object_id,
content::WebContents* web_contents) {
new RemoteCallbackFreer(isolate, target, context_id, object_id, web_contents);
new RemoteCallbackFreer(isolate, target, frame_id, context_id, object_id,
web_contents);
}
RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
v8::Local<v8::Object> target,
int frame_id,
const std::string& context_id,
int object_id,
content::WebContents* web_contents)
: ObjectLifeMonitor(isolate, target),
content::WebContentsObserver(web_contents),
frame_id_(frame_id),
context_id_(context_id),
object_id_(object_id) {}
@@ -40,10 +44,15 @@ void RemoteCallbackFreer::RunDestructor() {
int32_t sender_id = 0;
args.AppendString(context_id_);
args.AppendInteger(object_id_);
auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) {
auto frames = web_contents()->GetAllFrames();
auto iter = std::find_if(frames.begin(), frames.end(), [this](auto* f) {
return f->GetRoutingID() == frame_id_;
});
if (iter != frames.end() && (*iter)->IsRenderFrameLive()) {
mojom::ElectronRendererAssociatedPtr electron_ptr;
frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
(*iter)->GetRemoteAssociatedInterfaces()->GetInterface(
mojo::MakeRequest(&electron_ptr));
electron_ptr->Message(true /* internal */, false /* send_to_all */, channel,
args.Clone(), sender_id);

View File

@@ -17,6 +17,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
public:
static void BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
int frame_id,
const std::string& context_id,
int object_id,
content::WebContents* web_conents);
@@ -24,6 +25,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
protected:
RemoteCallbackFreer(v8::Isolate* isolate,
v8::Local<v8::Object> target,
int frame_id,
const std::string& context_id,
int object_id,
content::WebContents* web_conents);
@@ -35,6 +37,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
void RenderViewDeleted(content::RenderViewHost*) override;
private:
int frame_id_;
std::string context_id_;
int object_id_;

View File

@@ -9,7 +9,7 @@
#include "base/values.h"
#include "content/public/renderer/render_frame.h"
#include "electron/atom/common/api/api.mojom.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/web/web_local_frame.h"
using blink::WebLocalFrame;
@@ -77,8 +77,8 @@ void RemoteObjectFreer::RunDestructor() {
if (ref_mapper_[context_id_].empty())
ref_mapper_.erase(context_id_);
mojom::ElectronBrowserAssociatedPtr electron_ptr;
render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
mojom::ElectronBrowserPtr electron_ptr;
render_frame->GetRemoteInterfaces()->GetInterface(
mojo::MakeRequest(&electron_ptr));
electron_ptr->Message(true, channel, args.Clone());
}

View File

@@ -13,6 +13,7 @@
#include "base/lazy_instance.h"
#include "base/stl_util.h"
#include "base/threading/thread_local.h"
#include "base/threading/thread_restrictions.h"
namespace asar {
@@ -25,6 +26,17 @@ base::LazyInstance<base::ThreadLocalPointer<ArchiveMap>>::Leaky
const base::FilePath::CharType kAsarExtension[] = FILE_PATH_LITERAL(".asar");
std::map<base::FilePath, bool> g_is_directory_cache;
bool IsDirectoryCached(const base::FilePath& path) {
auto it = g_is_directory_cache.find(path);
if (it != g_is_directory_cache.end()) {
return it->second;
}
base::ThreadRestrictions::ScopedAllowIO allow_io;
return g_is_directory_cache[path] = base::DirectoryExists(path);
}
} // namespace
std::shared_ptr<Archive> GetOrCreateAsarArchive(const base::FilePath& path) {
@@ -47,11 +59,12 @@ void ClearArchives() {
bool GetAsarArchivePath(const base::FilePath& full_path,
base::FilePath* asar_path,
base::FilePath* relative_path) {
base::FilePath* relative_path,
bool allow_root) {
base::FilePath iter = full_path;
while (true) {
base::FilePath dirname = iter.DirName();
if (iter.MatchesExtension(kAsarExtension))
if (iter.MatchesExtension(kAsarExtension) && !IsDirectoryCached(iter))
break;
else if (iter == dirname)
return false;
@@ -59,7 +72,8 @@ bool GetAsarArchivePath(const base::FilePath& full_path,
}
base::FilePath tail;
if (!iter.AppendRelativePath(full_path, &tail))
if (!((allow_root && iter == full_path) ||
iter.AppendRelativePath(full_path, &tail)))
return false;
*asar_path = iter;

View File

@@ -25,7 +25,8 @@ void ClearArchives();
// Separates the path to Archive out.
bool GetAsarArchivePath(const base::FilePath& full_path,
base::FilePath* asar_path,
base::FilePath* relative_path);
base::FilePath* relative_path,
bool allow_root = false);
// Same with base::ReadFileToString but supports asar Archive.
bool ReadFileToString(const base::FilePath& path, std::string* contents);

View File

@@ -6,8 +6,8 @@
#define ATOM_COMMON_ATOM_VERSION_H_
#define ATOM_MAJOR_VERSION 6
#define ATOM_MINOR_VERSION 0
#define ATOM_PATCH_VERSION 8
#define ATOM_MINOR_VERSION 1
#define ATOM_PATCH_VERSION 3
// clang-format off
// #define ATOM_PRE_RELEASE_VERSION
// clang-format on

View File

@@ -28,9 +28,17 @@ namespace {
#if defined(_WIN64)
int CrashForException(EXCEPTION_POINTERS* info) {
auto* reporter = crash_reporter::CrashReporterWin::GetInstance();
if (reporter->IsInitialized())
if (reporter->IsInitialized()) {
reporter->GetCrashpadClient().DumpAndCrash(info);
return EXCEPTION_CONTINUE_SEARCH;
return EXCEPTION_CONTINUE_SEARCH;
}
// When there is exception and we do not have crashReporter set up, we just
// let the execution continue and crash, which is the default behavior.
//
// We must not return EXCEPTION_CONTINUE_SEARCH here, as it would end up with
// busy loop when there is no exception handler in the program.
return EXCEPTION_CONTINUE_EXECUTION;
}
#endif

View File

@@ -6,13 +6,17 @@
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "atom/common/keyboard_util.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "gin/converter.h"
#include "mojo/public/cpp/base/values_mojom_traits.h"
#include "mojo/public/mojom/base/values.mojom.h"
#include "native_mate/dictionary.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/platform/web_mouse_event.h"
@@ -527,4 +531,175 @@ bool Converter<network::mojom::ReferrerPolicy>::FromV8(
return true;
}
namespace {
constexpr uint8_t kNewSerializationTag = 0;
constexpr uint8_t kOldSerializationTag = 1;
class V8Serializer : public v8::ValueSerializer::Delegate {
public:
explicit V8Serializer(v8::Isolate* isolate,
bool use_old_serialization = false)
: isolate_(isolate),
serializer_(isolate, this),
use_old_serialization_(use_old_serialization) {}
~V8Serializer() override = default;
bool Serialize(v8::Local<v8::Value> value, blink::CloneableMessage* out) {
serializer_.WriteHeader();
if (use_old_serialization_) {
WriteTag(kOldSerializationTag);
if (!WriteBaseValue(value)) {
isolate_->ThrowException(
mate::StringToV8(isolate_, "An object could not be cloned."));
return false;
}
} else {
WriteTag(kNewSerializationTag);
bool wrote_value;
v8::TryCatch try_catch(isolate_);
if (!serializer_.WriteValue(isolate_->GetCurrentContext(), value)
.To(&wrote_value)) {
try_catch.Reset();
if (!V8Serializer(isolate_, true).Serialize(value, out)) {
try_catch.ReThrow();
return false;
}
return true;
}
DCHECK(wrote_value);
}
std::pair<uint8_t*, size_t> buffer = serializer_.Release();
DCHECK_EQ(buffer.first, data_.data());
out->encoded_message = base::make_span(buffer.first, buffer.second);
out->owned_encoded_message = std::move(data_);
return true;
}
bool WriteBaseValue(v8::Local<v8::Value> object) {
base::Value value;
if (!ConvertFromV8(isolate_, object, &value)) {
return false;
}
mojo::Message message = mojo_base::mojom::Value::SerializeAsMessage(&value);
serializer_.WriteUint32(message.data_num_bytes());
serializer_.WriteRawBytes(message.data(), message.data_num_bytes());
return true;
}
void WriteTag(uint8_t tag) { serializer_.WriteRawBytes(&tag, 1); }
// v8::ValueSerializer::Delegate
void* ReallocateBufferMemory(void* old_buffer,
size_t size,
size_t* actual_size) override {
DCHECK_EQ(old_buffer, data_.data());
data_.resize(size);
*actual_size = data_.capacity();
return data_.data();
}
void FreeBufferMemory(void* buffer) override {
DCHECK_EQ(buffer, data_.data());
data_ = {};
}
void ThrowDataCloneError(v8::Local<v8::String> message) override {
isolate_->ThrowException(v8::Exception::Error(message));
}
private:
v8::Isolate* isolate_;
std::vector<uint8_t> data_;
v8::ValueSerializer serializer_;
bool use_old_serialization_;
};
class V8Deserializer : public v8::ValueDeserializer::Delegate {
public:
V8Deserializer(v8::Isolate* isolate, const blink::CloneableMessage& message)
: isolate_(isolate),
deserializer_(isolate,
message.encoded_message.data(),
message.encoded_message.size(),
this) {}
v8::Local<v8::Value> Deserialize() {
v8::EscapableHandleScope scope(isolate_);
auto context = isolate_->GetCurrentContext();
bool read_header;
if (!deserializer_.ReadHeader(context).To(&read_header))
return v8::Null(isolate_);
DCHECK(read_header);
uint8_t tag;
if (!ReadTag(&tag))
return v8::Null(isolate_);
switch (tag) {
case kNewSerializationTag: {
v8::Local<v8::Value> value;
if (!deserializer_.ReadValue(context).ToLocal(&value)) {
return v8::Null(isolate_);
}
return scope.Escape(value);
}
case kOldSerializationTag: {
v8::Local<v8::Value> value;
if (!ReadBaseValue(&value)) {
return v8::Null(isolate_);
}
return scope.Escape(value);
}
default:
NOTREACHED() << "Invalid tag: " << tag;
return v8::Null(isolate_);
}
}
bool ReadTag(uint8_t* tag) {
const void* tag_bytes;
if (!deserializer_.ReadRawBytes(1, &tag_bytes))
return false;
*tag = *reinterpret_cast<const uint8_t*>(tag_bytes);
return true;
}
bool ReadBaseValue(v8::Local<v8::Value>* value) {
uint32_t length;
const void* data;
if (!deserializer_.ReadUint32(&length) ||
!deserializer_.ReadRawBytes(length, &data)) {
return false;
}
mojo::Message message(
base::make_span(reinterpret_cast<const uint8_t*>(data), length), {});
base::Value out;
if (!mojo_base::mojom::Value::DeserializeFromMessage(std::move(message),
&out)) {
return false;
}
*value = ConvertToV8(isolate_, out);
return true;
}
private:
v8::Isolate* isolate_;
v8::ValueDeserializer deserializer_;
};
} // namespace
v8::Local<v8::Value> Converter<blink::CloneableMessage>::ToV8(
v8::Isolate* isolate,
const blink::CloneableMessage& in) {
return V8Deserializer(isolate, in).Deserialize();
}
bool Converter<blink::CloneableMessage>::FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
blink::CloneableMessage* out) {
return V8Serializer(isolate).Serialize(val, out);
}
} // namespace mate

View File

@@ -6,6 +6,7 @@
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_
#include "native_mate/converter.h"
#include "third_party/blink/public/common/messaging/cloneable_message.h"
#include "third_party/blink/public/platform/web_cache.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/web/web_context_menu_data.h"
@@ -131,6 +132,15 @@ struct Converter<network::mojom::ReferrerPolicy> {
network::mojom::ReferrerPolicy* out);
};
template <>
struct Converter<blink::CloneableMessage> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const blink::CloneableMessage& in);
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
blink::CloneableMessage* out);
};
v8::Local<v8::Value> EditFlagsToV8(v8::Isolate* isolate, int editFlags);
v8::Local<v8::Value> MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags);

View File

@@ -5,6 +5,7 @@
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_
#include <utility>
#include <vector>
#include "atom/common/api/locker.h"
@@ -54,7 +55,8 @@ struct V8FunctionInvoker<v8::Local<v8::Value>(ArgTypes...)> {
v8::Local<v8::Function> holder = function.NewHandle(isolate);
v8::Local<v8::Context> context = holder->CreationContext();
v8::Context::Scope context_scope(context);
std::vector<v8::Local<v8::Value>> args{ConvertToV8(isolate, raw)...};
std::vector<v8::Local<v8::Value>> args{
ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
v8::MaybeLocal<v8::Value> ret = holder->Call(
context, holder, args.size(), args.empty() ? nullptr : &args.front());
if (ret.IsEmpty())
@@ -78,7 +80,8 @@ struct V8FunctionInvoker<void(ArgTypes...)> {
v8::Local<v8::Function> holder = function.NewHandle(isolate);
v8::Local<v8::Context> context = holder->CreationContext();
v8::Context::Scope context_scope(context);
std::vector<v8::Local<v8::Value>> args{ConvertToV8(isolate, raw)...};
std::vector<v8::Local<v8::Value>> args{
ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
holder
->Call(context, holder, args.size(),
args.empty() ? nullptr : &args.front())
@@ -101,7 +104,8 @@ struct V8FunctionInvoker<ReturnType(ArgTypes...)> {
v8::Local<v8::Function> holder = function.NewHandle(isolate);
v8::Local<v8::Context> context = holder->CreationContext();
v8::Context::Scope context_scope(context);
std::vector<v8::Local<v8::Value>> args{ConvertToV8(isolate, raw)...};
std::vector<v8::Local<v8::Value>> args{
ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
v8::Local<v8::Value> result;
auto maybe_result = holder->Call(context, holder, args.size(),
args.empty() ? nullptr : &args.front());
@@ -138,20 +142,6 @@ struct NativeFunctionInvoker<ReturnType(ArgTypes...)> {
} // namespace internal
template <typename Sig>
struct Converter<base::OnceCallback<Sig>> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
base::OnceCallback<Sig>* out) {
if (!val->IsFunction())
return false;
*out = base::BindOnce(&internal::V8FunctionInvoker<Sig>::Go, isolate,
internal::SafeV8Function(isolate, val));
return true;
}
};
template <typename Sig>
struct Converter<base::RepeatingCallback<Sig>> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,

View File

@@ -0,0 +1,87 @@
// Copyright (c) 2019 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_ONCE_CALLBACK_H_
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_ONCE_CALLBACK_H_
#include <utility>
#include "atom/common/native_mate_converters/callback.h"
namespace mate {
namespace internal {
// Manages the OnceCallback with ref-couting.
template <typename Sig>
class RefCountedOnceCallback
: public base::RefCounted<RefCountedOnceCallback<Sig>> {
public:
explicit RefCountedOnceCallback(base::OnceCallback<Sig> callback)
: callback_(std::move(callback)) {}
base::OnceCallback<Sig> GetCallback() { return std::move(callback_); }
private:
friend class base::RefCounted<RefCountedOnceCallback<Sig>>;
~RefCountedOnceCallback() = default;
base::OnceCallback<Sig> callback_;
};
// Invokes the OnceCallback.
template <typename Sig>
struct InvokeOnceCallback {};
template <typename... ArgTypes>
struct InvokeOnceCallback<void(ArgTypes...)> {
static void Go(
scoped_refptr<RefCountedOnceCallback<void(ArgTypes...)>> holder,
ArgTypes... args) {
base::OnceCallback<void(ArgTypes...)> callback = holder->GetCallback();
DCHECK(!callback.is_null());
std::move(callback).Run(std::move(args)...);
}
};
template <typename ReturnType, typename... ArgTypes>
struct InvokeOnceCallback<ReturnType(ArgTypes...)> {
static ReturnType Go(
scoped_refptr<RefCountedOnceCallback<ReturnType(ArgTypes...)>> holder,
ArgTypes... args) {
base::OnceCallback<void(ArgTypes...)> callback = holder->GetCallback();
DCHECK(!callback.is_null());
return std::move(callback).Run(std::move(args)...);
}
};
} // namespace internal
template <typename Sig>
struct Converter<base::OnceCallback<Sig>> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
base::OnceCallback<Sig> val) {
// Reuse the converter of base::RepeatingCallback by storing the callback
// with a RefCounted.
auto holder = base::MakeRefCounted<internal::RefCountedOnceCallback<Sig>>(
std::move(val));
return Converter<base::RepeatingCallback<Sig>>::ToV8(
isolate,
base::BindRepeating(&internal::InvokeOnceCallback<Sig>::Go, holder));
}
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
base::OnceCallback<Sig>* out) {
if (!val->IsFunction())
return false;
*out = base::BindOnce(&internal::V8FunctionInvoker<Sig>::Go, isolate,
internal::SafeV8Function(isolate, val));
return true;
}
};
} // namespace mate
#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_ONCE_CALLBACK_H_

View File

@@ -65,6 +65,7 @@
V(atom_common_screen) \
V(atom_common_shell) \
V(atom_common_v8_util) \
V(atom_renderer_context_bridge) \
V(atom_renderer_ipc) \
V(atom_renderer_web_frame)

View File

@@ -103,6 +103,16 @@ class Promise {
return GetInner()->Reject(GetContext(), v8::Undefined(isolate()));
}
v8::Maybe<bool> Reject(v8::Local<v8::Value> exception) {
v8::HandleScope handle_scope(isolate());
v8::MicrotasksScope script_scope(isolate(),
v8::MicrotasksScope::kRunMicrotasks);
v8::Context::Scope context_scope(
v8::Local<v8::Context>::New(isolate(), GetContext()));
return GetInner()->Reject(GetContext(), exception);
}
// Please note that using Then is effectively the same as calling .then
// in javascript. This means (a) it is not type safe and (b) please note
// it is NOT type safe.

View File

@@ -0,0 +1,513 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/renderer/api/atom_api_context_bridge.h"
#include <set>
#include <utility>
#include <vector>
#include "atom/common/api/object_life_monitor.h"
#include "atom/common/native_mate_converters/blink_converter.h"
#include "atom/common/native_mate_converters/callback.h"
#include "atom/common/native_mate_converters/once_callback.h"
#include "atom/common/promise_util.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
namespace atom {
namespace api {
namespace {
static int kMaxRecursion = 1000;
content::RenderFrame* GetRenderFrame(const v8::Local<v8::Object>& value) {
v8::Local<v8::Context> context = value->CreationContext();
if (context.IsEmpty())
return nullptr;
blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForContext(context);
if (!frame)
return nullptr;
return content::RenderFrame::FromWebFrame(frame);
}
std::map<content::RenderFrame*, context_bridge::RenderFramePersistenceStore*>&
GetStoreMap() {
static base::NoDestructor<std::map<
content::RenderFrame*, context_bridge::RenderFramePersistenceStore*>>
store_map;
return *store_map;
}
context_bridge::RenderFramePersistenceStore* GetOrCreateStore(
content::RenderFrame* render_frame) {
auto it = GetStoreMap().find(render_frame);
if (it == GetStoreMap().end()) {
auto* store = new context_bridge::RenderFramePersistenceStore(render_frame);
GetStoreMap().emplace(render_frame, store);
return store;
}
return it->second;
}
// Sourced from "extensions/renderer/v8_schema_registry.cc"
// Recursively freezes every v8 object on |object|.
bool DeepFreeze(const v8::Local<v8::Object>& object,
const v8::Local<v8::Context>& context,
std::set<int> frozen = std::set<int>()) {
int hash = object->GetIdentityHash();
if (frozen.find(hash) != frozen.end())
return true;
frozen.insert(hash);
v8::Local<v8::Array> property_names =
object->GetOwnPropertyNames(context).ToLocalChecked();
for (uint32_t i = 0; i < property_names->Length(); ++i) {
v8::Local<v8::Value> child =
object->Get(context, property_names->Get(context, i).ToLocalChecked())
.ToLocalChecked();
if (child->IsObject() && !child->IsTypedArray()) {
if (!DeepFreeze(v8::Local<v8::Object>::Cast(child), context, frozen))
return false;
}
}
return mate::internal::IsTrue(
object->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen));
}
bool IsPlainObject(const v8::Local<v8::Value>& object) {
if (!object->IsObject())
return false;
return !(object->IsNullOrUndefined() || object->IsDate() ||
object->IsArgumentsObject() || object->IsBigIntObject() ||
object->IsBooleanObject() || object->IsNumberObject() ||
object->IsStringObject() || object->IsSymbolObject() ||
object->IsNativeError() || object->IsRegExp() ||
object->IsPromise() || object->IsMap() || object->IsSet() ||
object->IsMapIterator() || object->IsSetIterator() ||
object->IsWeakMap() || object->IsWeakSet() ||
object->IsArrayBuffer() || object->IsArrayBufferView() ||
object->IsArray() || object->IsDataView() ||
object->IsSharedArrayBuffer() || object->IsProxy() ||
object->IsWebAssemblyCompiledModule() ||
object->IsModuleNamespaceObject());
}
bool IsPlainArray(const v8::Local<v8::Value>& arr) {
if (!arr->IsArray())
return false;
return !arr->IsTypedArray();
}
class FunctionLifeMonitor final : public ObjectLifeMonitor {
public:
static void BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
context_bridge::RenderFramePersistenceStore* store,
size_t func_id) {
new FunctionLifeMonitor(isolate, target, store, func_id);
}
protected:
FunctionLifeMonitor(v8::Isolate* isolate,
v8::Local<v8::Object> target,
context_bridge::RenderFramePersistenceStore* store,
size_t func_id)
: ObjectLifeMonitor(isolate, target), store_(store), func_id_(func_id) {}
~FunctionLifeMonitor() override = default;
void RunDestructor() override { store_->functions().erase(func_id_); }
private:
context_bridge::RenderFramePersistenceStore* store_;
size_t func_id_;
};
} // namespace
template <typename Sig>
v8::Local<v8::Value> BindRepeatingFunctionToV8(
v8::Isolate* isolate,
const base::RepeatingCallback<Sig>& val) {
auto translater =
base::BindRepeating(&mate::internal::NativeFunctionInvoker<Sig>::Go, val);
return mate::internal::CreateFunctionFromTranslater(isolate, translater,
false);
}
v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Local<v8::Context> source_context,
v8::Local<v8::Context> destination_context,
v8::Local<v8::Value> value,
context_bridge::RenderFramePersistenceStore* store,
int recursion_depth) {
if (recursion_depth >= kMaxRecursion) {
v8::Context::Scope source_scope(source_context);
{
source_context->GetIsolate()->ThrowException(v8::Exception::TypeError(
mate::StringToV8(source_context->GetIsolate(),
"Electron contextBridge recursion depth exceeded. "
"Nested objects "
"deeper than 1000 are not supported.")));
return v8::MaybeLocal<v8::Value>();
}
}
// Check Cache
auto cached_value = store->GetCachedProxiedObject(value);
if (!cached_value.IsEmpty()) {
return cached_value;
}
// Proxy functions and monitor the lifetime in the new context to release
// the global handle at the right time.
if (value->IsFunction()) {
auto func = v8::Local<v8::Function>::Cast(value);
v8::Global<v8::Function> global_func(source_context->GetIsolate(), func);
v8::Global<v8::Context> global_source(source_context->GetIsolate(),
source_context);
size_t func_id = store->take_func_id();
store->functions()[func_id] =
std::make_tuple(std::move(global_func), std::move(global_source));
v8::Context::Scope destination_scope(destination_context);
{
v8::Local<v8::Value> proxy_func = BindRepeatingFunctionToV8(
destination_context->GetIsolate(),
base::BindRepeating(&ProxyFunctionWrapper, store, func_id));
FunctionLifeMonitor::BindTo(destination_context->GetIsolate(),
v8::Local<v8::Object>::Cast(proxy_func),
store, func_id);
store->CacheProxiedObject(value, proxy_func);
return v8::MaybeLocal<v8::Value>(proxy_func);
}
}
// Proxy promises as they have a safe and guaranteed memory lifecycle
if (value->IsPromise()) {
v8::Context::Scope destination_scope(destination_context);
{
auto source_promise = v8::Local<v8::Promise>::Cast(value);
auto* proxied_promise =
new util::Promise(destination_context->GetIsolate());
v8::Local<v8::Promise> proxied_promise_handle =
proxied_promise->GetHandle();
auto then_cb = base::BindOnce(
[](util::Promise* proxied_promise, v8::Isolate* isolate,
v8::Global<v8::Context> global_source_context,
v8::Global<v8::Context> global_destination_context,
context_bridge::RenderFramePersistenceStore* store,
v8::Local<v8::Value> result) {
auto val = PassValueToOtherContext(
global_source_context.Get(isolate),
global_destination_context.Get(isolate), result, store, 0);
if (!val.IsEmpty())
proxied_promise->Resolve(val.ToLocalChecked());
delete proxied_promise;
},
proxied_promise, destination_context->GetIsolate(),
v8::Global<v8::Context>(source_context->GetIsolate(), source_context),
v8::Global<v8::Context>(destination_context->GetIsolate(),
destination_context),
store);
auto catch_cb = base::BindOnce(
[](util::Promise* proxied_promise, v8::Isolate* isolate,
v8::Global<v8::Context> global_source_context,
v8::Global<v8::Context> global_destination_context,
context_bridge::RenderFramePersistenceStore* store,
v8::Local<v8::Value> result) {
auto val = PassValueToOtherContext(
global_source_context.Get(isolate),
global_destination_context.Get(isolate), result, store, 0);
if (!val.IsEmpty())
proxied_promise->Reject(val.ToLocalChecked());
delete proxied_promise;
},
proxied_promise, destination_context->GetIsolate(),
v8::Global<v8::Context>(source_context->GetIsolate(), source_context),
v8::Global<v8::Context>(destination_context->GetIsolate(),
destination_context),
store);
ignore_result(source_promise->Then(
source_context,
v8::Local<v8::Function>::Cast(
mate::ConvertToV8(destination_context->GetIsolate(), then_cb)),
v8::Local<v8::Function>::Cast(
mate::ConvertToV8(destination_context->GetIsolate(), catch_cb))));
store->CacheProxiedObject(value, proxied_promise_handle);
return v8::MaybeLocal<v8::Value>(proxied_promise_handle);
}
}
// Errors aren't serializable currently, we need to pull the message out and
// re-construct in the destination context
if (value->IsNativeError()) {
v8::Context::Scope destination_context_scope(destination_context);
return v8::MaybeLocal<v8::Value>(v8::Exception::Error(
v8::Exception::CreateMessage(destination_context->GetIsolate(), value)
->Get()));
}
// Manually go through the array and pass each value individually into a new
// array so that functions deep inside arrays get proxied or arrays of
// promises are proxied correctly.
if (IsPlainArray(value)) {
v8::Context::Scope destination_context_scope(destination_context);
{
v8::Local<v8::Array> arr = v8::Local<v8::Array>::Cast(value);
size_t length = arr->Length();
v8::Local<v8::Array> cloned_arr =
v8::Array::New(destination_context->GetIsolate(), length);
for (size_t i = 0; i < length; i++) {
auto value_for_array = PassValueToOtherContext(
source_context, destination_context,
arr->Get(source_context, i).ToLocalChecked(), store,
recursion_depth + 1);
if (value_for_array.IsEmpty())
return v8::MaybeLocal<v8::Value>();
if (!mate::internal::IsTrue(
cloned_arr->Set(destination_context, static_cast<int>(i),
value_for_array.ToLocalChecked()))) {
return v8::MaybeLocal<v8::Value>();
}
}
store->CacheProxiedObject(value, cloned_arr);
return v8::MaybeLocal<v8::Value>(cloned_arr);
}
}
// Proxy all objects
if (IsPlainObject(value)) {
auto object_value = v8::Local<v8::Object>::Cast(value);
auto passed_value =
CreateProxyForAPI(object_value, source_context, destination_context,
store, recursion_depth + 1);
if (passed_value.IsEmpty())
return v8::MaybeLocal<v8::Value>();
return v8::MaybeLocal<v8::Value>(passed_value.ToLocalChecked());
}
// Serializable objects
blink::CloneableMessage ret;
{
v8::Context::Scope source_context_scope(source_context);
{
// V8 serializer will throw an error if required
if (!mate::ConvertFromV8(source_context->GetIsolate(), value, &ret))
return v8::MaybeLocal<v8::Value>();
}
}
v8::Context::Scope destination_context_scope(destination_context);
{
v8::Local<v8::Value> cloned_value =
mate::ConvertToV8(destination_context->GetIsolate(), ret);
store->CacheProxiedObject(value, cloned_value);
return v8::MaybeLocal<v8::Value>(cloned_value);
}
}
v8::Local<v8::Value> ProxyFunctionWrapper(
context_bridge::RenderFramePersistenceStore* store,
size_t func_id,
mate::Arguments* args) {
// Context the proxy function was called from
v8::Local<v8::Context> calling_context = args->isolate()->GetCurrentContext();
// Context the function was created in
v8::Local<v8::Context> func_owning_context =
std::get<1>(store->functions()[func_id]).Get(args->isolate());
v8::Context::Scope func_owning_context_scope(func_owning_context);
{
v8::Local<v8::Function> func =
(std::get<0>(store->functions()[func_id])).Get(args->isolate());
std::vector<v8::Local<v8::Value>> original_args;
std::vector<v8::Local<v8::Value>> proxied_args;
args->GetRemaining(&original_args);
for (auto value : original_args) {
auto arg = PassValueToOtherContext(calling_context, func_owning_context,
value, store, 0);
if (arg.IsEmpty())
return v8::Undefined(args->isolate());
proxied_args.push_back(arg.ToLocalChecked());
}
v8::MaybeLocal<v8::Value> maybe_return_value;
bool did_error = false;
std::string 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() ||
!mate::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.";
}
}
}
if (did_error) {
v8::Context::Scope calling_context_scope(calling_context);
{
args->ThrowError(error_message);
return v8::Local<v8::Object>();
}
}
if (maybe_return_value.IsEmpty())
return v8::Undefined(args->isolate());
auto ret =
PassValueToOtherContext(func_owning_context, calling_context,
maybe_return_value.ToLocalChecked(), store, 0);
if (ret.IsEmpty())
return v8::Undefined(args->isolate());
return ret.ToLocalChecked();
}
}
v8::MaybeLocal<v8::Object> CreateProxyForAPI(
const v8::Local<v8::Object>& api_object,
const v8::Local<v8::Context>& source_context,
const v8::Local<v8::Context>& destination_context,
context_bridge::RenderFramePersistenceStore* store,
int recursion_depth) {
mate::Dictionary api(source_context->GetIsolate(), api_object);
mate::Dictionary proxy =
mate::Dictionary::CreateEmpty(destination_context->GetIsolate());
store->CacheProxiedObject(api.GetHandle(), proxy.GetHandle());
auto maybe_keys = api.GetHandle()->GetOwnPropertyNames(
source_context,
static_cast<v8::PropertyFilter>(v8::ONLY_ENUMERABLE | v8::SKIP_SYMBOLS),
v8::KeyConversionMode::kConvertToString);
if (maybe_keys.IsEmpty())
return v8::MaybeLocal<v8::Object>(proxy.GetHandle());
auto keys = maybe_keys.ToLocalChecked();
v8::Context::Scope destination_context_scope(destination_context);
{
uint32_t length = keys->Length();
std::string key_str;
for (uint32_t i = 0; i < length; i++) {
v8::Local<v8::Value> key =
keys->Get(destination_context, i).ToLocalChecked();
// Try get the key as a string
if (!mate::ConvertFromV8(api.isolate(), key, &key_str)) {
continue;
}
v8::Local<v8::Value> value;
if (!api.Get(key_str, &value))
continue;
auto passed_value =
PassValueToOtherContext(source_context, destination_context, value,
store, recursion_depth + 1);
if (passed_value.IsEmpty())
return v8::MaybeLocal<v8::Object>();
proxy.Set(key_str, passed_value.ToLocalChecked());
}
return proxy.GetHandle();
}
}
#ifdef DCHECK_IS_ON
mate::Dictionary DebugGC(mate::Dictionary empty) {
auto* render_frame = GetRenderFrame(empty.GetHandle());
auto* store = GetOrCreateStore(render_frame);
mate::Dictionary ret = mate::Dictionary::CreateEmpty(empty.isolate());
ret.Set("functionCount", store->functions().size());
auto* proxy_map = store->proxy_map();
ret.Set("objectCount", proxy_map->size() * 2);
int live_from = 0;
int live_proxy = 0;
for (auto iter = proxy_map->begin(); iter != proxy_map->end(); iter++) {
auto* node = iter->second;
while (node) {
if (!std::get<0>(node->pair).IsEmpty())
live_from++;
if (!std::get<1>(node->pair).IsEmpty())
live_proxy++;
node = node->next;
}
}
ret.Set("liveFromValues", live_from);
ret.Set("liveProxyValues", live_proxy);
return ret;
}
#endif
void ExposeAPIInMainWorld(const std::string& key,
v8::Local<v8::Object> api_object,
mate::Arguments* args) {
auto* render_frame = GetRenderFrame(api_object);
CHECK(render_frame);
context_bridge::RenderFramePersistenceStore* store =
GetOrCreateStore(render_frame);
auto* frame = render_frame->GetWebFrame();
CHECK(frame);
v8::Local<v8::Context> main_context = frame->MainWorldScriptContext();
mate::Dictionary global(main_context->GetIsolate(), main_context->Global());
if (global.Has(key)) {
args->ThrowError(
"Cannot bind an API on top of an existing property on the window "
"object");
return;
}
v8::Local<v8::Context> isolated_context =
frame->WorldScriptContext(args->isolate(), atom::World::ISOLATED_WORLD);
v8::Context::Scope main_context_scope(main_context);
{
v8::MaybeLocal<v8::Object> maybe_proxy =
CreateProxyForAPI(api_object, isolated_context, main_context, store, 0);
if (maybe_proxy.IsEmpty())
return;
auto proxy = maybe_proxy.ToLocalChecked();
if (!DeepFreeze(proxy, main_context))
return;
global.SetReadOnlyNonConfigurable(key, proxy);
}
}
} // namespace api
} // namespace atom
namespace {
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
mate::Dictionary dict(isolate, exports);
dict.SetMethod("exposeAPIInMainWorld", &atom::api::ExposeAPIInMainWorld);
#ifdef DCHECK_IS_ON
dict.SetMethod("_debugGCMaps", &atom::api::DebugGC);
#endif
}
} // namespace
NODE_LINKED_MODULE_CONTEXT_AWARE(atom_renderer_context_bridge, Initialize)

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_RENDERER_API_ATOM_API_CONTEXT_BRIDGE_H_
#define ATOM_RENDERER_API_ATOM_API_CONTEXT_BRIDGE_H_
#include <map>
#include <string>
#include <tuple>
#include "atom/common/node_includes.h"
#include "atom/renderer/api/context_bridge/render_frame_context_bridge_store.h"
#include "atom/renderer/atom_render_frame_observer.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "native_mate/converter.h"
#include "native_mate/dictionary.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace atom {
namespace api {
v8::Local<v8::Value> ProxyFunctionWrapper(
context_bridge::RenderFramePersistenceStore* store,
size_t func_id,
mate::Arguments* args);
v8::MaybeLocal<v8::Object> CreateProxyForAPI(
const v8::Local<v8::Object>& api_object,
const v8::Local<v8::Context>& source_context,
const v8::Local<v8::Context>& target_context,
context_bridge::RenderFramePersistenceStore* store,
int recursion_depth);
} // namespace api
} // namespace atom
#endif // ATOM_RENDERER_API_ATOM_API_CONTEXT_BRIDGE_H_

View File

@@ -82,6 +82,7 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
bool internal,
const std::string& channel,
const base::ListValue& arguments) {
std::string error;
base::Value result;
// A task is posted to a separate thread to execute the request so that
@@ -98,39 +99,66 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
// interface.
auto interface_info = electron_browser_ptr_.PassInterface();
task_runner->PostTask(
FROM_HERE, base::BindOnce(&IPCRenderer::SendMessageSyncOnWorkerThread,
base::Unretained(&interface_info),
base::Unretained(&response_received_event),
base::Unretained(&result), internal, channel,
base::Unretained(&arguments)));
FROM_HERE,
base::BindOnce(&IPCRenderer::SendMessageSyncOnWorkerThread,
base::Unretained(this),
base::Unretained(&interface_info),
base::Unretained(&response_received_event),
base::Unretained(&result), internal, channel,
base::Unretained(&arguments), base::Unretained(&error)));
response_received_event.Wait();
electron_browser_ptr_.Bind(std::move(interface_info));
if (!error.empty()) {
args->ThrowError(error);
}
return result;
}
private:
static void SendMessageSyncOnWorkerThread(
void SendMessageSyncOnWorkerThread(
atom::mojom::ElectronBrowserPtrInfo* interface_info,
base::WaitableEvent* event,
base::Value* result,
bool internal,
const std::string& channel,
const base::ListValue* arguments) {
const base::ListValue* arguments,
std::string* error) {
atom::mojom::ElectronBrowserPtr browser_ptr(std::move(*interface_info));
browser_ptr->MessageSync(
electron_browser_ptr_ = std::move(browser_ptr);
electron_browser_ptr_.set_connection_error_handler(
base::BindOnce(&IPCRenderer::HandleMojoConnectionErrorOnWorkerThread,
base::Unretained(&electron_browser_ptr_),
base::Unretained(interface_info),
base::Unretained(event), base::Unretained(error)));
electron_browser_ptr_->MessageSync(
internal, channel, arguments->Clone(),
base::BindOnce(&IPCRenderer::ReturnSyncResponseToMainThread,
std::move(browser_ptr), base::Unretained(interface_info),
base::Unretained(&electron_browser_ptr_),
base::Unretained(interface_info),
base::Unretained(event), base::Unretained(result)));
}
static void ReturnSyncResponseToMainThread(
atom::mojom::ElectronBrowserPtr ptr,
atom::mojom::ElectronBrowserPtr* ptr,
atom::mojom::ElectronBrowserPtrInfo* interface_info,
base::WaitableEvent* event,
base::Value* result,
base::Value response) {
*result = std::move(response);
*interface_info = ptr.PassInterface();
*interface_info = ptr->PassInterface();
event->Signal();
}
// If the other end of the message pipe disconnects, ensure we don't hang the
// main thread forever.
static void HandleMojoConnectionErrorOnWorkerThread(
atom::mojom::ElectronBrowserPtr* ptr,
atom::mojom::ElectronBrowserPtrInfo* interface_info,
base::WaitableEvent* event,
std::string* error) {
LOG(INFO) << "Mojo connection interuppted, likely due to the Mojo receiver "
"process crashing.";
*error = "IPC connection fatally interrupted.";
*interface_info = ptr->PassInterface();
event->Signal();
}

View File

@@ -0,0 +1,145 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/renderer/api/context_bridge/render_frame_context_bridge_store.h"
#include <utility>
#include "atom/common/api/object_life_monitor.h"
namespace atom {
namespace api {
namespace context_bridge {
namespace {
class CachedProxyLifeMonitor final : public ObjectLifeMonitor {
public:
static void BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
RenderFramePersistenceStore* store,
WeakGlobalPairNode* node,
int hash) {
new CachedProxyLifeMonitor(isolate, target, store, node, hash);
}
protected:
CachedProxyLifeMonitor(v8::Isolate* isolate,
v8::Local<v8::Object> target,
RenderFramePersistenceStore* store,
WeakGlobalPairNode* node,
int hash)
: ObjectLifeMonitor(isolate, target),
store_(store),
node_(node),
hash_(hash) {}
void RunDestructor() override {
if (node_->detached) {
delete node_;
}
if (node_->prev) {
node_->prev->next = node_->next;
}
if (node_->next) {
node_->next->prev = node_->prev;
}
if (!node_->prev && !node_->next) {
// Must be a single length linked list
store_->proxy_map()->erase(hash_);
}
node_->detached = true;
}
private:
RenderFramePersistenceStore* store_;
WeakGlobalPairNode* node_;
int hash_;
};
} // namespace
WeakGlobalPairNode::WeakGlobalPairNode(WeakGlobalPair pair) {
this->pair = std::move(pair);
}
WeakGlobalPairNode::~WeakGlobalPairNode() {
if (next) {
delete next;
}
}
RenderFramePersistenceStore::RenderFramePersistenceStore(
content::RenderFrame* render_frame)
: content::RenderFrameObserver(render_frame) {}
RenderFramePersistenceStore::~RenderFramePersistenceStore() = default;
void RenderFramePersistenceStore::OnDestruct() {
delete this;
}
void RenderFramePersistenceStore::CacheProxiedObject(
v8::Local<v8::Value> from,
v8::Local<v8::Value> proxy_value) {
if (from->IsObject() && !from->IsNullOrUndefined()) {
auto obj = v8::Local<v8::Object>::Cast(from);
int hash = obj->GetIdentityHash();
auto global_from = v8::Global<v8::Value>(v8::Isolate::GetCurrent(), from);
auto global_proxy =
v8::Global<v8::Value>(v8::Isolate::GetCurrent(), proxy_value);
// Do not retain
global_from.SetWeak();
global_proxy.SetWeak();
auto iter = proxy_map_.find(hash);
auto* node = new WeakGlobalPairNode(
std::make_tuple(std::move(global_from), std::move(global_proxy)));
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(), obj, this, node,
hash);
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(),
v8::Local<v8::Object>::Cast(proxy_value),
this, node, hash);
if (iter == proxy_map_.end()) {
proxy_map_.emplace(hash, node);
} else {
WeakGlobalPairNode* target = iter->second;
while (target->next) {
target = target->next;
}
target->next = node;
node->prev = target;
}
}
}
v8::MaybeLocal<v8::Value> RenderFramePersistenceStore::GetCachedProxiedObject(
v8::Local<v8::Value> from) {
if (!from->IsObject() || from->IsNullOrUndefined())
return v8::MaybeLocal<v8::Value>();
auto obj = v8::Local<v8::Object>::Cast(from);
int hash = obj->GetIdentityHash();
auto iter = proxy_map_.find(hash);
if (iter == proxy_map_.end())
return v8::MaybeLocal<v8::Value>();
WeakGlobalPairNode* target = iter->second;
while (target) {
auto from_cmp = std::get<0>(target->pair).Get(v8::Isolate::GetCurrent());
if (from_cmp == from) {
if (std::get<1>(target->pair).IsEmpty())
return v8::MaybeLocal<v8::Value>();
return std::get<1>(target->pair).Get(v8::Isolate::GetCurrent());
}
target = target->next;
}
return v8::MaybeLocal<v8::Value>();
}
} // namespace context_bridge
} // namespace api
} // namespace atom

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2019 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
#define ATOM_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
#include <map>
#include <tuple>
#include "atom/renderer/atom_render_frame_observer.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace atom {
namespace api {
namespace context_bridge {
using FunctionContextPair =
std::tuple<v8::Global<v8::Function>, v8::Global<v8::Context>>;
using WeakGlobalPair = std::tuple<v8::Global<v8::Value>, v8::Global<v8::Value>>;
struct WeakGlobalPairNode {
explicit WeakGlobalPairNode(WeakGlobalPair pair_);
~WeakGlobalPairNode();
WeakGlobalPair pair;
bool detached = false;
struct WeakGlobalPairNode* prev = nullptr;
struct WeakGlobalPairNode* next = nullptr;
};
class RenderFramePersistenceStore final : public content::RenderFrameObserver {
public:
explicit RenderFramePersistenceStore(content::RenderFrame* render_frame);
~RenderFramePersistenceStore() override;
// RenderFrameObserver implementation.
void OnDestruct() override;
size_t take_func_id() { return next_func_id_++; }
std::map<size_t, FunctionContextPair>& functions() { return functions_; }
std::map<int, WeakGlobalPairNode*>* proxy_map() { return &proxy_map_; }
void CacheProxiedObject(v8::Local<v8::Value> from,
v8::Local<v8::Value> proxy_value);
v8::MaybeLocal<v8::Value> GetCachedProxiedObject(v8::Local<v8::Value> from);
private:
// func_id ==> { function, owning_context }
std::map<size_t, FunctionContextPair> functions_;
size_t next_func_id_ = 1;
// proxy maps are weak globals, i.e. these are not retained beyond
// there normal JS lifetime. You must check IsEmpty()
// object_identity ==> [from_value, proxy_value]
std::map<int, WeakGlobalPairNode*> proxy_map_;
};
} // namespace context_bridge
} // namespace api
} // namespace atom
#endif // ATOM_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_

View File

@@ -77,8 +77,8 @@ steps:
python electron\script\verify-ffmpeg.py --build-dir out\Default --source-root %cd% --ffmpeg-path out\ffmpeg
displayName: 'Verify ffmpeg'
- script: |
taskkill /F /IM electron.exe
taskkill /F /IM MicrosoftEdge.exe
- powershell: |
Get-Process | Where Name Like "electron*" | Stop-Process
Get-Process | Where Name Like "MicrosoftEdge*" | Stop-Process
displayName: 'Kill processes left running from last test run'
condition: always()

View File

@@ -2,10 +2,9 @@
<head>
<title>Electron</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-6PH54BfkNq/EMMhUY7nhHf3c+AxloOwfy7hWyT01CM8='; style-src 'self'; img-src 'self'; connect-src 'self'" />
<link href="./styles.css" type="text/css" rel="stylesheet" />
<link href="./octicon/build.css" type="text/css" rel="stylesheet" />
<script defer src="./index.js"></script>
</head>
<body>
@@ -84,6 +83,9 @@
</div>
</div>
</nav>
<script>
window.electronDefaultApp.initialize()
</script>
</body>
</html>

View File

@@ -1,30 +0,0 @@
async function getOcticonSvg (name: string) {
try {
const response = await fetch(`octicon/${name}.svg`)
const div = document.createElement('div')
div.innerHTML = await response.text()
return div
} catch {
return null
}
}
async function loadSVG (element: HTMLSpanElement) {
for (const cssClass of element.classList) {
if (cssClass.startsWith('octicon-')) {
const icon = await getOcticonSvg(cssClass.substr(8))
if (icon) {
for (const elemClass of element.classList) {
icon.classList.add(elemClass)
}
element.before(icon)
element.remove()
break
}
}
}
}
for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) {
loadSVG(element)
}

View File

@@ -1,4 +1,31 @@
import { ipcRenderer } from 'electron'
import { ipcRenderer, contextBridge } from 'electron'
async function getOcticonSvg (name: string) {
try {
const response = await fetch(`octicon/${name}.svg`)
const div = document.createElement('div')
div.innerHTML = await response.text()
return div
} catch {
return null
}
}
async function loadSVG (element: HTMLSpanElement) {
for (const cssClass of element.classList) {
if (cssClass.startsWith('octicon-')) {
const icon = await getOcticonSvg(cssClass.substr(8))
if (icon) {
for (const elemClass of element.classList) {
icon.classList.add(elemClass)
}
element.before(icon)
element.remove()
break
}
}
}
}
function initialize () {
const electronPath = ipcRenderer.sendSync('bootstrap')
@@ -15,6 +42,12 @@ function initialize () {
replaceText('.node-version', `Node v${process.versions.node}`)
replaceText('.v8-version', `v8 v${process.versions.v8}`)
replaceText('.command-example', `${electronPath} path-to-app`)
for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) {
loadSVG(element)
}
}
document.addEventListener('DOMContentLoaded', initialize)
contextBridge.exposeInMainWorld('electronDefaultApp', {
initialize
})

View File

@@ -1285,6 +1285,8 @@ you exactly what went wrong
* `type` String (optional) - Can be `critical` or `informational`. The default is
`informational`
Returns `Integer` an ID representing the request.
When `critical` is passed, the dock icon will bounce until either the
application becomes active or the request is canceled.
@@ -1292,7 +1294,7 @@ When `informational` is passed, the dock icon will bounce for one second.
However, the request remains active until either the application becomes active
or the request is canceled.
Returns `Integer` an ID representing the request.
**Nota Bene:** This method can only be used while the app is not focused; when the app is focused it will return -1.
### `app.dock.cancelBounce(id)` _macOS_

View File

@@ -4,18 +4,12 @@
Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process)
The following example shows how to write a string to the clipboard:
```javascript
const { clipboard } = require('electron')
clipboard.writeText('Example String')
```
On Linux, there is also a `selection` clipboard. To manipulate it
you need to pass `selection` to each method:
```javascript
const { clipboard } = require('electron')
clipboard.writeText('Example String', 'selection')
console.log(clipboard.readText('selection'))
```
@@ -28,56 +22,106 @@ The `clipboard` module has the following methods:
### `clipboard.readText([type])`
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Returns `String` - The content in the clipboard as plain text.
```js
const { clipboard } = require('electron')
clipboard.writeText('hello i am a bit of text!')
const text = clipboard.readText()
console.log(text)
// hello i am a bit of text!'
```
### `clipboard.writeText(text[, type])`
* `text` String
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Writes the `text` into the clipboard as plain text.
```js
const { clipboard } = require('electron')
const text = 'hello i am a bit of text!'
clipboard.writeText(text)
```
### `clipboard.readHTML([type])`
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Returns `String` - The content in the clipboard as markup.
```js
const { clipboard } = require('electron')
clipboard.writeHTML('<b>Hi</b>')
const html = clipboard.readHTML()
console.log(html)
// <meta charset='utf-8'><b>Hi</b>
```
### `clipboard.writeHTML(markup[, type])`
* `markup` String
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Writes `markup` to the clipboard.
```js
const { clipboard } = require('electron')
clipboard.writeHTML('<b>Hi</b')
```
### `clipboard.readImage([type])`
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Returns [`NativeImage`](native-image.md) - The image content in the clipboard.
### `clipboard.writeImage(image[, type])`
* `image` [NativeImage](native-image.md)
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Writes `image` to the clipboard.
### `clipboard.readRTF([type])`
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Returns `String` - The content in the clipboard as RTF.
```js
const { clipboard } = require('electron')
clipboard.writeRTF('{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}')
const rtf = clipboard.readRTF()
console.log(rtf)
// {\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}
```
### `clipboard.writeRTF(text[, type])`
* `text` String
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Writes the `text` into the clipboard in RTF.
```js
const { clipboard } = require('electron')
const rtf = '{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}'
clipboard.writeRTF(rtf)
```
### `clipboard.readBookmark()` _macOS_ _Windows_
Returns `Object`:
@@ -93,7 +137,7 @@ bookmark is unavailable.
* `title` String
* `url` String
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Writes the `title` and `url` into the clipboard as a bookmark.
@@ -102,7 +146,9 @@ you can use `clipboard.write` to write both a bookmark and fallback text to the
clipboard.
```js
clipboard.write({
const { clipboard } = require('electron')
clipboard.writeBookmark({
text: 'https://electronjs.org',
bookmark: 'Electron Homepage'
})
@@ -110,39 +156,50 @@ clipboard.write({
### `clipboard.readFindText()` _macOS_
Returns `String` - The text on the find pasteboard. This method uses synchronous
IPC when called from the renderer process. The cached value is reread from the
find pasteboard whenever the application is activated.
Returns `String` - The text on the find pasteboard, which is the pasteboard that holds information about the current state of the active applications find panel.
This method uses synchronous IPC when called from the renderer process.
The cached value is reread from the find pasteboard whenever the application is activated.
### `clipboard.writeFindText(text)` _macOS_
* `text` String
Writes the `text` into the find pasteboard as plain text. This method uses
synchronous IPC when called from the renderer process.
Writes the `text` into the find pasteboard (the pasteboard that holds information about the current state of the active applications find panel) as plain text. This method uses synchronous IPC when called from the renderer process.
### `clipboard.clear([type])`
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Clears the clipboard content.
### `clipboard.availableFormats([type])`
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Returns `String[]` - An array of supported formats for the clipboard `type`.
```js
const { clipboard } = require('electron')
const formats = clipboard.availableFormats()
console.log(formats)
// [ 'text/plain', 'text/html' ]
```
### `clipboard.has(format[, type])` _Experimental_
* `format` String
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Returns `Boolean` - Whether the clipboard supports the specified `format`.
```javascript
```js
const { clipboard } = require('electron')
console.log(clipboard.has('<p>selection</p>'))
const hasFormat = clipboard.has('<p>selection</p>')
console.log(hasFormat)
// 'true' or 'false
```
### `clipboard.read(format)` _Experimental_
@@ -157,14 +214,33 @@ Returns `String` - Reads `format` type from the clipboard.
Returns `Buffer` - Reads `format` type from the clipboard.
```js
const { clipboard } = require('electron')
const buffer = Buffer.from('this is binary', 'utf8')
clipboard.writeBuffer('public.utf8-plain-text', buffer)
const ret = clipboard.readBuffer('public.utf8-plain-text')
console.log(buffer.equals(out))
// true
```
### `clipboard.writeBuffer(format, buffer[, type])` _Experimental_
* `format` String
* `buffer` Buffer
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
Writes the `buffer` into the clipboard as `format`.
```js
const { clipboard } = require('electron')
const buffer = Buffer.from('writeBuffer', 'utf8')
clipboard.writeBuffer('public.utf8-plain-text', buffer)
```
### `clipboard.write(data[, type])`
* `data` Object
@@ -172,11 +248,30 @@ Writes the `buffer` into the clipboard as `format`.
* `html` String (optional)
* `image` [NativeImage](native-image.md) (optional)
* `rtf` String (optional)
* `bookmark` String (optional) - The title of the url at `text`.
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
* `bookmark` String (optional) - The title of the URL at `text`.
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
```javascript
const { clipboard } = require('electron')
clipboard.write({ text: 'test', html: '<b>test</b>' })
```
Writes `data` to the clipboard.
```js
const { clipboard } = require('electron')
clipboard.write({
text: 'test',
html: '<b>Hi</b>',
rtf: '{\\rtf1\\utf8 text}',
bookmark: 'a title'
})
console.log(clipboard.readText())
// 'test'
console.log(clipboard.readHTML())
// <meta charset='utf-8'><b>Hi</b>
console.log(clipboard.readRTF())
// '{\\rtf1\\utf8 text}'
console.log(clipboard.readBookmark())
// { title: 'a title', url: 'test' }
```

111
docs/api/context-bridge.md Normal file
View File

@@ -0,0 +1,111 @@
# contextBridge
> Create a safe, bi-directional, synchronous bridge across isolated contexts
Process: [Renderer](../glossary.md#renderer-process)
An example of exposing an API to a renderer from an isolated preload script is given below:
```javascript
// Preload (Isolated World)
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing')
}
)
```
```javascript
// Renderer (Main World)
window.electron.doThing()
```
## Glossary
### Main World
The "Main World" is the javascript context that your main renderer code runs in. By default the page you load in your renderer
executes code in this world.
### Isolated World
When `contextIsolation` is enabled in your `webPreferences` your `preload` scripts run in an "Isolated World". You can read more about
context isolation and what it affects in the [BrowserWindow](browser-window.md) docs.
## Methods
The `contextBridge` module has the following methods:
### `contextBridge.exposeInMainWorld(apiKey, api)` _Experimental_
* `apiKey` String - The key to inject the API onto `window` with. The API will be accessible on `window[apiKey]`.
* `api` Record<String, any> - Your API object, more information on what this API can be and how it works is available below.
## Usage
### API Objects
The `api` object provided to [`exposeInMainWorld`](#contextbridgeexposeinmainworldapikey-api-experimental) must be an object
whose keys are strings and values are a `Function`, `String`, `Number`, `Array`, `Boolean` or another nested object that meets the same conditions.
`Function` values are proxied to the other context and all other values are **copied** and **frozen**. I.e. Any data / primitives sent in
the API object become immutable and updates on either side of the bridge do not result in an update on the other side.
An example of a complex API object is shown below.
```javascript
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing'),
myPromises: [Promise.resolve(), Promise.reject(new Error('whoops'))],
anAsyncFunction: async () => 123,
data: {
myFlags: ['a', 'b', 'c'],
bootTime: 1234
},
nestedAPI: {
evenDeeper: {
youCanDoThisAsMuchAsYouWant: {
fn: () => ({
returnData: 123
})
}
}
}
}
)
```
### API Functions
`Function` values that you bind through the `contextBridge` are proxied through Electron to ensure that contexts remain isolated. This
results in some key limitations that we've outlined below.
#### Parameter / Error / Return Type support
Because parameters, errors and return values are **copied** when they are sent over the bridge there are only certain types that can be used.
At a high level if the type you want to use can be serialized and un-serialized into the same object it will work. A table of type support
has been included below for completeness.
| Type | Complexity | Parameter Support | Return Value Support | Limitations |
| ---- | ---------- | ----------------- | -------------------- | ----------- |
| `String` | Simple | ✅ | ✅ | N/A |
| `Number` | Simple | ✅ | ✅ | N/A |
| `Boolean` | Simple | ✅ | ✅ | N/A |
| `Object` | Complex | ✅ | ✅ | Keys must be supported "Simple" types in this table. Values must be supported in this table. Prototype modifications are dropped. Sending custom classes will copy values but not the prototype. |
| `Array` | Complex | ✅ | ✅ | Same limitations as the `Object` type |
| `Error` | Complex | ✅ | ✅ | Errors that are thrown are also copied, this can result in the message and stack trace of the error changing slightly due to being thrown in a different context |
| `Promise` | Complex | ✅ | ✅ | Promises are only proxied if they are a the return value or exact parameter. Promises nested in arrays or obejcts will be dropped. |
| `Function` | Complex | ✅ | ✅ | Prototype modifications are dropped. Sending classes or constructors will not work. |
| [Cloneable Types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) | Simple | ✅ | ✅ | See the linked document on cloneable types |
| `Symbol` | N/A | ❌ | ❌ | Symbols cannot be copied across contexts so they are dropped |
If the type you care about is not in the above table it is probably not supported.

View File

@@ -117,7 +117,7 @@ dialog.showOpenDialogSync(mainWindow, {
Returns `Promise<Object>` - Resolve wih an object containing the following:
* `canceled` - Boolean - whether or not the dialog was canceled.
* `canceled` Boolean - whether or not the dialog was canceled.
* `filePaths` String[] (optional) - An array of file paths chosen by the user. If the dialog is cancelled this will be an empty array.
* `bookmarks` String[] (optional) _macOS_ _mas_ - An array matching the `filePaths` array of base64 encoded strings which contains security scoped bookmark data. `securityScopedBookmarks` must be enabled for this to be populated.

View File

@@ -56,7 +56,7 @@ You can avoid much of the wait by reusing Electron CI's build output via
optional steps (listed below) and these two environment variables:
```sh
export SCCACHE_BUCKET="electronjs-sccache"
export SCCACHE_BUCKET="electronjs-sccache-ci"
export SCCACHE_TWO_TIER=true
```

View File

@@ -97,6 +97,17 @@ a text file. A typical cache might look like this:
├── SHASUMS256.txt-1.8.2-beta.3
```
## Skip binary download
When installing the `electron` NPM package, it automatically downloads the electron binary.
This can sometimes be unnecessary, e.g. in a CI environment, when testing another component.
To prevent the binary from being downloaded when you install all npm dependencies you can set the environment variable `ELECTRON_SKIP_BINARY_DOWNLOAD`.
E.g.:
```sh
ELECTRON_SKIP_BINARY_DOWNLOAD=1 npm install
```
## Troubleshooting
When running `npm install electron`, some users occasionally encounter

View File

@@ -13,7 +13,7 @@
<!-- Desktop Capturer API -->
<message name="IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME" desc="Name for screens in the desktop media picker UI when there is only one monitor.">
Entire screen
Entire Screen
</message>
<message name="IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME" desc="Name for screens in the desktop media picker UI when there are multiple monitors.">
{SCREEN_INDEX, plural, =1{Screen #} other{Screen #}}

View File

@@ -12,6 +12,7 @@ auto_filenames = {
"docs/api/client-request.md",
"docs/api/clipboard.md",
"docs/api/content-tracing.md",
"docs/api/context-bridge.md",
"docs/api/cookies.md",
"docs/api/crash-reporter.md",
"docs/api/debugger.md",

View File

@@ -80,6 +80,7 @@ filenames = {
"lib/renderer/web-view/web-view-impl.ts",
"lib/renderer/web-view/web-view-init.ts",
"lib/renderer/api/exports/electron.js",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.js",
"lib/renderer/api/ipc-renderer.js",
"lib/renderer/api/module-list.js",
@@ -94,7 +95,6 @@ filenames = {
default_app_ts_sources = [
"default_app/default_app.ts",
"default_app/index.ts",
"default_app/main.ts",
"default_app/preload.ts",
]
@@ -622,6 +622,7 @@ filenames = {
"atom/common/native_mate_converters/net_converter.h",
"atom/common/native_mate_converters/network_converter.cc",
"atom/common/native_mate_converters/network_converter.h",
"atom/common/native_mate_converters/once_callback.h",
"atom/common/native_mate_converters/string16_converter.h",
"atom/common/native_mate_converters/ui_base_types_converter.h",
"atom/common/native_mate_converters/v8_value_converter.cc",
@@ -645,6 +646,10 @@ filenames = {
"atom/common/platform_util_win.cc",
"atom/common/promise_util.h",
"atom/common/promise_util.cc",
"atom/renderer/api/context_bridge/render_frame_context_bridge_store.cc",
"atom/renderer/api/context_bridge/render_frame_context_bridge_store.h",
"atom/renderer/api/atom_api_context_bridge.cc",
"atom/renderer/api/atom_api_context_bridge.h",
"atom/renderer/api/atom_api_renderer_ipc.cc",
"atom/renderer/api/atom_api_spell_check_client.cc",
"atom/renderer/api/atom_api_spell_check_client.h",

View File

@@ -72,7 +72,6 @@ const defaultPrintingSetting = {
headerFooterEnabled: false,
marginsType: 0,
isFirstRequest: false,
requestID: getNextId(),
previewUIID: 0,
previewModifiable: true,
printToPDF: true,
@@ -240,7 +239,10 @@ WebContents.prototype.takeHeapSnapshot = function (filePath) {
// Translate the options of printToPDF.
WebContents.prototype.printToPDF = function (options) {
const printingSetting = Object.assign({}, defaultPrintingSetting)
const printingSetting = {
...defaultPrintingSetting,
requestID: getNextId()
}
if (options.landscape) {
printingSetting.landscape = options.landscape
}

View File

@@ -127,6 +127,10 @@ class ObjectsRegistry {
this.clear(webContents, contextId)
}
}
// Note that the "render-view-deleted" event may not be emitted on time when
// the renderer process get destroyed because of navigation, we rely on the
// renderer process to send "ELECTRON_BROWSER_CONTEXT_RELEASE" message to
// guard this situation.
webContents.on('render-view-deleted', listener)
}
}

View File

@@ -231,7 +231,7 @@ const unwrapArgs = function (sender, frameId, contextId, args) {
v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location)
Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender)
rendererFunctions.set(objectId, callIntoRenderer)
return callIntoRenderer
}
@@ -440,7 +440,6 @@ handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId,
handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
objectsRegistry.clear(event.sender, contextId)
return null
})
handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {

View File

@@ -40,8 +40,6 @@
return newArchive
}
const ASAR_EXTENSION = '.asar'
// Separate asar package's path from full path.
const splitPath = archivePathOrBuffer => {
// Shortcut for disabled asar.
@@ -54,22 +52,7 @@
}
if (typeof archivePath !== 'string') return { isAsar: false }
if (archivePath.endsWith(ASAR_EXTENSION)) {
return { isAsar: true, asarPath: archivePath, filePath: '' }
}
archivePath = path.normalize(archivePath)
const index = archivePath.lastIndexOf(`${ASAR_EXTENSION}${path.sep}`)
if (index === -1) return { isAsar: false }
// E.g. for "//some/path/to/archive.asar/then/internal.file"...
return {
isAsar: true,
// "//some/path/to/archive.asar"
asarPath: archivePath.substr(0, index + ASAR_EXTENSION.length),
// "then/internal.file" (with a path separator excluded)
filePath: archivePath.substr(index + ASAR_EXTENSION.length + 1)
}
return asar.splitPath(path.normalize(archivePath))
}
// Convert asar archive's Stats object to fs's Stats object.

View File

@@ -0,0 +1,20 @@
const { hasSwitch } = process.electronBinding('command_line')
const binding = process.electronBinding('context_bridge')
const contextIsolationEnabled = hasSwitch('context-isolation')
const checkContextIsolationEnabled = () => {
if (!contextIsolationEnabled) throw new Error('contextBridge API can only be used when contextIsolation is enabled')
}
const contextBridge = {
exposeInMainWorld: (key: string, api: Record<string, any>) => {
checkContextIsolationEnabled()
return binding.exposeAPIInMainWorld(key, api)
},
debugGC: () => binding._debugGCMaps({})
}
if (!binding._debugGCMaps) delete contextBridge.debugGC
export default contextBridge

View File

@@ -8,6 +8,7 @@ const enableRemoteModule = v8Util.getHiddenValue(global, 'enableRemoteModule')
// Renderer side modules, please sort alphabetically.
// A module is `enabled` if there is no explicit condition defined.
module.exports = [
{ name: 'contextBridge', file: 'context-bridge' },
{ name: 'crashReporter', file: 'crash-reporter', enabled: true },
{
name: 'desktopCapturer',

View File

@@ -21,7 +21,7 @@ const contextId = v8Util.getHiddenValue(global, 'contextId')
// to guard that situation.
process.on('exit', () => {
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
ipcRendererInternal.sendSync(command, contextId)
ipcRendererInternal.send(command, contextId)
})
// Convert the arguments object into an array of meta data.

View File

@@ -3,6 +3,10 @@
const features = process.electronBinding('features')
module.exports = [
{
name: 'contextBridge',
load: () => require('@electron/internal/renderer/api/context-bridge')
},
{
name: 'crashReporter',
load: () => require('@electron/internal/renderer/api/crash-reporter')

View File

@@ -25,7 +25,7 @@ class Arguments {
template<typename T>
bool GetHolder(T* out) {
return ConvertFromV8(isolate_, info_->Holder(), out);
return mate::ConvertFromV8(isolate_, info_->Holder(), out);
}
template<typename T>
@@ -40,7 +40,7 @@ class Arguments {
return false;
}
v8::Local<v8::Value> val = (*info_)[next_];
bool success = ConvertFromV8(isolate_, val, out);
bool success = mate::ConvertFromV8(isolate_, val, out);
if (success)
next_++;
return success;

View File

@@ -8,6 +8,7 @@
#include <map>
#include <set>
#include <string>
#include <type_traits>
#include <vector>
#include "base/strings/string_piece.h"
@@ -317,6 +318,12 @@ v8::Local<v8::Value> ConvertToV8(v8::Isolate* isolate, const T& input) {
return Converter<T>::ToV8(isolate, input);
}
template <typename T>
v8::Local<v8::Value> ConvertToV8(v8::Isolate* isolate, T&& input) {
return Converter<typename std::remove_reference<T>::type>::ToV8(
isolate, std::move(input));
}
inline v8::Local<v8::Value> ConvertToV8(v8::Isolate* isolate,
const char* input) {
return Converter<const char*>::ToV8(isolate, input);

View File

@@ -40,6 +40,12 @@ class Dictionary {
static Dictionary CreateEmpty(v8::Isolate* isolate);
bool Has(base::StringPiece key) const {
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
v8::Local<v8::String> v8_key = StringToV8(isolate_, key);
return internal::IsTrue(GetHandle()->Has(context, v8_key));
}
template <typename T>
bool Get(const base::StringPiece& key, T* out) const {
// Check for existence before getting, otherwise this method will always
@@ -102,6 +108,17 @@ class Dictionary {
return !result.IsNothing() && result.FromJust();
}
template <typename T>
bool SetReadOnlyNonConfigurable(base::StringPiece key, T val) {
v8::Local<v8::Value> v8_value;
if (!TryConvertToV8(isolate_, val, &v8_value))
return false;
v8::Maybe<bool> result = GetHandle()->DefineOwnProperty(
isolate_->GetCurrentContext(), StringToV8(isolate_, key), v8_value,
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete));
return !result.IsNothing() && result.FromJust();
}
template <typename T>
bool SetMethod(const base::StringPiece& key, const T& callback) {
return GetHandle()

View File

@@ -15,6 +15,10 @@ try {
// do nothing
}
if (process.env.ELECTRON_SKIP_BINARY_DOWNLOAD) {
process.exit(0)
}
var platformPath = getPlatformPath()
var electronPath = process.env.ELECTRON_OVERRIDE_DIST_PATH || path.join(__dirname, 'dist', platformPath)

View File

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

View File

@@ -83,3 +83,8 @@ crashpad_pid_check.patch
fix_use_weakptr_to_detect_deletion.patch
fix_disabling_compositor_recycling.patch
allow_new_privileges_in_unsandboxed_child_processes.patch
fix_add_more_checks_in_mojocdmservice.patch
recreate_directmanipulationhelper_when_every_lrwhh_updateparent.patch
notify_directmanipulationeventhandler_when_directmanipulationhelper.patch
build_fix_when_building_with_enable_plugins_false.patch
mojo-js_change_how_js_sends_mojo_messages.patch

View File

@@ -6,7 +6,7 @@ Subject: allow new privileges in unsandboxed child processes
This allows unsandboxed renderers to launch setuid processes on Linux.
diff --git a/content/browser/child_process_launcher_helper_linux.cc b/content/browser/child_process_launcher_helper_linux.cc
index 720b92a1a3a7ab5512f839005b272e4989d2ac65..b1759109627cd00053489dcdd397e942fa9d289f 100644
index 5b82388932eb845a0cf932ee2b04ece51c474d1c..6cfa4c8f34fe382fe5db6cd31dd56967a45f3bf4 100644
--- a/content/browser/child_process_launcher_helper_linux.cc
+++ b/content/browser/child_process_launcher_helper_linux.cc
@@ -54,6 +54,18 @@ bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(

View File

@@ -0,0 +1,74 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: deepak1556 <hop2deep@gmail.com>
Date: Tue, 8 Oct 2019 15:40:50 +0000
Subject: build: fix when building with enable_plugins=false
Bug: none
Change-Id: If878b3a7f5bb051c6e99c617418475c12754ae90
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1845624
Reviewed-by: Robert Sesek <rsesek@chromium.org>
Commit-Queue: Robert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#703739}
diff --git a/AUTHORS b/AUTHORS
index 32fc92e6113c6324cc0b09b7a7ff309e2c89ddda..ae51b1d483564c96cb98b603280ac510f90c36c1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -219,6 +219,7 @@ Debashish Samantaray <d.samantaray@samsung.com>
Debug Wang <debugwang@tencent.com>
Deepak Dilip Borade <deepak.db@samsung.com>
Deepak Mittal <deepak.m1@samsung.com>
+Deepak Mohan <hop2deep@gmail.com>
Deepak Sharma <deepak.sharma@amd.com>
Deepak Singla <deepak.s@samsung.com>
Deokjin Kim <deokjin81.kim@samsung.com>
diff --git a/content/browser/sandbox_parameters_mac.mm b/content/browser/sandbox_parameters_mac.mm
index b4d539bab49d468e0d2bdade76aad3dba0facfc4..73905a6eac811f9bff04e1b0ceb47b3ea326ee62 100644
--- a/content/browser/sandbox_parameters_mac.mm
+++ b/content/browser/sandbox_parameters_mac.mm
@@ -21,12 +21,16 @@
#include "content/public/browser/plugin_service.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
-#include "content/public/common/pepper_plugin_info.h"
+#include "ppapi/buildflags/buildflags.h"
#include "sandbox/mac/seatbelt_exec.h"
#include "services/service_manager/sandbox/mac/sandbox_mac.h"
#include "services/service_manager/sandbox/sandbox_type.h"
#include "services/service_manager/sandbox/switches.h"
+#if BUILDFLAG(ENABLE_PLUGINS)
+#include "content/public/common/pepper_plugin_info.h"
+#endif
+
namespace content {
namespace {
@@ -125,6 +129,7 @@ void SetupNetworkSandboxParameters(sandbox::SeatbeltExecClient* client) {
}
}
+#if BUILDFLAG(ENABLE_PLUGINS)
void SetupPPAPISandboxParameters(sandbox::SeatbeltExecClient* client) {
SetupCommonSandboxParameters(client);
@@ -149,6 +154,7 @@ void SetupPPAPISandboxParameters(sandbox::SeatbeltExecClient* client) {
// to n+1 more than the plugins added.
CHECK(index <= 5);
}
+#endif
void SetupCDMSandboxParameters(sandbox::SeatbeltExecClient* client) {
SetupCommonSandboxParameters(client);
@@ -186,9 +192,11 @@ void SetupSandboxParameters(service_manager::SandboxType sandbox_type,
case service_manager::SANDBOX_TYPE_NETWORK:
SetupNetworkSandboxParameters(client);
break;
+#if BUILDFLAG(ENABLE_PLUGINS)
case service_manager::SANDBOX_TYPE_PPAPI:
SetupPPAPISandboxParameters(client);
break;
+#endif
case service_manager::SANDBOX_TYPE_PROFILING:
case service_manager::SANDBOX_TYPE_UTILITY:
SetupUtilitySandboxParameters(client, command_line);

View File

@@ -18,6 +18,23 @@ This can be removed once web content (including WebGL) learn how
to deal with color spaces. That is being tracked at
https://crbug.com/634542 and https://crbug.com/711107.
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index e2921219d253dbb198bd089811cc102aeec594cc..62290a30dee8d51df6e1f771894832b4105c2dd3 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -1625,6 +1625,12 @@ const gfx::ColorSpace& LayerTreeHostImpl::GetRasterColorSpace() const {
const gfx::ColorSpace& LayerTreeHostImpl::GetRasterColorSpaceAndId(
int* id) const {
+ if (!settings_.enable_color_correct_rendering) {
+ static gfx::ColorSpace invalid_color_space;
+ *id = -1;
+ return invalid_color_space;
+ }
+
const gfx::ColorSpace* result = nullptr;
// The pending tree will have the most recently updated color space, so
// prefer that.
diff --git a/cc/trees/layer_tree_settings.h b/cc/trees/layer_tree_settings.h
index 092fb1b7ea3626b7649472c35ec20bf1a6aaf4cd..fed8dd322faba387ebd0508228f847766a76011e 100644
--- a/cc/trees/layer_tree_settings.h
@@ -248,6 +265,42 @@ index a450561913e274b36ece94c22bae7b14930db36e..3b9a0a5cef0ec2fc02d4e2d1b0e4e1da
// Checkerimaging is not supported for synchronous single-threaded mode, which
// is what the renderer uses if its not threaded.
settings.enable_checker_imaging =
diff --git a/third_party/blink/renderer/platform/graphics/canvas_color_params.cc b/third_party/blink/renderer/platform/graphics/canvas_color_params.cc
index 9e5b58dc78b05491d1154b25b5b9c8c55b883b07..8524fd9f090fbb5cb0e37b1a6205d77917e9f221 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_color_params.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_color_params.cc
@@ -11,6 +11,7 @@
#include "third_party/khronos/GLES3/gl3.h"
#include "third_party/skia/include/core/SkSurfaceProps.h"
#include "ui/gfx/color_space.h"
+#include "ui/gfx/switches.h"
namespace blink {
@@ -81,6 +82,11 @@ uint8_t CanvasColorParams::BytesPerPixel() const {
}
gfx::ColorSpace CanvasColorParams::GetSamplerGfxColorSpace() const {
+ auto* cmd_line = base::CommandLine::ForCurrentProcess();
+ if (cmd_line->HasSwitch(switches::kDisableColorCorrectRendering)) {
+ return gfx::ColorSpace();
+ }
+
gfx::ColorSpace::PrimaryID primary_id = GetPrimaryID(color_space_);
// TODO(ccameron): This needs to take into account whether or not this texture
@@ -94,6 +100,11 @@ gfx::ColorSpace CanvasColorParams::GetSamplerGfxColorSpace() const {
}
gfx::ColorSpace CanvasColorParams::GetStorageGfxColorSpace() const {
+ auto* cmd_line = base::CommandLine::ForCurrentProcess();
+ if (cmd_line->HasSwitch(switches::kDisableColorCorrectRendering)) {
+ return gfx::ColorSpace();
+ }
+
gfx::ColorSpace::PrimaryID primary_id = GetPrimaryID(color_space_);
gfx::ColorSpace::TransferID transfer_id =
diff --git a/ui/gfx/mac/io_surface.cc b/ui/gfx/mac/io_surface.cc
index 88ec94963569588ed2882193a28197879dcb1090..eae37577bc9b1872c0162f55de218553eed61587 100644
--- a/ui/gfx/mac/io_surface.cc

View File

@@ -0,0 +1,43 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Pedro Pontes <pepontes@microsoft.com>
Date: Thu, 12 Sep 2019 16:28:47 +0200
Subject: fix: Add more checks in MojoCdmService.
Applies https://chromium.googlesource.com/chromium/src.git/+/b7b305f3389017cc42e2cfac6e7a319f42d5bde3%5E%21/
diff --git a/media/mojo/services/mojo_cdm_service.cc b/media/mojo/services/mojo_cdm_service.cc
index 1ccfd2f05a7c56d6a867bbcc7b04aed948b73b15..a3f8332768b2e1c6375b5f643acb197a55521f83 100644
--- a/media/mojo/services/mojo_cdm_service.cc
+++ b/media/mojo/services/mojo_cdm_service.cc
@@ -63,7 +63,9 @@ void MojoCdmService::Initialize(const std::string& key_system,
const CdmConfig& cdm_config,
InitializeCallback callback) {
DVLOG(1) << __func__ << ": " << key_system;
- DCHECK(!cdm_);
+
+ CHECK(!has_initialize_been_called_) << "Initialize should only happen once";
+ has_initialize_been_called_ = true;
auto weak_this = weak_factory_.GetWeakPtr();
cdm_factory_->Create(
@@ -157,6 +159,7 @@ void MojoCdmService::OnCdmCreated(
return;
}
+ CHECK(!cdm_) << "CDM should only be created once.";
cdm_ = cdm;
if (context_) {
diff --git a/media/mojo/services/mojo_cdm_service.h b/media/mojo/services/mojo_cdm_service.h
index fd265467859f86c97f16f6abb2cef19ad5b3e139..1e575474f5bcde434af1f55c0361a30e42ac6f29 100644
--- a/media/mojo/services/mojo_cdm_service.h
+++ b/media/mojo/services/mojo_cdm_service.h
@@ -101,6 +101,8 @@ class MEDIA_MOJO_EXPORT MojoCdmService : public mojom::ContentDecryptionModule {
// Callback for when |decryptor_| loses connectivity.
void OnDecryptorConnectionError();
+ bool has_initialize_been_called_ = false;
+
CdmFactory* cdm_factory_;
MojoCdmServiceContext* const context_ = nullptr;
scoped_refptr<::media::ContentDecryptionModule> cdm_;

View File

@@ -5,16 +5,21 @@ Subject: fix: disabling compositor recycling
Compositor recycling is useful for Chrome because there can be many tabs and spinning up a compositor for each one would be costly. In practice, Chrome uses the parent compositor code path of browser_compositor_view_mac.mm; the NSView of each tab is detached when it's hidden and attached when it's shown. For Electron, there is no parent compositor, so we're forced into the "own compositor" code path, which seems to be non-optimal and pretty ruthless in terms of the release of resources. Electron has no real concept of multiple tabs per window, so it should be okay to disable this ruthless recycling altogether in Electron.
diff --git a/content/browser/renderer_host/browser_compositor_view_mac.mm b/content/browser/renderer_host/browser_compositor_view_mac.mm
index 59e58d693c971742951434f6582140d9179235f2..135e7a384a560f55e5201f108fe1ac2db621fbca 100644
--- a/content/browser/renderer_host/browser_compositor_view_mac.mm
+++ b/content/browser/renderer_host/browser_compositor_view_mac.mm
@@ -209,7 +209,7 @@
}
void BrowserCompositorMac::SetRenderWidgetHostIsHidden(bool hidden) {
- render_widget_host_is_hidden_ = hidden;
+ render_widget_host_is_hidden_ = false;
UpdateState();
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index a1138408c0985efc1334c9a92f7e875680b69a4a..913c1c7cb541fd0f6f400beafbe9796b9cfcda96 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -483,7 +483,12 @@
void RenderWidgetHostViewMac::WasOccluded() {
host()->WasHidden();
- browser_compositor_->SetRenderWidgetHostIsHidden(true);
+
+ // Consider the RWHV occluded only if it is not attached to a window
+ // (e.g. unattached BrowserView). Otherwise we treat it as visible to
+ // prevent unnecessary compositor recycling.
+ const bool unattached = ![cocoa_view() window];
+ browser_compositor_->SetRenderWidgetHostIsHidden(unattached);
}
void RenderWidgetHostViewMac::SetSize(const gfx::Size& size) {

View File

@@ -0,0 +1,248 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ken Rockot <rockot@google.com>
Date: Thu, 20 Jun 2019 01:37:22 +0000
Subject: [mojo-js] Change how JS sends Mojo messages
This changes how Mojo JS Blink bindings transmit messages produced from
JS code. Namely, instead of blindly stuffing the JS-provided message
payload into the pipe, we construct a C++ bindings Message object from
the data.
This Message object is then immediately pushed into a message pipe
without incurring any additional copies.
The change is effectively a no-op in terms of functional behavior, but
this makes it easier for us to manipulate bits on the Message (such as
header flags) from native code before actually sending it out.
Bug: 976506
Change-Id: Iee08fb1abb160888bcbb433bec071ae322d30161
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1666271
Commit-Queue: Ken Rockot <rockot@google.com>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#670762}
diff --git a/mojo/public/cpp/bindings/lib/message.cc b/mojo/public/cpp/bindings/lib/message.cc
index 29b4d3cc38587c9ca3106818cd7463460e1bf35f..87965c8aa0cff1f71c0b1031586b453cce0d309a 100644
--- a/mojo/public/cpp/bindings/lib/message.cc
+++ b/mojo/public/cpp/bindings/lib/message.cc
@@ -243,6 +243,35 @@ Message::Message(uint32_t name,
serialized_ = true;
}
+Message::Message(base::span<const uint8_t> payload,
+ base::span<ScopedHandle> handles) {
+ MojoResult rv = mojo::CreateMessage(&handle_);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ DCHECK(handle_.is_valid());
+
+ void* buffer;
+ uint32_t buffer_size;
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(payload.size()));
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(handles.size()));
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ rv = MojoAppendMessageData(
+ handle_->value(), static_cast<uint32_t>(payload.size()),
+ reinterpret_cast<MojoHandle*>(handles.data()),
+ static_cast<uint32_t>(handles.size()), &options, &buffer, &buffer_size);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ // Handle ownership has been taken by MojoAppendMessageData.
+ for (auto& handle : handles)
+ ignore_result(handle.release());
+
+ payload_buffer_ = internal::Buffer(buffer, payload.size(), payload.size());
+ std::copy(payload.begin(), payload.end(),
+ static_cast<uint8_t*>(payload_buffer_.data()));
+ transferable_ = true;
+ serialized_ = true;
+}
+
// static
Message Message::CreateFromMessageHandle(ScopedMessageHandle* message_handle) {
DCHECK(message_handle);
diff --git a/mojo/public/cpp/bindings/message.h b/mojo/public/cpp/bindings/message.h
index 4ff8a6b5e63e1c1e27b299cf292ebcf9d0e9981c..3be96dea09a8c96f4961edd122115d01bf8adaeb 100644
--- a/mojo/public/cpp/bindings/message.h
+++ b/mojo/public/cpp/bindings/message.h
@@ -16,6 +16,7 @@
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/component_export.h"
+#include "base/containers/span.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "mojo/public/cpp/bindings/lib/buffer.h"
@@ -68,6 +69,14 @@ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) Message {
size_t payload_interface_id_count,
std::vector<ScopedHandle>* handles);
+ // Constructs a new serialized Message object from a fully populated message
+ // payload (including a well-formed message header) and an optional set of
+ // handle attachments. This Message may not be extended with additional
+ // payload or handles once constructed, but its payload remains mutable as
+ // long as the Message is not moved and neither |Reset()| nor
+ // |TakeMojoMessage()| is called.
+ Message(base::span<const uint8_t> payload, base::span<ScopedHandle> handles);
+
// Constructs a new serialized Message object from an existing
// ScopedMessageHandle; e.g., one read from a message pipe.
//
diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn
index 73c5ba421b97904f8b5a4001f1166dd3e73751b2..21b0a185dbb6019fdd4220663d5d71d49f7010cf 100644
--- a/mojo/public/cpp/bindings/tests/BUILD.gn
+++ b/mojo/public/cpp/bindings/tests/BUILD.gn
@@ -24,6 +24,7 @@ source_set("tests") {
"map_unittest.cc",
"message_queue.cc",
"message_queue.h",
+ "message_unittest.cc",
"multiplex_router_unittest.cc",
"native_struct_unittest.cc",
"new_endpoint_types_unittest.cc",
diff --git a/mojo/public/cpp/bindings/tests/message_unittest.cc b/mojo/public/cpp/bindings/tests/message_unittest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6c68fdf634e40ca8b485d67ed477d405eaf91c52
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/message_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+namespace {
+
+constexpr int32_t kTestMessageName = 42;
+constexpr int32_t kTestMessageFlags = 7;
+constexpr uint32_t kTestPayloadSize = 32;
+
+void CreateTestMessagePayload(std::vector<uint8_t>* bytes,
+ std::vector<ScopedHandle>* handles) {
+ Message message(kTestMessageName, kTestMessageFlags, 0, kTestPayloadSize,
+ nullptr);
+ message.header()->trace_id = 0;
+ bytes->resize(message.data_num_bytes());
+ std::copy(message.data(), message.data() + message.data_num_bytes(),
+ bytes->begin());
+
+ MessagePipe pipe;
+ handles->resize(2);
+ handles->at(0) = ScopedHandle(std::move(pipe.handle0));
+ handles->at(1) = ScopedHandle(std::move(pipe.handle1));
+}
+
+TEST(BindingsMessageTest, ConstructFromPayload) {
+ // Verifies that Message objects constructed directly from a raw payload look
+ // the same on the wire as raw messages constructed with lower level APIs.
+ MessagePipe pipe;
+
+ // First feed the raw message data directly into the pipe.
+ std::vector<uint8_t> in_bytes1;
+ std::vector<ScopedHandle> in_handles1;
+ CreateTestMessagePayload(&in_bytes1, &in_handles1);
+ WriteMessageRaw(pipe.handle0.get(), in_bytes1.data(), in_bytes1.size(),
+ reinterpret_cast<const MojoHandle*>(in_handles1.data()),
+ in_handles1.size(), MOJO_WRITE_MESSAGE_FLAG_NONE);
+ for (auto& handle : in_handles1)
+ ignore_result(handle.release());
+
+ // Now construct a Message object from the same payload and feed that into the
+ // pipe.
+ std::vector<uint8_t> in_bytes2;
+ std::vector<ScopedHandle> in_handles2;
+ CreateTestMessagePayload(&in_bytes2, &in_handles2);
+ Message message(in_bytes2, in_handles2);
+ WriteMessageNew(pipe.handle0.get(), message.TakeMojoMessage(),
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+
+ // Now read both messages and ensure that they're identical.
+ // NOTE: The handles themselves cannot be identical, but the same number of
+ // handles should be attached.
+ std::vector<uint8_t> out_bytes1;
+ std::vector<ScopedHandle> out_handles1;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(pipe.handle1.get(), &out_bytes1, &out_handles1,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ std::vector<uint8_t> out_bytes2;
+ std::vector<ScopedHandle> out_handles2;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(pipe.handle1.get(), &out_bytes2, &out_handles2,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+
+ EXPECT_EQ(out_bytes1, out_bytes2);
+ EXPECT_EQ(out_handles1.size(), out_handles2.size());
+}
+
+} // namespace
+} // namespace test
+} // namespace mojo
diff --git a/third_party/blink/renderer/core/mojo/DEPS b/third_party/blink/renderer/core/mojo/DEPS
index a3ad46964e418e4b11347842cf876678ebe3ff0b..a3bebdc81a825b87c4419f220d46dd34a247ff75 100644
--- a/third_party/blink/renderer/core/mojo/DEPS
+++ b/third_party/blink/renderer/core/mojo/DEPS
@@ -1,3 +1,5 @@
include_rules = [
- "+mojo/public/cpp/system",
+ "+base/numerics/safe_math.h",
+
+ "+mojo/public",
]
diff --git a/third_party/blink/renderer/core/mojo/mojo_handle.cc b/third_party/blink/renderer/core/mojo/mojo_handle.cc
index b74bf25779e2f6f72312ddd44ac62cc7b18f23c2..2951ad1b39b09a09f929e10ae8e5634eaea11587 100644
--- a/third_party/blink/renderer/core/mojo/mojo_handle.cc
+++ b/third_party/blink/renderer/core/mojo/mojo_handle.cc
@@ -4,6 +4,9 @@
#include "third_party/blink/renderer/core/mojo/mojo_handle.h"
+#include "base/numerics/safe_math.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "third_party/blink/renderer/bindings/core/v8/array_buffer_or_array_buffer_view.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -50,11 +53,10 @@ MojoWatcher* MojoHandle::watch(ScriptState* script_state,
MojoResult MojoHandle::writeMessage(
ArrayBufferOrArrayBufferView& buffer,
const HeapVector<Member<MojoHandle>>& handles) {
- // mojo::WriteMessageRaw takes ownership of the handles, so release them here.
- Vector<::MojoHandle, kHandleVectorInlineCapacity> raw_handles(handles.size());
- std::transform(
- handles.begin(), handles.end(), raw_handles.begin(),
- [](MojoHandle* handle) { return handle->handle_.release().value(); });
+ Vector<mojo::ScopedHandle, kHandleVectorInlineCapacity> scoped_handles(
+ handles.size());
+ std::transform(handles.begin(), handles.end(), scoped_handles.begin(),
+ [](MojoHandle* handle) { return std::move(handle->handle_); });
const void* bytes = nullptr;
size_t num_bytes = 0;
@@ -68,9 +70,13 @@ MojoResult MojoHandle::writeMessage(
num_bytes = view->byteLength();
}
- return mojo::WriteMessageRaw(
- mojo::MessagePipeHandle(handle_.get().value()), bytes, num_bytes,
- raw_handles.data(), raw_handles.size(), MOJO_WRITE_MESSAGE_FLAG_NONE);
+ auto message = mojo::Message(
+ base::make_span(static_cast<const uint8_t*>(bytes), num_bytes),
+ base::make_span(scoped_handles));
+ DCHECK(!message.IsNull());
+ return mojo::WriteMessageNew(mojo::MessagePipeHandle(handle_.get().value()),
+ message.TakeMojoMessage(),
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
}
MojoReadMessageResult* MojoHandle::readMessage(

View File

@@ -0,0 +1,118 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Siye Liu <siliu@microsoft.com>
Date: Thu, 15 Aug 2019 02:30:05 +0000
Subject: Notify DirectManipulationEventHandler when DirectManipulationHelper
is destructed.
The crash report shows that DirectManipulation may call
|DirectManipulationEventHandler::OnInteraction| after
DirectManipulationHelper is destroyed. Since |OnInteraction| is relying
on DirectManipulationHelper to add/remove animation observer, we should
set the pointer to DirectManipulationHelper to nullptr after it is
destroyed.
In this CL, we set the pointer to DirectManipulationHelper in separate
function |SetDirectManipulationHelper| instead of passing the pointer
during ctor of DirectManipulationEventHandler.
Bug: 993260
Change-Id: Id781af047e72268532d861920a077a0c6b1650bb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1753661
Reviewed-by: Scott Violet <sky@chromium.org>
Commit-Queue: Siye Liu <siliu@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#687125}
diff --git a/content/browser/renderer_host/direct_manipulation_event_handler_win.cc b/content/browser/renderer_host/direct_manipulation_event_handler_win.cc
index 33ce63d8d0f59573cb4764f146b4f88008cca4a8..bae879411fb253a810034eb2cb531a54530a4183 100644
--- a/content/browser/renderer_host/direct_manipulation_event_handler_win.cc
+++ b/content/browser/renderer_host/direct_manipulation_event_handler_win.cc
@@ -28,9 +28,8 @@ bool FloatEquals(float f1, float f2) {
} // namespace
DirectManipulationEventHandler::DirectManipulationEventHandler(
- DirectManipulationHelper* helper,
ui::WindowEventTarget* event_target)
- : helper_(helper), event_target_(event_target) {}
+ : event_target_(event_target) {}
bool DirectManipulationEventHandler::SetViewportSizeInPixels(
const gfx::Size& viewport_size_in_pixels) {
@@ -45,6 +44,11 @@ void DirectManipulationEventHandler::SetDeviceScaleFactor(
device_scale_factor_ = device_scale_factor;
}
+void DirectManipulationEventHandler::SetDirectManipulationHelper(
+ DirectManipulationHelper* helper) {
+ helper_ = helper;
+}
+
DirectManipulationEventHandler::~DirectManipulationEventHandler() {}
void DirectManipulationEventHandler::TransitionToState(
@@ -303,6 +307,9 @@ HRESULT DirectManipulationEventHandler::OnContentUpdated(
HRESULT DirectManipulationEventHandler::OnInteraction(
IDirectManipulationViewport2* viewport,
DIRECTMANIPULATION_INTERACTION_TYPE interaction) {
+ if (!helper_)
+ return S_OK;
+
if (interaction == DIRECTMANIPULATION_INTERACTION_BEGIN) {
DebugLogging("OnInteraction BEGIN.", S_OK);
helper_->AddAnimationObserver();
diff --git a/content/browser/renderer_host/direct_manipulation_event_handler_win.h b/content/browser/renderer_host/direct_manipulation_event_handler_win.h
index f1902085032ffc95edb2d8dcd5224f1c5ecda3d2..e654c5f1a45da9e054d2c367df6f5115fa25862c 100644
--- a/content/browser/renderer_host/direct_manipulation_event_handler_win.h
+++ b/content/browser/renderer_host/direct_manipulation_event_handler_win.h
@@ -38,14 +38,15 @@ class DirectManipulationEventHandler
IDirectManipulationViewportEventHandler,
IDirectManipulationInteractionEventHandler>> {
public:
- DirectManipulationEventHandler(DirectManipulationHelper* helper,
- ui::WindowEventTarget* event_target);
+ DirectManipulationEventHandler(ui::WindowEventTarget* event_target);
// Return true if viewport_size_in_pixels_ changed.
bool SetViewportSizeInPixels(const gfx::Size& viewport_size_in_pixels);
void SetDeviceScaleFactor(float device_scale_factor);
+ void SetDirectManipulationHelper(DirectManipulationHelper* helper);
+
private:
friend class DirectManipulationBrowserTest;
friend DirectManipulationUnitTest;
diff --git a/content/browser/renderer_host/direct_manipulation_helper_win.cc b/content/browser/renderer_host/direct_manipulation_helper_win.cc
index 9401e7f8fb1fafd0532bb1e86035701c1df2ffda..6ce09b9f7b80e94c2adb582954c90afc95fc20e4 100644
--- a/content/browser/renderer_host/direct_manipulation_helper_win.cc
+++ b/content/browser/renderer_host/direct_manipulation_helper_win.cc
@@ -78,8 +78,9 @@ DirectManipulationHelper::CreateInstanceForTesting(
base::WrapUnique(new DirectManipulationHelper(0, nullptr));
instance->event_handler_ =
- Microsoft::WRL::Make<DirectManipulationEventHandler>(instance.get(),
- event_target);
+ Microsoft::WRL::Make<DirectManipulationEventHandler>(event_target);
+
+ instance->event_handler_->SetDirectManipulationHelper(instance.get());
instance->viewport_ = viewport;
@@ -159,7 +160,9 @@ bool DirectManipulationHelper::Initialize(ui::WindowEventTarget* event_target) {
}
event_handler_ =
- Microsoft::WRL::Make<DirectManipulationEventHandler>(this, event_target);
+ Microsoft::WRL::Make<DirectManipulationEventHandler>(event_target);
+
+ event_handler_->SetDirectManipulationHelper(this);
// We got Direct Manipulation transform from
// IDirectManipulationViewportEventHandler.
@@ -265,6 +268,7 @@ void DirectManipulationHelper::Destroy() {
if (has_animation_observer_)
RemoveAnimationObserver();
compositor_ = nullptr;
+ event_handler_->SetDirectManipulationHelper(nullptr);
HRESULT hr;
if (viewport_) {

View File

@@ -0,0 +1,975 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Siye Liu <siliu@microsoft.com>
Date: Mon, 12 Aug 2019 19:26:49 +0000
Subject: Recreate DirectManipulationHelper when every LRWHH UpdateParent
This is a reland of 7da070704275ac67c95a89dd373a1dc0a1ba1256 and
d77c5029a13a83e676833aea61966a003564e57a
Compositor and window event target is associated with window's parent.
We call LRWHH UpdateParent when window's parent update, includes
window's parent actually update and window initialize. Recreate
DirectManipulationHelper on every window's parent update helps keep
DirectManipulationHelper lifecycle tracking simpler. We also make
CompositorAnimationObserver owned by DirectManipulationHelper.
With this changes, we start the DirectManipulation event polling when
DirectManipulationHelper created and stop when it destroyed. The issue
should be fix since event polling start no more depends on
DM_POINTERHITTEST.
This CL also includes 3 refactoring changes:
1. Move CompositorAnimationObserver into DirectManipulationHelper.
2. Call ZoomToRect to reset viewport of DirectManipulation when
viewport is actually transformed in RUNNING - READAY sequence.
3. Pass the viewport size to DMEventHandler and use it to reset viewport
at gesture end.
The original changes caused a regression that browser UI composition
keeps ticking begin frames. We register DirectManipulationHelperWin
as an AnimationObserver of ui::Compositor. UI compositor will ask for
begin frames as long as it has an AnimationObserver. We should call
OnCompositorShuttingDown() when the compositor is going Idle. Therefore,
I added a IDirectManipulationInteractionEventHandler that adds the
observer when a manipulation begins, and removes it when manipulation
ends. After my fix, we start the DirectManipulation event polling when
DirectManipulation interaction begin and stop when DirectManipulation
interaction end.
Bug: 914914
Change-Id: I9f59381bdcc6e4ed0970003d87b26ac750bfb42d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1689922
Reviewed-by: Scott Violet <sky@chromium.org>
Commit-Queue: Siye Liu <siliu@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#686111}
diff --git a/content/browser/renderer_host/direct_manipulation_event_handler_win.cc b/content/browser/renderer_host/direct_manipulation_event_handler_win.cc
index eec54fcb0187cfd47286d51226057b2fc4b3204a..33ce63d8d0f59573cb4764f146b4f88008cca4a8 100644
--- a/content/browser/renderer_host/direct_manipulation_event_handler_win.cc
+++ b/content/browser/renderer_host/direct_manipulation_event_handler_win.cc
@@ -28,19 +28,16 @@ bool FloatEquals(float f1, float f2) {
} // namespace
DirectManipulationEventHandler::DirectManipulationEventHandler(
- DirectManipulationHelper* helper)
- : helper_(helper) {}
-
-void DirectManipulationEventHandler::SetWindowEventTarget(
- ui::WindowEventTarget* event_target) {
- if (!event_target && LoggingEnabled()) {
- DebugLogging("Event target is null.", S_OK);
- if (event_target_)
- DebugLogging("Previous event target is not null", S_OK);
- else
- DebugLogging("Previous event target is null", S_OK);
- }
- event_target_ = event_target;
+ DirectManipulationHelper* helper,
+ ui::WindowEventTarget* event_target)
+ : helper_(helper), event_target_(event_target) {}
+
+bool DirectManipulationEventHandler::SetViewportSizeInPixels(
+ const gfx::Size& viewport_size_in_pixels) {
+ if (viewport_size_in_pixels_ == viewport_size_in_pixels)
+ return false;
+ viewport_size_in_pixels_ = viewport_size_in_pixels;
+ return true;
}
void DirectManipulationEventHandler::SetDeviceScaleFactor(
@@ -175,19 +172,28 @@ HRESULT DirectManipulationEventHandler::OnViewportStatusChanged(
if (current != DIRECTMANIPULATION_READY)
return S_OK;
- // Reset the viewport when we're idle, so the content transforms always start
- // at identity.
- // Every animation will receive 2 ready message, we should stop request
- // compositor animation at the second ready.
- first_ready_ = !first_ready_;
- HRESULT hr = helper_->Reset(first_ready_);
+ // Normally gesture sequence will receive 2 READY message, the first one is
+ // gesture end, the second one is from viewport reset. We don't have content
+ // transform in the second RUNNING -> READY. We should not reset on an empty
+ // RUNNING -> READY sequence.
+ if (last_scale_ != 1.0f || last_x_offset_ != 0 || last_y_offset_ != 0) {
+ HRESULT hr = viewport->ZoomToRect(
+ static_cast<float>(0), static_cast<float>(0),
+ static_cast<float>(viewport_size_in_pixels_.width()),
+ static_cast<float>(viewport_size_in_pixels_.height()), FALSE);
+ if (!SUCCEEDED(hr)) {
+ DebugLogging("Viewport zoom to rect failed.", hr);
+ return hr;
+ }
+ }
+
last_scale_ = 1.0f;
last_x_offset_ = 0.0f;
last_y_offset_ = 0.0f;
TransitionToState(GestureState::kNone);
- return hr;
+ return S_OK;
}
HRESULT DirectManipulationEventHandler::OnViewportUpdated(
@@ -294,4 +300,18 @@ HRESULT DirectManipulationEventHandler::OnContentUpdated(
return hr;
}
+HRESULT DirectManipulationEventHandler::OnInteraction(
+ IDirectManipulationViewport2* viewport,
+ DIRECTMANIPULATION_INTERACTION_TYPE interaction) {
+ if (interaction == DIRECTMANIPULATION_INTERACTION_BEGIN) {
+ DebugLogging("OnInteraction BEGIN.", S_OK);
+ helper_->AddAnimationObserver();
+ } else if (interaction == DIRECTMANIPULATION_INTERACTION_END) {
+ DebugLogging("OnInteraction END.", S_OK);
+ helper_->RemoveAnimationObserver();
+ }
+
+ return S_OK;
+}
+
} // namespace content
diff --git a/content/browser/renderer_host/direct_manipulation_event_handler_win.h b/content/browser/renderer_host/direct_manipulation_event_handler_win.h
index 270e85a09feb0add5b62afe3d9234627070ffe00..f1902085032ffc95edb2d8dcd5224f1c5ecda3d2 100644
--- a/content/browser/renderer_host/direct_manipulation_event_handler_win.h
+++ b/content/browser/renderer_host/direct_manipulation_event_handler_win.h
@@ -11,6 +11,7 @@
#include <wrl.h>
#include "base/macros.h"
+#include "ui/gfx/geometry/size.h"
namespace ui {
@@ -21,6 +22,7 @@ class WindowEventTarget;
namespace content {
class DirectManipulationHelper;
+class DirectManipulationBrowserTest;
class DirectManipulationUnitTest;
// DirectManipulationEventHandler receives status update and gesture events from
@@ -33,17 +35,19 @@ class DirectManipulationEventHandler
Microsoft::WRL::RuntimeClassFlags<
Microsoft::WRL::RuntimeClassType::ClassicCom>,
Microsoft::WRL::FtmBase,
- IDirectManipulationViewportEventHandler>> {
+ IDirectManipulationViewportEventHandler,
+ IDirectManipulationInteractionEventHandler>> {
public:
- explicit DirectManipulationEventHandler(DirectManipulationHelper* helper);
+ DirectManipulationEventHandler(DirectManipulationHelper* helper,
+ ui::WindowEventTarget* event_target);
- // WindowEventTarget updates for every DM_POINTERHITTEST in case window
- // hierarchy changed.
- void SetWindowEventTarget(ui::WindowEventTarget* event_target);
+ // Return true if viewport_size_in_pixels_ changed.
+ bool SetViewportSizeInPixels(const gfx::Size& viewport_size_in_pixels);
void SetDeviceScaleFactor(float device_scale_factor);
private:
+ friend class DirectManipulationBrowserTest;
friend DirectManipulationUnitTest;
// DirectManipulationEventHandler();
@@ -65,18 +69,23 @@ class DirectManipulationEventHandler
OnContentUpdated(_In_ IDirectManipulationViewport* viewport,
_In_ IDirectManipulationContent* content) override;
+ HRESULT STDMETHODCALLTYPE
+ OnInteraction(_In_ IDirectManipulationViewport2* viewport,
+ _In_ DIRECTMANIPULATION_INTERACTION_TYPE interaction) override;
+
DirectManipulationHelper* helper_ = nullptr;
ui::WindowEventTarget* event_target_ = nullptr;
float device_scale_factor_ = 1.0f;
float last_scale_ = 1.0f;
int last_x_offset_ = 0;
int last_y_offset_ = 0;
- bool first_ready_ = false;
bool should_send_scroll_begin_ = false;
// Current recognized gesture from Direct Manipulation.
GestureState gesture_state_ = GestureState::kNone;
+ gfx::Size viewport_size_in_pixels_;
+
DISALLOW_COPY_AND_ASSIGN(DirectManipulationEventHandler);
};
diff --git a/content/browser/renderer_host/direct_manipulation_helper_win.cc b/content/browser/renderer_host/direct_manipulation_helper_win.cc
index cc62fd688711153e2930fe06550c219e4cc01173..9401e7f8fb1fafd0532bb1e86035701c1df2ffda 100644
--- a/content/browser/renderer_host/direct_manipulation_helper_win.cc
+++ b/content/browser/renderer_host/direct_manipulation_helper_win.cc
@@ -14,6 +14,8 @@
#include "base/win/windows_version.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/win/window_event_target.h"
+#include "ui/compositor/compositor.h"
+#include "ui/compositor/compositor_animation_observer.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/rect.h"
@@ -39,8 +41,9 @@ void DebugLogging(const std::string& s, HRESULT hr) {
// static
std::unique_ptr<DirectManipulationHelper>
DirectManipulationHelper::CreateInstance(HWND window,
+ ui::Compositor* compositor,
ui::WindowEventTarget* event_target) {
- if (!::IsWindow(window))
+ if (!::IsWindow(window) || !compositor || !event_target)
return nullptr;
if (!base::FeatureList::IsEnabled(features::kPrecisionTouchpad))
@@ -51,8 +54,7 @@ DirectManipulationHelper::CreateInstance(HWND window,
return nullptr;
std::unique_ptr<DirectManipulationHelper> instance =
- base::WrapUnique(new DirectManipulationHelper());
- instance->window_ = window;
+ base::WrapUnique(new DirectManipulationHelper(window, compositor));
if (instance->Initialize(event_target))
return instance;
@@ -73,11 +75,11 @@ DirectManipulationHelper::CreateInstanceForTesting(
return nullptr;
std::unique_ptr<DirectManipulationHelper> instance =
- base::WrapUnique(new DirectManipulationHelper());
+ base::WrapUnique(new DirectManipulationHelper(0, nullptr));
instance->event_handler_ =
- Microsoft::WRL::Make<DirectManipulationEventHandler>(instance.get());
- instance->event_handler_->SetWindowEventTarget(event_target);
+ Microsoft::WRL::Make<DirectManipulationEventHandler>(instance.get(),
+ event_target);
instance->viewport_ = viewport;
@@ -85,11 +87,25 @@ DirectManipulationHelper::CreateInstanceForTesting(
}
DirectManipulationHelper::~DirectManipulationHelper() {
- if (viewport_)
- viewport_->Abandon();
+ Destroy();
}
-DirectManipulationHelper::DirectManipulationHelper() {}
+DirectManipulationHelper::DirectManipulationHelper(HWND window,
+ ui::Compositor* compositor)
+ : window_(window), compositor_(compositor) {}
+
+void DirectManipulationHelper::OnAnimationStep(base::TimeTicks timestamp) {
+ // Simulate 1 frame in update_manager_.
+ HRESULT hr = update_manager_->Update(nullptr);
+ if (!SUCCEEDED(hr))
+ DebugLogging("UpdateManager update failed.", hr);
+}
+
+void DirectManipulationHelper::OnCompositingShuttingDown(
+ ui::Compositor* compositor) {
+ DCHECK_EQ(compositor, compositor_);
+ Destroy();
+}
bool DirectManipulationHelper::Initialize(ui::WindowEventTarget* event_target) {
// IDirectManipulationUpdateManager is the first COM object created by the
@@ -142,8 +158,8 @@ bool DirectManipulationHelper::Initialize(ui::WindowEventTarget* event_target) {
return false;
}
- event_handler_ = Microsoft::WRL::Make<DirectManipulationEventHandler>(this);
- event_handler_->SetWindowEventTarget(event_target);
+ event_handler_ =
+ Microsoft::WRL::Make<DirectManipulationEventHandler>(this, event_target);
// We got Direct Manipulation transform from
// IDirectManipulationViewportEventHandler.
@@ -155,8 +171,9 @@ bool DirectManipulationHelper::Initialize(ui::WindowEventTarget* event_target) {
}
// Set default rect for viewport before activate.
- viewport_size_in_pixels_ = {1000, 1000};
- RECT rect = gfx::Rect(viewport_size_in_pixels_).ToRECT();
+ gfx::Size viewport_size_in_pixels = {1000, 1000};
+ event_handler_->SetViewportSizeInPixels(viewport_size_in_pixels);
+ RECT rect = gfx::Rect(viewport_size_in_pixels).ToRECT();
hr = viewport_->SetViewportRect(&rect);
if (!SUCCEEDED(hr)) {
DebugLogging("Viewport set rect failed.", hr);
@@ -185,33 +202,9 @@ bool DirectManipulationHelper::Initialize(ui::WindowEventTarget* event_target) {
return true;
}
-void DirectManipulationHelper::Activate() {
- HRESULT hr = viewport_->Stop();
- if (!SUCCEEDED(hr)) {
- DebugLogging("Viewport stop failed.", hr);
- return;
- }
-
- hr = manager_->Activate(window_);
- if (!SUCCEEDED(hr))
- DebugLogging("DirectManipulationManager activate failed.", hr);
-}
-
-void DirectManipulationHelper::Deactivate() {
- HRESULT hr = viewport_->Stop();
- if (!SUCCEEDED(hr)) {
- DebugLogging("Viewport stop failed.", hr);
- return;
- }
-
- hr = manager_->Deactivate(window_);
- if (!SUCCEEDED(hr))
- DebugLogging("DirectManipulationManager deactivate failed.", hr);
-}
-
void DirectManipulationHelper::SetSizeInPixels(
const gfx::Size& size_in_pixels) {
- if (viewport_size_in_pixels_ == size_in_pixels)
+ if (!event_handler_->SetViewportSizeInPixels(size_in_pixels))
return;
HRESULT hr = viewport_->Stop();
@@ -220,16 +213,13 @@ void DirectManipulationHelper::SetSizeInPixels(
return;
}
- viewport_size_in_pixels_ = size_in_pixels;
- RECT rect = gfx::Rect(viewport_size_in_pixels_).ToRECT();
+ RECT rect = gfx::Rect(size_in_pixels).ToRECT();
hr = viewport_->SetViewportRect(&rect);
if (!SUCCEEDED(hr))
DebugLogging("Viewport set rect failed.", hr);
}
-bool DirectManipulationHelper::OnPointerHitTest(
- WPARAM w_param,
- ui::WindowEventTarget* event_target) {
+void DirectManipulationHelper::OnPointerHitTest(WPARAM w_param) {
// Update the device scale factor.
event_handler_->SetDeviceScaleFactor(
display::win::ScreenWin::GetScaleFactorForHWND(window_));
@@ -240,53 +230,62 @@ bool DirectManipulationHelper::OnPointerHitTest(
// For WM_POINTER, the pointer type will show the event from mouse.
// For WM_POINTERACTIVATE, the pointer id will be different with the following
// message.
- event_handler_->SetWindowEventTarget(event_target);
-
using GetPointerTypeFn = BOOL(WINAPI*)(UINT32, POINTER_INPUT_TYPE*);
UINT32 pointer_id = GET_POINTERID_WPARAM(w_param);
POINTER_INPUT_TYPE pointer_type;
static const auto get_pointer_type = reinterpret_cast<GetPointerTypeFn>(
base::win::GetUser32FunctionPointer("GetPointerType"));
if (get_pointer_type && get_pointer_type(pointer_id, &pointer_type) &&
- pointer_type == PT_TOUCHPAD && event_target) {
+ pointer_type == PT_TOUCHPAD) {
HRESULT hr = viewport_->SetContact(pointer_id);
- if (!SUCCEEDED(hr)) {
+ if (!SUCCEEDED(hr))
DebugLogging("Viewport set contact failed.", hr);
- return false;
- }
-
- // Request begin frame for fake viewport.
- need_poll_events_ = true;
}
- return need_poll_events_;
}
-HRESULT DirectManipulationHelper::Reset(bool need_poll_events) {
- // By zooming the primary content to a rect that match the viewport rect, we
- // reset the content's transform to identity.
- HRESULT hr = viewport_->ZoomToRect(
- static_cast<float>(0), static_cast<float>(0),
- static_cast<float>(viewport_size_in_pixels_.width()),
- static_cast<float>(viewport_size_in_pixels_.height()), FALSE);
- if (!SUCCEEDED(hr)) {
- DebugLogging("Viewport zoom to rect failed.", hr);
- return hr;
- }
-
- need_poll_events_ = need_poll_events;
- return S_OK;
+void DirectManipulationHelper::AddAnimationObserver() {
+ DCHECK(compositor_);
+ compositor_->AddAnimationObserver(this);
+ has_animation_observer_ = true;
}
-bool DirectManipulationHelper::PollForNextEvent() {
- // Simulate 1 frame in update_manager_.
- HRESULT hr = update_manager_->Update(nullptr);
- if (!SUCCEEDED(hr))
- DebugLogging("UpdateManager update failed.", hr);
- return need_poll_events_;
+void DirectManipulationHelper::RemoveAnimationObserver() {
+ DCHECK(compositor_);
+ compositor_->RemoveAnimationObserver(this);
+ has_animation_observer_ = false;
}
void DirectManipulationHelper::SetDeviceScaleFactorForTesting(float factor) {
event_handler_->SetDeviceScaleFactor(factor);
}
+void DirectManipulationHelper::Destroy() {
+ if (!compositor_)
+ return;
+ if (has_animation_observer_)
+ RemoveAnimationObserver();
+ compositor_ = nullptr;
+
+ HRESULT hr;
+ if (viewport_) {
+ hr = viewport_->Stop();
+ if (!SUCCEEDED(hr))
+ DebugLogging("Viewport stop failed.", hr);
+
+ hr = viewport_->RemoveEventHandler(view_port_handler_cookie_);
+ if (!SUCCEEDED(hr))
+ DebugLogging("Viewport remove event handler failed.", hr);
+
+ hr = viewport_->Abandon();
+ if (!SUCCEEDED(hr))
+ DebugLogging("Viewport abandon failed.", hr);
+ }
+
+ if (manager_) {
+ hr = manager_->Deactivate(window_);
+ if (!SUCCEEDED(hr))
+ DebugLogging("DirectManipulationManager deactivate failed.", hr);
+ }
+}
+
} // namespace content
diff --git a/content/browser/renderer_host/direct_manipulation_helper_win.h b/content/browser/renderer_host/direct_manipulation_helper_win.h
index 76c889b35508ebd92f803a2dd723cfdc11cef1e1..adf19d6849f69b83c51b123c86600a15679da9c6 100644
--- a/content/browser/renderer_host/direct_manipulation_helper_win.h
+++ b/content/browser/renderer_host/direct_manipulation_helper_win.h
@@ -16,10 +16,12 @@
#include "base/macros.h"
#include "content/browser/renderer_host/direct_manipulation_event_handler_win.h"
#include "content/common/content_export.h"
+#include "ui/compositor/compositor_animation_observer.h"
#include "ui/gfx/geometry/size.h"
namespace ui {
+class Compositor;
class WindowEventTarget;
} // namespace ui
@@ -44,13 +46,15 @@ bool LoggingEnabled();
// when DM_POINTERHITTEST.
// 3. OnViewportStatusChanged will be called when the gesture phase change.
// OnContentUpdated will be called when the gesture update.
-class CONTENT_EXPORT DirectManipulationHelper {
+class CONTENT_EXPORT DirectManipulationHelper
+ : public ui::CompositorAnimationObserver {
public:
// Creates and initializes an instance of this class if Direct Manipulation is
// enabled on the platform. Returns nullptr if it disabled or failed on
// initialization.
static std::unique_ptr<DirectManipulationHelper> CreateInstance(
HWND window,
+ ui::Compositor* compositor,
ui::WindowEventTarget* event_target);
// Creates and initializes an instance for testing.
@@ -58,49 +62,48 @@ class CONTENT_EXPORT DirectManipulationHelper {
ui::WindowEventTarget* event_target,
Microsoft::WRL::ComPtr<IDirectManipulationViewport> viewport);
- ~DirectManipulationHelper();
+ ~DirectManipulationHelper() override;
- // Actives Direct Manipulation, call when window show.
- void Activate();
-
- // Deactivates Direct Manipulation, call when window show.
- void Deactivate();
+ // CompositorAnimationObserver implements.
+ // DirectManipulation needs to poll for new events every frame while finger
+ // gesturing on touchpad.
+ void OnAnimationStep(base::TimeTicks timestamp) override;
+ void OnCompositingShuttingDown(ui::Compositor* compositor) override;
// Updates viewport size. Call it when window bounds updated.
void SetSizeInPixels(const gfx::Size& size_in_pixels);
- // Reset for gesture end.
- HRESULT Reset(bool need_animtation);
+ // Pass the pointer hit test to Direct Manipulation.
+ void OnPointerHitTest(WPARAM w_param);
- // Pass the pointer hit test to Direct Manipulation. Return true indicated we
- // need poll for new events every frame from here.
- bool OnPointerHitTest(WPARAM w_param, ui::WindowEventTarget* event_target);
+ // Register this as an AnimationObserver of ui::Compositor.
+ void AddAnimationObserver();
- // On each frame poll new Direct Manipulation events. Return true if we still
- // need poll for new events on next frame, otherwise stop request need begin
- // frame.
- bool PollForNextEvent();
+ // Unregister this as an AnimationObserver of ui::Compositor.
+ void RemoveAnimationObserver();
private:
friend class content::DirectManipulationBrowserTest;
friend class DirectManipulationUnitTest;
- DirectManipulationHelper();
+ DirectManipulationHelper(HWND window, ui::Compositor* compositor);
// This function instantiates Direct Manipulation and creates a viewport for
- // the passed in |window|. Return false if initialize failed.
+ // |window_|. Return false if initialize failed.
bool Initialize(ui::WindowEventTarget* event_target);
void SetDeviceScaleFactorForTesting(float factor);
+ void Destroy();
+
Microsoft::WRL::ComPtr<IDirectManipulationManager> manager_;
Microsoft::WRL::ComPtr<IDirectManipulationUpdateManager> update_manager_;
Microsoft::WRL::ComPtr<IDirectManipulationViewport> viewport_;
Microsoft::WRL::ComPtr<DirectManipulationEventHandler> event_handler_;
HWND window_;
+ ui::Compositor* compositor_ = nullptr;
DWORD view_port_handler_cookie_;
- bool need_poll_events_ = false;
- gfx::Size viewport_size_in_pixels_;
+ bool has_animation_observer_ = false;
DISALLOW_COPY_AND_ASSIGN(DirectManipulationHelper);
};
diff --git a/content/browser/renderer_host/direct_manipulation_win_browsertest.cc b/content/browser/renderer_host/direct_manipulation_win_browsertest.cc
index 7648cf140d0de6e82ea81c33877495c91e7a57a9..10cf453cda7110b0531854ba63bb908fdd590d50 100644
--- a/content/browser/renderer_host/direct_manipulation_win_browsertest.cc
+++ b/content/browser/renderer_host/direct_manipulation_win_browsertest.cc
@@ -49,33 +49,23 @@ class DirectManipulationBrowserTest : public ContentBrowserTest,
return rwhva->legacy_render_widget_host_HWND_;
}
- HWND GetSubWindowHWND() {
- LegacyRenderWidgetHostHWND* lrwhh = GetLegacyRenderWidgetHostHWND();
-
- return lrwhh->hwnd();
- }
-
ui::WindowEventTarget* GetWindowEventTarget() {
LegacyRenderWidgetHostHWND* lrwhh = GetLegacyRenderWidgetHostHWND();
return lrwhh->GetWindowEventTarget(lrwhh->GetParent());
}
- void SimulatePointerHitTest() {
- LegacyRenderWidgetHostHWND* lrwhh = GetLegacyRenderWidgetHostHWND();
-
- lrwhh->direct_manipulation_helper_->need_poll_events_ = true;
- lrwhh->CreateAnimationObserver();
- }
-
- void UpdateParent(HWND hwnd) {
+ void SetDirectManipulationInteraction(
+ DIRECTMANIPULATION_INTERACTION_TYPE type) {
LegacyRenderWidgetHostHWND* lrwhh = GetLegacyRenderWidgetHostHWND();
- lrwhh->UpdateParent(hwnd);
+ lrwhh->direct_manipulation_helper_->event_handler_->OnInteraction(nullptr,
+ type);
}
- bool HasCompositorAnimationObserver(LegacyRenderWidgetHostHWND* lrwhh) {
- return lrwhh->compositor_animation_observer_ != nullptr;
+ bool HasAnimationObserver(LegacyRenderWidgetHostHWND* lrwhh) {
+ return lrwhh->direct_manipulation_helper_->compositor_
+ ->HasAnimationObserver(lrwhh->direct_manipulation_helper_.get());
}
private:
@@ -88,8 +78,10 @@ INSTANTIATE_TEST_SUITE_P(WithScrollEventPhase,
DirectManipulationBrowserTest,
testing::Bool());
-// Ensure the AnimationObserver destroy when hwnd reparent to other hwnd.
-IN_PROC_BROWSER_TEST_P(DirectManipulationBrowserTest, HWNDReparent) {
+// Ensure the AnimationObserver is only created after direct manipulation
+// interaction begin and destroyed after direct manipulation interaction end.
+IN_PROC_BROWSER_TEST_P(DirectManipulationBrowserTest,
+ ObserverDuringInteraction) {
if (base::win::GetVersion() < base::win::Version::WIN10)
return;
@@ -98,25 +90,20 @@ IN_PROC_BROWSER_TEST_P(DirectManipulationBrowserTest, HWNDReparent) {
LegacyRenderWidgetHostHWND* lrwhh = GetLegacyRenderWidgetHostHWND();
ASSERT_TRUE(lrwhh);
- // The observer should not create before it needed.
- ASSERT_TRUE(!HasCompositorAnimationObserver(lrwhh));
+ // The observer should not be created before it is needed.
+ EXPECT_FALSE(HasAnimationObserver(lrwhh));
- // Add AnimationObserver to tab to simulate direct manipulation start.
- SimulatePointerHitTest();
- ASSERT_TRUE(HasCompositorAnimationObserver(lrwhh));
+ // Begin direct manipulation interaction.
+ SetDirectManipulationInteraction(DIRECTMANIPULATION_INTERACTION_BEGIN);
+ // AnimationObserver should be added after direct manipulation interaction
+ // begin.
+ EXPECT_TRUE(HasAnimationObserver(lrwhh));
- // Create another browser.
- Shell* shell2 = CreateBrowser();
- NavigateToURL(shell2, GURL(url::kAboutBlankURL));
-
- // Move to the tab to browser2.
- UpdateParent(
- shell2->window()->GetRootWindow()->GetHost()->GetAcceleratedWidget());
+ // End direct manipulation interaction.
+ SetDirectManipulationInteraction(DIRECTMANIPULATION_INTERACTION_END);
// The animation observer should be removed.
- EXPECT_FALSE(HasCompositorAnimationObserver(lrwhh));
-
- shell2->Close();
+ EXPECT_FALSE(HasAnimationObserver(lrwhh));
}
// EventLogger is to observe the events sent from WindowEventTarget (the root
diff --git a/content/browser/renderer_host/direct_manipulation_win_unittest.cc b/content/browser/renderer_host/direct_manipulation_win_unittest.cc
index 3bf1b55555d0485c25575264045d415cf534323c..ccfa4d90b4e5c546c78afdac7c6b7b36f6096514 100644
--- a/content/browser/renderer_host/direct_manipulation_win_unittest.cc
+++ b/content/browser/renderer_host/direct_manipulation_win_unittest.cc
@@ -31,6 +31,12 @@ class MockDirectManipulationViewport
~MockDirectManipulationViewport() override {}
+ bool WasZoomToRectCalled() {
+ bool called = zoom_to_rect_called_;
+ zoom_to_rect_called_ = false;
+ return called;
+ }
+
HRESULT STDMETHODCALLTYPE Enable() override { return S_OK; }
HRESULT STDMETHODCALLTYPE Disable() override { return S_OK; }
@@ -75,6 +81,7 @@ class MockDirectManipulationViewport
_In_ const float right,
_In_ const float bottom,
_In_ BOOL animate) override {
+ zoom_to_rect_called_ = true;
return S_OK;
}
@@ -161,6 +168,8 @@ class MockDirectManipulationViewport
HRESULT STDMETHODCALLTYPE Abandon() override { return S_OK; }
private:
+ bool zoom_to_rect_called_ = false;
+
DISALLOW_COPY_AND_ASSIGN(MockDirectManipulationViewport);
};
@@ -397,13 +406,7 @@ class DirectManipulationUnitTest : public testing::Test {
viewport_.Get(), content_.Get());
}
- void SetNeedAnimation(bool need_poll_events) {
- direct_manipulation_helper_->need_poll_events_ = need_poll_events;
- }
-
- bool NeedAnimation() {
- return direct_manipulation_helper_->need_poll_events_;
- }
+ bool WasZoomToRectCalled() { return viewport_->WasZoomToRectCalled(); }
void SetDeviceScaleFactor(float factor) {
direct_manipulation_helper_->SetDeviceScaleFactorForTesting(factor);
@@ -721,21 +724,19 @@ TEST_F(DirectManipulationUnitTest,
}
TEST_F(DirectManipulationUnitTest,
- NeedAnimtationShouldBeFalseAfterSecondReset) {
+ ZoomToRectShouldNotBeCalledInEmptyRunningReadySequence) {
if (!GetDirectManipulationHelper())
return;
- // Direct Manipulation will set need_poll_events_ true when DM_POINTERTEST
- // from touchpad.
- SetNeedAnimation(true);
+ ContentUpdated(1.0f, 5, 0);
// Receive first ready when gesture end.
ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_RUNNING);
- EXPECT_TRUE(NeedAnimation());
+ EXPECT_TRUE(WasZoomToRectCalled());
// Receive second ready from ZoomToRect.
ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_RUNNING);
- EXPECT_FALSE(NeedAnimation());
+ EXPECT_FALSE(WasZoomToRectCalled());
}
TEST_F(DirectManipulationUnitTest, HiDPIScroll) {
diff --git a/content/browser/renderer_host/legacy_render_widget_host_win.cc b/content/browser/renderer_host/legacy_render_widget_host_win.cc
index b78186969fc928468c67f0bfcc853f5d7418df95..d2dd02a743a9bc908b6f8438ccac9dd85aa0a281 100644
--- a/content/browser/renderer_host/legacy_render_widget_host_win.cc
+++ b/content/browser/renderer_host/legacy_render_widget_host_win.cc
@@ -27,7 +27,6 @@
#include "ui/base/view_prop.h"
#include "ui/base/win/internal_constants.h"
#include "ui/base/win/window_event_target.h"
-#include "ui/compositor/compositor.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/rect.h"
@@ -38,47 +37,6 @@ namespace content {
// accessibility support.
const int kIdScreenReaderHoneyPot = 1;
-// DirectManipulation needs to poll for new events every frame while finger
-// gesturing on touchpad.
-class CompositorAnimationObserverForDirectManipulation
- : public ui::CompositorAnimationObserver {
- public:
- CompositorAnimationObserverForDirectManipulation(
- LegacyRenderWidgetHostHWND* render_widget_host_hwnd,
- ui::Compositor* compositor)
- : render_widget_host_hwnd_(render_widget_host_hwnd),
- compositor_(compositor) {
- DCHECK(compositor_);
- compositor_->AddAnimationObserver(this);
- DebugLogging("Add AnimationObserverForDirectManipulation.");
- }
-
- ~CompositorAnimationObserverForDirectManipulation() override {
- if (compositor_) {
- compositor_->RemoveAnimationObserver(this);
- DebugLogging("Remove AnimationObserverForDirectManipulation.");
- }
- }
-
- // ui::CompositorAnimationObserver
- void OnAnimationStep(base::TimeTicks timestamp) override {
- render_widget_host_hwnd_->PollForNextEvent();
- }
-
- // ui::CompositorAnimationObserver
- void OnCompositingShuttingDown(ui::Compositor* compositor) override {
- DebugLogging("OnCompositingShuttingDown.");
- compositor->RemoveAnimationObserver(this);
- compositor_ = nullptr;
- }
-
- private:
- LegacyRenderWidgetHostHWND* render_widget_host_hwnd_;
- ui::Compositor* compositor_;
-
- DISALLOW_COPY_AND_ASSIGN(CompositorAnimationObserverForDirectManipulation);
-};
-
// static
LegacyRenderWidgetHostHWND* LegacyRenderWidgetHostHWND::Create(
HWND parent) {
@@ -103,8 +61,9 @@ LegacyRenderWidgetHostHWND* LegacyRenderWidgetHostHWND::Create(
}
void LegacyRenderWidgetHostHWND::Destroy() {
- // Stop the AnimationObserver when window close.
- DestroyAnimationObserver();
+ // Delete DirectManipulationHelper before the window is destroyed.
+ if (direct_manipulation_helper_)
+ direct_manipulation_helper_.reset();
host_ = nullptr;
if (::IsWindow(hwnd()))
::DestroyWindow(hwnd());
@@ -113,10 +72,16 @@ void LegacyRenderWidgetHostHWND::Destroy() {
void LegacyRenderWidgetHostHWND::UpdateParent(HWND parent) {
if (GetWindowEventTarget(GetParent()))
GetWindowEventTarget(GetParent())->HandleParentChanged();
- // Stop the AnimationObserver when window hide. eg. tab switch, move tab to
- // another window.
- DestroyAnimationObserver();
+
::SetParent(hwnd(), parent);
+
+ // Direct Manipulation is enabled on Windows 10+. The CreateInstance function
+ // returns NULL if Direct Manipulation is not available. Recreate
+ // |direct_manipulation_helper_| when parent changed (compositor and window
+ // event target updated).
+ direct_manipulation_helper_ = DirectManipulationHelper::CreateInstance(
+ hwnd(), host_->GetNativeView()->GetHost()->compositor(),
+ GetWindowEventTarget(GetParent()));
}
HWND LegacyRenderWidgetHostHWND::GetParent() {
@@ -125,14 +90,10 @@ HWND LegacyRenderWidgetHostHWND::GetParent() {
void LegacyRenderWidgetHostHWND::Show() {
::ShowWindow(hwnd(), SW_SHOW);
- if (direct_manipulation_helper_)
- direct_manipulation_helper_->Activate();
}
void LegacyRenderWidgetHostHWND::Hide() {
::ShowWindow(hwnd(), SW_HIDE);
- if (direct_manipulation_helper_)
- direct_manipulation_helper_->Deactivate();
}
void LegacyRenderWidgetHostHWND::SetBounds(const gfx::Rect& bounds) {
@@ -191,11 +152,6 @@ bool LegacyRenderWidgetHostHWND::Init() {
CHILDID_SELF);
}
- // Direct Manipulation is enabled on Windows 10+. The CreateInstance function
- // returns NULL if Direct Manipulation is not available.
- direct_manipulation_helper_ = DirectManipulationHelper::CreateInstance(
- hwnd(), GetWindowEventTarget(GetParent()));
-
// Disable pen flicks (http://crbug.com/506977)
base::win::DisableFlicks(hwnd());
@@ -501,21 +457,6 @@ LRESULT LegacyRenderWidgetHostHWND::OnSize(UINT message,
return 0;
}
-LRESULT LegacyRenderWidgetHostHWND::OnWindowPosChanged(UINT message,
- WPARAM w_param,
- LPARAM l_param) {
- WINDOWPOS* window_pos = reinterpret_cast<WINDOWPOS*>(l_param);
- if (direct_manipulation_helper_) {
- if (window_pos->flags & SWP_SHOWWINDOW) {
- direct_manipulation_helper_->Activate();
- } else if (window_pos->flags & SWP_HIDEWINDOW) {
- direct_manipulation_helper_->Deactivate();
- }
- }
- SetMsgHandled(FALSE);
- return 0;
-}
-
LRESULT LegacyRenderWidgetHostHWND::OnDestroy(UINT message,
WPARAM w_param,
LPARAM l_param) {
@@ -534,30 +475,12 @@ LRESULT LegacyRenderWidgetHostHWND::OnPointerHitTest(UINT message,
return 0;
DebugLogging("Receive DM_POINTERHITTEST.");
- // Update window event target for each DM_POINTERHITTEST.
- if (direct_manipulation_helper_->OnPointerHitTest(
- w_param, GetWindowEventTarget(GetParent()))) {
- if (compositor_animation_observer_) {
- // This is reach if Windows send a DM_POINTERHITTEST before the last
- // DM_POINTERHITTEST receive READY status. We never see this but still
- // worth to handle it.
- DebugLogging("AnimationObserverForDirectManipulation exists.");
- return 0;
- }
- CreateAnimationObserver();
- }
+ direct_manipulation_helper_->OnPointerHitTest(w_param);
return 0;
}
-void LegacyRenderWidgetHostHWND::PollForNextEvent() {
- DCHECK(direct_manipulation_helper_);
-
- if (!direct_manipulation_helper_->PollForNextEvent())
- DestroyAnimationObserver();
-}
-
gfx::NativeViewAccessible
LegacyRenderWidgetHostHWND::GetOrCreateWindowRootAccessible() {
if (!host_)
@@ -589,20 +512,4 @@ LegacyRenderWidgetHostHWND::GetOrCreateWindowRootAccessible() {
return root->GetNativeViewAccessible();
}
-void LegacyRenderWidgetHostHWND::CreateAnimationObserver() {
- DCHECK(!compositor_animation_observer_);
- DCHECK(host_);
- DCHECK(host_->GetNativeView()->GetHost());
- DCHECK(host_->GetNativeView()->GetHost()->compositor());
-
- compositor_animation_observer_ =
- std::make_unique<CompositorAnimationObserverForDirectManipulation>(
- this, host_->GetNativeView()->GetHost()->compositor());
-}
-
-void LegacyRenderWidgetHostHWND::DestroyAnimationObserver() {
- DebugLogging("DestroyAnimationObserver.");
- compositor_animation_observer_.reset();
-}
-
} // namespace content
diff --git a/content/browser/renderer_host/legacy_render_widget_host_win.h b/content/browser/renderer_host/legacy_render_widget_host_win.h
index 4f12296e3185caac685c8326192d88d32037a9d9..be0ea7bdbf460f414c94b0f2275a00424ec3198a 100644
--- a/content/browser/renderer_host/legacy_render_widget_host_win.h
+++ b/content/browser/renderer_host/legacy_render_widget_host_win.h
@@ -17,7 +17,6 @@
#include "base/macros.h"
#include "content/common/content_export.h"
-#include "ui/compositor/compositor_animation_observer.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_widget_types.h"
@@ -99,7 +98,6 @@ class CONTENT_EXPORT LegacyRenderWidgetHostHWND
OnMouseRange)
MESSAGE_HANDLER_EX(WM_NCCALCSIZE, OnNCCalcSize)
MESSAGE_HANDLER_EX(WM_SIZE, OnSize)
- MESSAGE_HANDLER_EX(WM_WINDOWPOSCHANGED, OnWindowPosChanged)
MESSAGE_HANDLER_EX(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER_EX(DM_POINTERHITTEST, OnPointerHitTest)
END_MSG_MAP()
@@ -126,10 +124,6 @@ class CONTENT_EXPORT LegacyRenderWidgetHostHWND
host_ = host;
}
- // DirectManipulation needs to poll for new events every frame while finger
- // gesturing on touchpad.
- void PollForNextEvent();
-
// Return the root accessible object for either MSAA or UI Automation.
gfx::NativeViewAccessible GetOrCreateWindowRootAccessible();
@@ -166,15 +160,10 @@ class CONTENT_EXPORT LegacyRenderWidgetHostHWND
LRESULT OnSetCursor(UINT message, WPARAM w_param, LPARAM l_param);
LRESULT OnNCCalcSize(UINT message, WPARAM w_param, LPARAM l_param);
LRESULT OnSize(UINT message, WPARAM w_param, LPARAM l_param);
- LRESULT OnWindowPosChanged(UINT message, WPARAM w_param, LPARAM l_param);
LRESULT OnDestroy(UINT message, WPARAM w_param, LPARAM l_param);
LRESULT OnPointerHitTest(UINT message, WPARAM w_param, LPARAM l_param);
- void CreateAnimationObserver();
-
- void DestroyAnimationObserver();
-
Microsoft::WRL::ComPtr<IAccessible> window_accessible_;
// Set to true if we turned on mouse tracking.
@@ -193,9 +182,6 @@ class CONTENT_EXPORT LegacyRenderWidgetHostHWND
// in Chrome on Windows 10.
std::unique_ptr<DirectManipulationHelper> direct_manipulation_helper_;
- std::unique_ptr<ui::CompositorAnimationObserver>
- compositor_animation_observer_;
-
DISALLOW_COPY_AND_ASSIGN(LegacyRenderWidgetHostHWND);
};

View File

@@ -32,7 +32,6 @@ feat_add_original-fs_module.patch
build_allow_embedders_to_override_the_node_module_version_define.patch
refactor_allow_embedder_overriding_of_internal_fs_calls.patch
chore_add_ability_to_prevent_warn_non_context-aware_native_modules.patch
fsevents_fix_file_event_reporting.patch
src_only_run_preloadmodules_if_the_preload_array_is_not_empty.patch
src_read_break_node_first_line_from_the_inspect_options.patch
chore_allow_the_node_entrypoint_to_be_a_builtin_module.patch
@@ -55,3 +54,8 @@ chore_read_nobrowserglobals_from_global_not_process.patch
chore_use_v8_inspector_js_protocol_to_find_pdl_file.patch
chore_split_createenvironment_into_createenvironment_and.patch
fix_set_uptime_offset_in_correct_init_method.patch
fix_enable_worker_threads.patch
fsevents-stop-using-fsevents-to-watch-files.patch
fsevents-regression-in-watching.patch
build_bring_back_node_with_ltcg_configuration.patch
fix_uv_fs_mkdir_for_invalid_names.patch

View File

@@ -0,0 +1,51 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Deepak Mohan <hop2deep@gmail.com>
Date: Wed, 16 Oct 2019 13:41:12 -0700
Subject: build: bring back node_with_ltcg configuration
This was moved to code node.gyp as part of https://github.com/nodejs/node/pull/25931
which caused native modules size increase which were depending on
this configuration transitively https://github.com/nodejs/node/issues/29501.
THe fix for this should land in node-gyp as discussed in above issue,
landing this as temporary patch.
diff --git a/common.gypi b/common.gypi
index bde7d7300f44596abe5cdfac0639ecb1bb4d885f..412f613e7cfcf563fa6a000b932723166ab567da 100644
--- a/common.gypi
+++ b/common.gypi
@@ -19,7 +19,7 @@
'node_use_v8_platform%': 'true',
'node_use_bundled_v8%': 'true',
'node_module_version%': '',
- 'node_with_ltcg%': '',
+ 'node_with_ltcg%': 'true',
'node_shared_openssl%': 'false',
'node_tag%': '',
@@ -240,6 +240,26 @@
'cflags': [ '-fPIE' ],
'ldflags': [ '-fPIE', '-pie' ]
}],
+ ['node_with_ltcg=="true"', {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ 'WholeProgramOptimization': 'true' # /GL, whole program optimization, needed for LTCG
+ },
+ 'VCLibrarianTool': {
+ 'AdditionalOptions': [
+ '/LTCG:INCREMENTAL', # incremental link-time code generation
+ ]
+ },
+ 'VCLinkerTool': {
+ 'OptimizeReferences': 2, # /OPT:REF
+ 'EnableCOMDATFolding': 2, # /OPT:ICF
+ 'LinkIncremental': 1, # disable incremental linking
+ 'AdditionalOptions': [
+ '/LTCG:INCREMENTAL', # incremental link-time code generation
+ ]
+ }
+ }
+ }]
],
'msvs_settings': {
'VCCLCompilerTool': {

View File

@@ -0,0 +1,29 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Thu, 3 Oct 2019 19:03:30 +0200
Subject: fix: enable worker_threads
Electron sets NODE_USE_V8_PLATFORM to false, because Electron
initializes the v8 platform itself and Node.js worker thread
initialization relies on the platform it uses having been set inside
code guarded by NODE_USE_V8_PLATFORM.
This commit fixes this problem by changing node_worker to use the three-arg
implementation of `NewIsolate` to prevent it trying to use a possibly-null ptr.
diff --git a/src/node_worker.cc b/src/node_worker.cc
index 8f97f5c351..2bfbb28e61 100644
--- a/src/node_worker.cc
+++ b/src/node_worker.cc
@@ -112,7 +112,10 @@ class WorkerThreadData {
array_buffer_allocator_(ArrayBufferAllocator::Create()) {
CHECK_EQ(uv_loop_init(&loop_), 0);
- Isolate* isolate = NewIsolate(array_buffer_allocator_.get(), &loop_);
+ Isolate* isolate = NewIsolate(
+ array_buffer_allocator_.get(),
+ &loop_,
+ w->platform_);
CHECK_NOT_NULL(isolate);
{

View File

@@ -0,0 +1,76 @@
From ecff27857dafe3f5d30a6ab8646ea69a93e4940a Mon Sep 17 00:00:00 2001
From: Bartosz Sosnowski <bartosz@janeasystems.com>
Date: Thu, 11 Jul 2019 12:45:38 +0200
Subject: [PATCH] win, fs: mkdir return UV_EINVAL for invalid names
Makes uv_fs_mkdir return UV_EINVAL for invalid filenames instead of
UV_ENOENT.
Ref: https://github.com/nodejs/node/issues/28599
PR-URL: https://github.com/libuv/libuv/pull/2375
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
diff --git a/deps/uv/src/win/fs.c b/deps/uv/src/win/fs.c
index 15094121..31242b51 100644
--- a/deps/uv/src/win/fs.c
+++ b/deps/uv/src/win/fs.c
@@ -1180,8 +1180,13 @@ void fs__unlink(uv_fs_t* req) {
void fs__mkdir(uv_fs_t* req) {
/* TODO: use req->mode. */
- int result = _wmkdir(req->file.pathw);
- SET_REQ_RESULT(req, result);
+ req->result = _wmkdir(req->file.pathw);
+ if (req->result == -1) {
+ req->sys_errno_ = _doserrno;
+ req->result = req->sys_errno_ == ERROR_INVALID_NAME
+ ? UV_EINVAL
+ : uv_translate_sys_error(req->sys_errno_);
+ }
}
diff --git a/deps/uv/test/test-fs.c b/deps/uv/test/test-fs.c
index 35a992d8..95f6b5e9 100644
--- a/deps/uv/test/test-fs.c
+++ b/deps/uv/test/test-fs.c
@@ -4060,4 +4060,16 @@ TEST_IMPL(fs_fchmod_archive_readonly) {
return 0;
}
+
+TEST_IMPL(fs_invalid_mkdir_name) {
+ uv_loop_t* loop;
+ uv_fs_t req;
+ int r;
+
+ loop = uv_default_loop();
+ r = uv_fs_mkdir(loop, &req, "invalid>", 0, NULL);
+ ASSERT(r == UV_EINVAL);
+
+ return 0;
+}
#endif
diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h
index 3c5f21b9..ffa7e545 100644
--- a/deps/uv/test/test-list.h
+++ b/deps/uv/test/test-list.h
@@ -380,6 +380,7 @@ TEST_DECLARE (fs_exclusive_sharing_mode)
TEST_DECLARE (fs_file_flag_no_buffering)
TEST_DECLARE (fs_open_readonly_acl)
TEST_DECLARE (fs_fchmod_archive_readonly)
+TEST_DECLARE (fs_invalid_mkdir_name)
#endif
TEST_DECLARE (strscpy)
TEST_DECLARE (threadpool_queue_work_simple)
@@ -973,6 +974,7 @@ TASK_LIST_START
TEST_ENTRY (fs_file_flag_no_buffering)
TEST_ENTRY (fs_open_readonly_acl)
TEST_ENTRY (fs_fchmod_archive_readonly)
+ TEST_ENTRY (fs_invalid_mkdir_name)
#endif
TEST_ENTRY (get_osfhandle_valid_handle)
TEST_ENTRY (open_osfhandle_valid_handle)

View File

@@ -0,0 +1,158 @@
From ae12376dbb56fa080b699f00840c7b9c5230a85f Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Sat, 7 Sep 2019 20:45:39 -0400
Subject: [PATCH] fsevents: regression in watching /
This case got lost by accident in
https://github.com/libuv/libuv/pull/2082,
preventing the realpath `/` from ever matching.
Fixes: https://github.com/nodejs/node/issues/28917
PR-URL: https://github.com/libuv/libuv/pull/2460
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Saúl Ibarra Corretgé <s@saghul.net>
diff --git a/deps/uv/src/unix/fsevents.c b/deps/uv/src/unix/fsevents.c
index ddacda31..deeaa63d 100644
--- a/deps/uv/src/unix/fsevents.c
+++ b/deps/uv/src/unix/fsevents.c
@@ -263,10 +263,12 @@ static void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef,
if (len < handle->realpath_len)
continue;
+ /* Make sure that realpath actually named a directory,
+ * (unless watching root, which alone keeps a trailing slash on the realpath)
+ * or that we matched the whole string */
if (handle->realpath_len != len &&
+ handle->realpath_len > 1 &&
path[handle->realpath_len] != '/')
- /* Make sure that realpath actually named a directory,
- * or that we matched the whole string */
continue;
if (memcmp(path, handle->realpath, handle->realpath_len) != 0)
diff --git a/deps/uv/test/test-fs-event.c b/deps/uv/test/test-fs-event.c
index 4b8bb1ef..7725c3af 100644
--- a/deps/uv/test/test-fs-event.c
+++ b/deps/uv/test/test-fs-event.c
@@ -47,6 +47,7 @@ static const char file_prefix[] = "fsevent-";
static const int fs_event_file_count = 16;
#if defined(__APPLE__) || defined(_WIN32)
static const char file_prefix_in_subdir[] = "subdir";
+static int fs_multievent_cb_called;
#endif
static uv_timer_t timer;
static int timer_cb_called;
@@ -280,7 +281,7 @@ static void fs_event_cb_dir_multi_file_in_subdir(uv_fs_event_t* handle,
if (filename && strcmp(filename, file_prefix_in_subdir) == 0)
return;
#endif
- fs_event_cb_called++;
+ fs_multievent_cb_called++;
ASSERT(handle == &fs_event);
ASSERT(status == 0);
ASSERT(events == UV_CHANGE || events == UV_RENAME);
@@ -298,7 +299,7 @@ static void fs_event_cb_dir_multi_file_in_subdir(uv_fs_event_t* handle,
if (fs_event_created + fs_event_removed == fs_event_file_count) {
/* Once we've processed all create events, delete all files */
ASSERT(0 == uv_timer_start(&timer, fs_event_unlink_files_in_subdir, 1, 0));
- } else if (fs_event_cb_called == 2 * fs_event_file_count) {
+ } else if (fs_multievent_cb_called == 2 * fs_event_file_count) {
/* Once we've processed all create and delete events, stop watching */
uv_close((uv_handle_t*) &timer, close_cb);
uv_close((uv_handle_t*) handle, close_cb);
@@ -393,6 +394,21 @@ static void timer_cb_watch_twice(uv_timer_t* handle) {
uv_close((uv_handle_t*) handle, NULL);
}
+static void fs_event_cb_close(uv_fs_event_t* handle,
+ const char* filename,
+ int events,
+ int status) {
+ ASSERT(status == 0);
+
+ ASSERT(fs_event_cb_called < 3);
+ ++fs_event_cb_called;
+
+ if (fs_event_cb_called == 3) {
+ uv_close((uv_handle_t*) handle, close_cb);
+ }
+}
+
+
TEST_IMPL(fs_event_watch_dir) {
#if defined(NO_FS_EVENTS)
RETURN_SKIP(NO_FS_EVENTS);
@@ -434,10 +450,12 @@ TEST_IMPL(fs_event_watch_dir) {
return 0;
}
+
TEST_IMPL(fs_event_watch_dir_recursive) {
#if defined(__APPLE__) || defined(_WIN32)
uv_loop_t* loop;
int r;
+ uv_fs_event_t fs_event_root;
/* Setup */
loop = uv_default_loop();
@@ -451,17 +469,37 @@ TEST_IMPL(fs_event_watch_dir_recursive) {
r = uv_fs_event_init(loop, &fs_event);
ASSERT(r == 0);
- r = uv_fs_event_start(&fs_event, fs_event_cb_dir_multi_file_in_subdir, "watch_dir", UV_FS_EVENT_RECURSIVE);
+ r = uv_fs_event_start(&fs_event,
+ fs_event_cb_dir_multi_file_in_subdir,
+ "watch_dir",
+ UV_FS_EVENT_RECURSIVE);
ASSERT(r == 0);
r = uv_timer_init(loop, &timer);
ASSERT(r == 0);
r = uv_timer_start(&timer, fs_event_create_files_in_subdir, 100, 0);
ASSERT(r == 0);
+#ifndef _WIN32
+ /* Also try to watch the root directory.
+ * This will be noisier, so we're just checking for any couple events to happen. */
+ r = uv_fs_event_init(loop, &fs_event_root);
+ ASSERT(r == 0);
+ r = uv_fs_event_start(&fs_event_root,
+ fs_event_cb_close,
+ "/",
+ UV_FS_EVENT_RECURSIVE);
+ ASSERT(r == 0);
+#else
+ fs_event_cb_called += 3;
+ close_cb_called += 1;
+ (void)fs_event_root;
+#endif
+
uv_run(loop, UV_RUN_DEFAULT);
- ASSERT(fs_event_cb_called == fs_event_created + fs_event_removed);
- ASSERT(close_cb_called == 2);
+ ASSERT(fs_multievent_cb_called == fs_event_created + fs_event_removed);
+ ASSERT(fs_event_cb_called == 3);
+ ASSERT(close_cb_called == 3);
/* Cleanup */
fs_event_unlink_files_in_subdir(NULL);
@@ -870,18 +908,6 @@ TEST_IMPL(fs_event_close_with_pending_event) {
return 0;
}
-static void fs_event_cb_close(uv_fs_event_t* handle, const char* filename,
- int events, int status) {
- ASSERT(status == 0);
-
- ASSERT(fs_event_cb_called < 3);
- ++fs_event_cb_called;
-
- if (fs_event_cb_called == 3) {
- uv_close((uv_handle_t*) handle, close_cb);
- }
-}
-
TEST_IMPL(fs_event_close_in_callback) {
#if defined(NO_FS_EVENTS)
RETURN_SKIP(NO_FS_EVENTS);

View File

@@ -0,0 +1,120 @@
From 97b85e8b75b8f3df774b6e008dbaa143daa412b7 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Sat, 7 Sep 2019 14:55:40 -0400
Subject: [PATCH] fsevents: stop using fsevents to watch files
Goes back to just using it to watch folders,
but keeps the other logic changes around.
Refs: https://github.com/libuv/libuv/pull/387
Refs: https://github.com/libuv/libuv/pull/2082
Refs: https://github.com/libuv/libuv/pull/1572
Refs: https://github.com/nodejs/node/issues/29460
Fixes: https://github.com/libuv/libuv/issues/2488
Closes: https://github.com/libuv/libuv/pull/2452
PR-URL: https://github.com/libuv/libuv/pull/2459
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Saúl Ibarra Corretgé <s@saghul.net>
diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c
index c04e7a48..ad09f403 100644
--- a/deps/uv/src/unix/kqueue.c
+++ b/deps/uv/src/unix/kqueue.c
@@ -454,10 +454,26 @@ int uv_fs_event_start(uv_fs_event_t* handle,
const char* path,
unsigned int flags) {
int fd;
+#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
+ struct stat statbuf;
+#endif
if (uv__is_active(handle))
return UV_EINVAL;
+ handle->cb = cb;
+ handle->path = uv__strdup(path);
+ if (handle->path == NULL)
+ return UV_ENOMEM;
+
+ /* TODO open asynchronously - but how do we report back errors? */
+ fd = open(handle->path, O_RDONLY);
+ if (fd == -1) {
+ uv__free(handle->path);
+ handle->path = NULL;
+ return UV__ERR(errno);
+ }
+
#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
/* Nullify field to perform checks later */
handle->cf_cb = NULL;
@@ -465,14 +481,17 @@ int uv_fs_event_start(uv_fs_event_t* handle,
handle->realpath_len = 0;
handle->cf_flags = flags;
+ if (fstat(fd, &statbuf))
+ goto fallback;
+ /* FSEvents works only with directories */
+ if (!(statbuf.st_mode & S_IFDIR))
+ goto fallback;
+
if (!uv__has_forked_with_cfrunloop) {
int r;
- /* The fallback fd is not used */
+ /* The fallback fd is no longer needed */
+ uv__close_nocheckstdio(fd);
handle->event_watcher.fd = -1;
- handle->path = uv__strdup(path);
- if (handle->path == NULL)
- return UV_ENOMEM;
- handle->cb = cb;
r = uv__fsevents_init(handle);
if (r == 0) {
uv__handle_start(handle);
@@ -482,20 +501,9 @@ int uv_fs_event_start(uv_fs_event_t* handle,
}
return r;
}
+fallback:
#endif /* #if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 */
- /* TODO open asynchronously - but how do we report back errors? */
- fd = open(path, O_RDONLY);
- if (fd == -1)
- return UV__ERR(errno);
-
- handle->path = uv__strdup(path);
- if (handle->path == NULL) {
- uv__close_nocheckstdio(fd);
- return UV_ENOMEM;
- }
-
- handle->cb = cb;
uv__handle_start(handle);
uv__io_init(&handle->event_watcher, uv__fs_event, fd);
uv__io_start(handle->loop, &handle->event_watcher, POLLIN);
@@ -514,7 +522,7 @@ int uv_fs_event_stop(uv_fs_event_t* handle) {
uv__handle_stop(handle);
#if defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
- if (!uv__has_forked_with_cfrunloop)
+ if (!uv__has_forked_with_cfrunloop && handle->cf_cb != NULL)
r = uv__fsevents_close(handle);
#endif
diff --git a/deps/uv/test/test-fs-event.c b/deps/uv/test/test-fs-event.c
index ea34bd63..4b8bb1ef 100644
--- a/deps/uv/test/test-fs-event.c
+++ b/deps/uv/test/test-fs-event.c
@@ -656,6 +656,12 @@ TEST_IMPL(fs_event_watch_file_current_dir) {
/* Setup */
remove("watch_file");
create_file("watch_file");
+#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_12)
+ /* Empirically, kevent seems to (sometimes) report the preceeding
+ * create_file events prior to macOS 10.11.6 in the subsequent fs_event_start
+ * So let the system settle before running the test. */
+ uv_sleep(1100);
+#endif
r = uv_fs_event_init(loop, &fs_event);
ASSERT(r == 0);

View File

@@ -1,213 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ben Noordhuis <info@bnoordhuis.nl>
Date: Mon, 27 May 2019 14:31:53 +0200
Subject: fsevents: fix file event reporting
Commit 2d2af38 ("fsevents: really watch files with fsevents on macos
10.7+") from last November introduced a regression where events that
were previously reported as UV_RENAME were now reported as UV_CHANGE.
This commit rectifies that.
Fixes: https://github.com/nodejs/node/issues/27869
diff --git a/deps/uv/src/unix/fsevents.c b/deps/uv/src/unix/fsevents.c
index ddacda31fef87eee131fc2ee2ff46cc88be429d9..911023a6562319af5e9e3412ddbfd2ca14f77b05 100644
--- a/deps/uv/src/unix/fsevents.c
+++ b/deps/uv/src/unix/fsevents.c
@@ -49,19 +49,9 @@ void uv__fsevents_loop_delete(uv_loop_t* loop) {
#include <CoreFoundation/CFRunLoop.h>
#include <CoreServices/CoreServices.h>
-/* These are macros to avoid "initializer element is not constant" errors
+/* Macro to avoid "initializer element is not constant" errors
* with old versions of gcc.
*/
-#define kFSEventsModified (kFSEventStreamEventFlagItemFinderInfoMod | \
- kFSEventStreamEventFlagItemModified | \
- kFSEventStreamEventFlagItemInodeMetaMod | \
- kFSEventStreamEventFlagItemChangeOwner | \
- kFSEventStreamEventFlagItemXattrMod)
-
-#define kFSEventsRenamed (kFSEventStreamEventFlagItemCreated | \
- kFSEventStreamEventFlagItemRemoved | \
- kFSEventStreamEventFlagItemRenamed)
-
#define kFSEventsSystem (kFSEventStreamEventFlagUserDropped | \
kFSEventStreamEventFlagKernelDropped | \
kFSEventStreamEventFlagEventIdsWrapped | \
@@ -289,8 +279,6 @@ static void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef,
path--;
len++;
}
- /* Created and Removed seem to be always set, but don't make sense */
- flags &= ~kFSEventsRenamed;
} else {
/* Skip forward slash */
path++;
@@ -311,12 +299,12 @@ static void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef,
memset(event, 0, sizeof(*event));
memcpy(event->path, path, len + 1);
- event->events = UV_RENAME;
+ event->events = UV_CHANGE;
- if (0 == (flags & kFSEventsRenamed)) {
- if (0 != (flags & kFSEventsModified) ||
- 0 == (flags & kFSEventStreamEventFlagItemIsDir))
- event->events = UV_CHANGE;
+ if ((flags & kFSEventStreamEventFlagItemIsDir) ||
+ (flags & kFSEventStreamEventFlagItemRemoved) ||
+ (flags & kFSEventStreamEventFlagItemRenamed)) {
+ event->events = UV_RENAME;
}
QUEUE_INSERT_TAIL(&head, &event->member);
diff --git a/deps/uv/test/test-fs-event.c b/deps/uv/test/test-fs-event.c
index ea34bd63a70625c3e2c60d5a1bbb087c5f0bbb2e..38d722a27ef1a78717726272d54578c734280242 100644
--- a/deps/uv/test/test-fs-event.c
+++ b/deps/uv/test/test-fs-event.c
@@ -62,6 +62,15 @@ static char fs_event_filename[1024];
static int timer_cb_touch_called;
static int timer_cb_exact_called;
+static void expect_filename(const char* path, const char* expected) {
+#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
+ ASSERT(0 == strcmp(path, expected));
+#else
+ if (path != NULL)
+ ASSERT(0 == strcmp(path, expected));
+#endif
+}
+
static void fs_event_fail(uv_fs_event_t* handle,
const char* filename,
int events,
@@ -130,11 +139,7 @@ static void fs_event_cb_dir(uv_fs_event_t* handle, const char* filename,
ASSERT(handle == &fs_event);
ASSERT(status == 0);
ASSERT(events == UV_CHANGE);
- #if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
- ASSERT(strcmp(filename, "file1") == 0);
- #else
- ASSERT(filename == NULL || strcmp(filename, "file1") == 0);
- #endif
+ expect_filename(filename, "file1");
ASSERT(0 == uv_fs_event_stop(handle));
uv_close((uv_handle_t*)handle, close_cb);
}
@@ -312,11 +317,7 @@ static void fs_event_cb_file(uv_fs_event_t* handle, const char* filename,
ASSERT(handle == &fs_event);
ASSERT(status == 0);
ASSERT(events == UV_CHANGE);
- #if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
- ASSERT(strcmp(filename, "file2") == 0);
- #else
- ASSERT(filename == NULL || strcmp(filename, "file2") == 0);
- #endif
+ expect_filename(filename, "file2");
ASSERT(0 == uv_fs_event_stop(handle));
uv_close((uv_handle_t*)handle, close_cb);
}
@@ -339,11 +340,7 @@ static void fs_event_cb_file_current_dir(uv_fs_event_t* handle,
ASSERT(handle == &fs_event);
ASSERT(status == 0);
ASSERT(events == UV_CHANGE);
- #if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
- ASSERT(strcmp(filename, "watch_file") == 0);
- #else
- ASSERT(filename == NULL || strcmp(filename, "watch_file") == 0);
- #endif
+ expect_filename(filename, "watch_file");
/* Regression test for SunOS: touch should generate just one event. */
{
@@ -619,6 +616,69 @@ TEST_IMPL(fs_event_watch_file_exact_path) {
return 0;
}
+static void file_remove_cb(uv_fs_event_t* handle,
+ const char* path,
+ int events,
+ int status) {
+ fs_event_cb_called++;
+
+ expect_filename(path, "file1");
+ /* TODO(bnoordhuis) Harmonize the behavior across platforms. Right now
+ * this test merely ensures the status quo doesn't regress.
+ */
+#if defined(_AIX) || defined(__linux__)
+ ASSERT(UV_CHANGE == events);
+#else
+ ASSERT(UV_RENAME == events);
+#endif
+ ASSERT(0 == status);
+
+ uv_close((uv_handle_t*) handle, NULL);
+}
+
+static void file_remove_next(uv_timer_t* handle) {
+ uv_close((uv_handle_t*) handle, NULL);
+ remove("watch_dir/file1");
+}
+
+static void file_remove_start(uv_timer_t* handle) {
+ uv_fs_event_t* watcher;
+ uv_loop_t* loop;
+
+ loop = handle->loop;
+ watcher = handle->data;
+
+ ASSERT(0 == uv_fs_event_init(loop, watcher));
+ ASSERT(0 == uv_fs_event_start(watcher, file_remove_cb, "watch_dir/file1", 0));
+ ASSERT(0 == uv_timer_start(handle, file_remove_next, 50, 0));
+}
+
+TEST_IMPL(fs_event_watch_file_remove) {
+ uv_fs_event_t watcher;
+ uv_timer_t timer;
+ uv_loop_t* loop;
+
+#if defined(__MVS__)
+ RETURN_SKIP("test does not work on this OS");
+#endif
+
+ remove("watch_dir/file1");
+ remove("watch_dir/");
+ create_dir("watch_dir");
+ create_file("watch_dir/file1");
+
+ loop = uv_default_loop();
+ timer.data = &watcher;
+
+ ASSERT(0 == uv_timer_init(loop, &timer));
+ ASSERT(0 == uv_timer_start(&timer, file_remove_start, 500, 0));
+ ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT));
+ ASSERT(1 == fs_event_cb_called);
+
+ MAKE_VALGRIND_HAPPY();
+ return 0;
+}
+
TEST_IMPL(fs_event_watch_file_twice) {
#if defined(NO_FS_EVENTS)
RETURN_SKIP(NO_FS_EVENTS);
diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h
index 8886b07c8a74736208d5614257f832f59d79633a..3e758a1d2cbe104a691d362411770006d1494bae 100644
--- a/deps/uv/test/test-list.h
+++ b/deps/uv/test/test-list.h
@@ -333,6 +333,7 @@ TEST_DECLARE (fs_event_watch_dir_short_path)
#endif
TEST_DECLARE (fs_event_watch_file)
TEST_DECLARE (fs_event_watch_file_exact_path)
+TEST_DECLARE (fs_event_watch_file_remove)
TEST_DECLARE (fs_event_watch_file_twice)
TEST_DECLARE (fs_event_watch_file_current_dir)
#ifdef _WIN32
@@ -920,6 +921,7 @@ TASK_LIST_START
#endif
TEST_ENTRY (fs_event_watch_file)
TEST_ENTRY (fs_event_watch_file_exact_path)
+ TEST_ENTRY (fs_event_watch_file_remove)
TEST_ENTRY (fs_event_watch_file_twice)
TEST_ENTRY (fs_event_watch_file_current_dir)
#ifdef _WIN32

View File

@@ -225,7 +225,6 @@ function runRelease (targetBranch, options) {
} else {
buildCircleCI(targetBranch, options)
buildAppVeyor(targetBranch, options)
buildVSTS(targetBranch, options)
}
console.log(`${jobRequestedCount} jobs were requested.`)
}

View File

@@ -7,7 +7,7 @@ from lib.util import execute, get_out_dir
LINUX_BINARIES_TO_STRIP = [
'electron',
'chrome_sandbox',
'chrome-sandbox',
'libffmpeg.so',
'libGLESv2.so',
'libEGL.so',

View File

@@ -59,7 +59,10 @@ def get_files_list(version):
{ "filename": 'win-x86/iojs.lib', "required": False },
{ "filename": 'win-x64/iojs.lib', "required": False },
{ "filename": 'win-x86/node.lib', "required": False },
{ "filename": 'win-x64/node.lib', "required": False }
{ "filename": 'win-x64/node.lib', "required": False },
{ "filename": 'arm64/node.lib', "required": False },
{ "filename": 'win-arm64/iojs.lib', "required": False },
{ "filename": 'win-arm64/node.lib', "required": False }
]

View File

@@ -53,6 +53,10 @@ def upload_node(bucket, access_key, secret_key, version):
node_lib = os.path.join(DIST_DIR, 'node.lib')
iojs_lib = os.path.join(DIST_DIR, 'win-x86', 'iojs.lib')
v4_node_lib = os.path.join(DIST_DIR, 'win-x86', 'node.lib')
elif get_target_arch() == 'arm64':
node_lib = os.path.join(DIST_DIR, 'arm64', 'node.lib')
iojs_lib = os.path.join(DIST_DIR, 'win-arm64', 'iojs.lib')
v4_node_lib = os.path.join(DIST_DIR, 'win-arm64', 'node.lib')
else:
node_lib = os.path.join(DIST_DIR, 'x64', 'node.lib')
iojs_lib = os.path.join(DIST_DIR, 'win-x64', 'iojs.lib')

View File

@@ -9,6 +9,9 @@ import split = require('split')
import { app, BrowserWindow, Menu } from 'electron'
import { emittedOnce } from './events-helpers';
import { closeWindow } from './window-helpers';
import { ifdescribe } from './spec-helpers';
const features = process.electronBinding('features')
const { expect } = chai
@@ -405,103 +408,107 @@ describe('app module', () => {
expect(webContents).to.equal(w.webContents)
})
it('should emit desktop-capturer-get-sources event when desktopCapturer.getSources() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
ifdescribe(features.isDesktopCapturerEnabled())('desktopCapturer module filtering', () => {
it('should emit desktop-capturer-get-sources event when desktopCapturer.getSources() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
await w.loadURL('about:blank')
const promise = emittedOnce(app, 'desktop-capturer-get-sources')
w.webContents.executeJavaScript(`require('electron').desktopCapturer.getSources({ types: ['screen'] }, () => {})`)
const [, webContents] = await promise
expect(webContents).to.equal(w.webContents)
})
await w.loadURL('about:blank')
const promise = emittedOnce(app, 'desktop-capturer-get-sources')
w.webContents.executeJavaScript(`require('electron').desktopCapturer.getSources({ types: ['screen'] }, () => {})`)
const [, webContents] = await promise
expect(webContents).to.equal(w.webContents)
})
it('should emit remote-require event when remote.require() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
describe('remote module filtering', () => {
it('should emit remote-require event when remote.require() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
await w.loadURL('about:blank')
const promise = emittedOnce(app, 'remote-require')
w.webContents.executeJavaScript(`require('electron').remote.require('test')`)
const [, webContents, moduleName] = await promise
expect(webContents).to.equal(w.webContents)
expect(moduleName).to.equal('test')
})
await w.loadURL('about:blank')
const promise = emittedOnce(app, 'remote-require')
w.webContents.executeJavaScript(`require('electron').remote.require('test')`)
it('should emit remote-get-global event when remote.getGlobal() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
await w.loadURL('about:blank')
const [, webContents, moduleName] = await promise
expect(webContents).to.equal(w.webContents)
expect(moduleName).to.equal('test')
})
const promise = emittedOnce(app, 'remote-get-global')
w.webContents.executeJavaScript(`require('electron').remote.getGlobal('test')`)
it('should emit remote-get-global event when remote.getGlobal() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
const [, webContents, globalName] = await promise
expect(webContents).to.equal(w.webContents)
expect(globalName).to.equal('test')
})
await w.loadURL('about:blank')
const promise = emittedOnce(app, 'remote-get-global')
w.webContents.executeJavaScript(`require('electron').remote.getGlobal('test')`)
it('should emit remote-get-builtin event when remote.getBuiltin() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
await w.loadURL('about:blank')
const [, webContents, globalName] = await promise
expect(webContents).to.equal(w.webContents)
expect(globalName).to.equal('test')
})
const promise = emittedOnce(app, 'remote-get-builtin')
w.webContents.executeJavaScript(`require('electron').remote.app`)
it('should emit remote-get-builtin event when remote.getBuiltin() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
const [, webContents, moduleName] = await promise
expect(webContents).to.equal(w.webContents)
expect(moduleName).to.equal('app')
})
await w.loadURL('about:blank')
const promise = emittedOnce(app, 'remote-get-builtin')
w.webContents.executeJavaScript(`require('electron').remote.app`)
it('should emit remote-get-current-window event when remote.getCurrentWindow() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
await w.loadURL('about:blank')
const [, webContents, moduleName] = await promise
expect(webContents).to.equal(w.webContents)
expect(moduleName).to.equal('app')
})
const promise = emittedOnce(app, 'remote-get-current-window')
w.webContents.executeJavaScript(`require('electron').remote.getCurrentWindow()`)
it('should emit remote-get-current-window event when remote.getCurrentWindow() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
const [, webContents] = await promise
expect(webContents).to.equal(w.webContents)
})
await w.loadURL('about:blank')
const promise = emittedOnce(app, 'remote-get-current-window')
w.webContents.executeJavaScript(`require('electron').remote.getCurrentWindow()`)
it('should emit remote-get-current-web-contents event when remote.getCurrentWebContents() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
await w.loadURL('about:blank')
const [, webContents] = await promise
expect(webContents).to.equal(w.webContents)
})
const promise = emittedOnce(app, 'remote-get-current-web-contents')
w.webContents.executeJavaScript(`require('electron').remote.getCurrentWebContents()`)
it('should emit remote-get-current-web-contents event when remote.getCurrentWebContents() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
const [, webContents] = await promise
expect(webContents).to.equal(w.webContents)
})
await w.loadURL('about:blank')
const promise = emittedOnce(app, 'remote-get-current-web-contents')
w.webContents.executeJavaScript(`require('electron').remote.getCurrentWebContents()`)
const [, webContents] = await promise
expect(webContents).to.equal(w.webContents)
})
})

View File

@@ -0,0 +1,680 @@
import { contextBridge, BrowserWindow, ipcMain } from 'electron'
import { expect } from 'chai'
import * as fs from 'fs-extra'
import * as os from 'os'
import * as path from 'path'
import { closeWindow } from './window-helpers'
import { emittedOnce } from './events-helpers'
const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'context-bridge')
describe('contextBridge', () => {
let w: BrowserWindow
let dir: string
afterEach(async () => {
await closeWindow(w)
if (dir) await fs.remove(dir)
})
it('should not be accessible when contextIsolation is disabled', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
preload: path.resolve(fixturesPath, 'can-bind-preload.js')
}
})
const [,bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')))
expect(bound).to.equal(false)
})
it('should be accessible when contextIsolation is enabled', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: true,
preload: path.resolve(fixturesPath, 'can-bind-preload.js')
}
})
const [,bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')))
expect(bound).to.equal(true)
})
const generateTests = (useSandbox: boolean) => {
describe(`with sandbox=${useSandbox}`, () => {
const makeBindingWindow = async (bindingCreator: Function) => {
const preloadContent = `const electron_1 = require('electron');
${useSandbox ? '' : `require('v8').setFlagsFromString('--expose_gc');
const gc=require('vm').runInNewContext('gc');
electron_1.contextBridge.exposeInMainWorld('GCRunner', {
run: () => gc()
});`}
(${bindingCreator.toString()})();`
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-spec-preload-'))
dir = tmpDir
await fs.writeFile(path.resolve(tmpDir, 'preload.js'), preloadContent)
w = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: true,
nodeIntegration: true,
sandbox: useSandbox,
preload: path.resolve(tmpDir, 'preload.js')
}
})
await w.loadFile(path.resolve(fixturesPath, 'empty.html'))
}
const callWithBindings = async (fn: Function) => {
return await w.webContents.executeJavaScript(`(${fn.toString()})(window)`)
}
const getGCInfo = async (): Promise<{
functionCount: number
objectCount: number
liveFromValues: number
liveProxyValues: number
}> => {
const [,info] = await emittedOnce(ipcMain, 'gc-info', () => w.webContents.send('get-gc-info'))
return info
}
it('should proxy numbers', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myNumber: 123,
})
})
const result = await callWithBindings((root: any) => {
return root.example.myNumber
})
expect(result).to.equal(123)
})
it('should make properties unwriteable', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myNumber: 123,
})
})
const result = await callWithBindings((root: any) => {
root.example.myNumber = 456
return root.example.myNumber
})
expect(result).to.equal(123)
})
it('should proxy strings', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myString: 'my-words',
})
})
const result = await callWithBindings((root: any) => {
return root.example.myString
})
expect(result).to.equal('my-words')
})
it('should proxy arrays', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myArr: [123, 'my-words'],
})
})
const result = await callWithBindings((root: any) => {
return root.example.myArr
})
expect(result).to.deep.equal([123, 'my-words'])
})
it('should make arrays immutable', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myArr: [123, 'my-words'],
})
})
const immutable = await callWithBindings((root: any) => {
try {
root.example.myArr.push(456)
return false
} catch {
return true
}
})
expect(immutable).to.equal(true)
})
it('should proxy booleans', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myBool: true,
})
})
const result = await callWithBindings((root: any) => {
return root.example.myBool
})
expect(result).to.equal(true)
})
it('should proxy promises and resolve with the correct value', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myPromise: Promise.resolve('i-resolved'),
})
})
const result = await callWithBindings(async (root: any) => {
return await root.example.myPromise
})
expect(result).to.equal('i-resolved')
})
it('should proxy promises and reject with the correct value', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myPromise: Promise.reject('i-rejected'),
})
})
const result = await callWithBindings(async (root: any) => {
try {
await root.example.myPromise
return null
} catch (err) {
return err
}
})
expect(result).to.equal('i-rejected')
})
it('should proxy promises and resolve with the correct value if it resolves later', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myPromise: () => new Promise(r => setTimeout(() => r('delayed'), 20)),
})
})
const result = await callWithBindings(async (root: any) => {
return await root.example.myPromise()
})
expect(result).to.equal('delayed')
})
it('should proxy nested promises correctly', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
myPromise: () => new Promise(r => setTimeout(() => r(Promise.resolve(123)), 20)),
})
})
const result = await callWithBindings(async (root: any) => {
return await root.example.myPromise()
})
expect(result).to.equal(123)
})
it('should proxy methods', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
getNumber: () => 123,
getString: () => 'help',
getBoolean: () => false,
getPromise: async () => 'promise'
})
})
const result = await callWithBindings(async (root: any) => {
return [root.example.getNumber(), root.example.getString(), root.example.getBoolean(), await root.example.getPromise()]
})
expect(result).to.deep.equal([123, 'help', false, 'promise'])
})
it('should proxy methods that are callable multiple times', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
doThing: () => 123
})
})
const result = await callWithBindings(async (root: any) => {
return [root.example.doThing(), root.example.doThing(), root.example.doThing()]
})
expect(result).to.deep.equal([123, 123, 123])
})
it('should proxy methods in the reverse direction', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
callWithNumber: (fn: any) => fn(123),
})
})
const result = await callWithBindings(async (root: any) => {
return root.example.callWithNumber((n: number) => n + 1)
})
expect(result).to.equal(124)
})
it('should proxy promises in the reverse direction', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
getPromiseValue: async (p: Promise<any>) => await p,
})
})
const result = await callWithBindings(async (root: any) => {
return await root.example.getPromiseValue(Promise.resolve('my-proxied-value'))
})
expect(result).to.equal('my-proxied-value')
})
it('should proxy objects with number keys', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
[1]: 123,
[2]: 456,
'3': 789
})
})
const result = await callWithBindings(async (root: any) => {
return [root.example[1], root.example[2], root.example[3], Array.isArray(root.example)]
})
expect(result).to.deep.equal([123, 456, 789, false])
})
it('it should proxy null and undefined correctly', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
values: [null, undefined]
})
})
const result = await callWithBindings((root: any) => {
// Convert to strings as although the context bridge keeps the right value
// IPC does not
return root.example.values.map((val: any) => `${val}`)
})
expect(result).to.deep.equal(['null', 'undefined'])
})
it('should proxy typed arrays and regexps through the serializer', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
arr: new Uint8Array(100),
regexp: /a/g
})
})
const result = await callWithBindings((root: any) => {
return [root.example.arr.__proto__ === Uint8Array.prototype, root.example.regexp.__proto__ === RegExp.prototype]
})
expect(result).to.deep.equal([true, true])
})
it('it should handle recursive objects', async () => {
await makeBindingWindow(() => {
const o: any = { value: 135 }
o.o = o
contextBridge.exposeInMainWorld('example', {
o,
})
})
const result = await callWithBindings((root: any) => {
return [root.example.o.value, root.example.o.o.value, root.example.o.o.o.value]
})
expect(result).to.deep.equal([135, 135, 135])
})
it('it should follow expected simple rules of object identity', async () => {
await makeBindingWindow(() => {
const o: any = { value: 135 }
const sub = { thing: 7 }
o.a = sub
o.b = sub
contextBridge.exposeInMainWorld('example', {
o,
})
})
const result = await callWithBindings((root: any) => {
return root.example.a === root.example.b
})
expect(result).to.equal(true)
})
it('it should follow expected complex rules of object identity', async () => {
await makeBindingWindow(() => {
let first: any = null
contextBridge.exposeInMainWorld('example', {
check: (arg: any) => {
if (first === null) {
first = arg
} else {
return first === arg
}
},
})
})
const result = await callWithBindings((root: any) => {
const o = { thing: 123 }
root.example.check(o)
return root.example.check(o)
})
expect(result).to.equal(true)
})
// Can only run tests which use the GCRunner in non-sandboxed environments
if (!useSandbox) {
it('should release the global hold on methods sent across contexts', async () => {
await makeBindingWindow(() => {
require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
contextBridge.exposeInMainWorld('example', {
getFunction: () => () => 123
})
})
expect((await getGCInfo()).functionCount).to.equal(2)
await callWithBindings(async (root: any) => {
root.x = [root.example.getFunction()]
})
expect((await getGCInfo()).functionCount).to.equal(3)
await callWithBindings(async (root: any) => {
root.x = []
root.GCRunner.run()
})
expect((await getGCInfo()).functionCount).to.equal(2)
})
it('should release the global hold on objects sent across contexts when the object proxy is de-reffed', async () => {
await makeBindingWindow(() => {
require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
let myObj: any
contextBridge.exposeInMainWorld('example', {
setObj: (o: any) => {
myObj = o
},
getObj: () => myObj
})
})
await callWithBindings(async (root: any) => {
root.GCRunner.run()
})
// Initial Setup
let info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
// Create Reference
await callWithBindings(async (root: any) => {
root.x = { value: 123 }
root.example.setObj(root.x)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(4)
expect(info.liveProxyValues).to.equal(4)
expect(info.objectCount).to.equal(8)
// Release Reference
await callWithBindings(async (root: any) => {
root.example.setObj(null)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
})
it('should release the global hold on objects sent across contexts when the object source is de-reffed', async () => {
await makeBindingWindow(() => {
require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
let myObj: any;
contextBridge.exposeInMainWorld('example', {
setObj: (o: any) => {
myObj = o
},
getObj: () => myObj
})
})
await callWithBindings(async (root: any) => {
root.GCRunner.run()
})
// Initial Setup
let info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
// Create Reference
await callWithBindings(async (root: any) => {
root.x = { value: 123 }
root.example.setObj(root.x)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(4)
expect(info.liveProxyValues).to.equal(4)
expect(info.objectCount).to.equal(8)
// Release Reference
await callWithBindings(async (root: any) => {
delete root.x
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
})
it('should not crash when the object source is de-reffed AND the object proxy is de-reffed', async () => {
await makeBindingWindow(() => {
require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', (contextBridge as any).debugGC()))
let myObj: any;
contextBridge.exposeInMainWorld('example', {
setObj: (o: any) => {
myObj = o
},
getObj: () => myObj
})
})
await callWithBindings(async (root: any) => {
root.GCRunner.run()
})
// Initial Setup
let info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
// Create Reference
await callWithBindings(async (root: any) => {
root.x = { value: 123 }
root.example.setObj(root.x)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(4)
expect(info.liveProxyValues).to.equal(4)
expect(info.objectCount).to.equal(8)
// Release Reference
await callWithBindings(async (root: any) => {
delete root.x
root.example.setObj(null)
root.GCRunner.run()
})
info = await getGCInfo()
expect(info.liveFromValues).to.equal(3)
expect(info.liveProxyValues).to.equal(3)
expect(info.objectCount).to.equal(6)
})
}
it('it should not let you overwrite existing exposed things', async () => {
await makeBindingWindow(() => {
let threw = false
contextBridge.exposeInMainWorld('example', {
attempt: 1,
getThrew: () => threw
})
try {
contextBridge.exposeInMainWorld('example', {
attempt: 2,
getThrew: () => threw
})
} catch {
threw = true
}
})
const result = await callWithBindings((root: any) => {
return [root.example.attempt, root.example.getThrew()]
})
expect(result).to.deep.equal([1, true])
})
it('should work with complex nested methods and promises', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
first: (second: Function) => second(async (fourth: Function) => {
return await fourth()
})
})
})
const result = await callWithBindings((root: any) => {
return root.example.first((third: Function) => {
return third(() => Promise.resolve('final value'))
})
})
expect(result).to.equal('final value')
})
it('should throw an error when recursion depth is exceeded', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
doThing: (a: any) => console.log(a)
})
})
let threw = await callWithBindings((root: any) => {
try {
let a: any = []
for (let i = 0; i < 999; i++) {
a = [ a ]
}
root.example.doThing(a)
return false
} catch {
return true
}
})
expect(threw).to.equal(false)
threw = await callWithBindings((root: any) => {
try {
let a: any = []
for (let i = 0; i < 1000; i++) {
a = [ a ]
}
root.example.doThing(a)
return false
} catch {
return true
}
})
expect(threw).to.equal(true)
})
it('should not leak prototypes', async () => {
await makeBindingWindow(() => {
contextBridge.exposeInMainWorld('example', {
number: 123,
string: 'string',
boolean: true,
arr: [123, 'string', true, ['foo']],
getNumber: () => 123,
getString: () => 'string',
getBoolean: () => true,
getArr: () => [123, 'string', true, ['foo']],
getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']]}),
getFunctionFromFunction: async () => () => null,
object: {
number: 123,
string: 'string',
boolean: true,
arr: [123, 'string', true, ['foo']],
getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']]}),
},
receiveArguments: (fn: any) => fn({ key: 'value' })
})
})
const result = await callWithBindings(async (root: any) => {
const { example } = root
let arg: any
example.receiveArguments((o: any) => { arg = o })
const protoChecks = [
[example, Object],
[example.number, Number],
[example.string, String],
[example.boolean, Boolean],
[example.arr, Array],
[example.arr[0], Number],
[example.arr[1], String],
[example.arr[2], Boolean],
[example.arr[3], Array],
[example.arr[3][0], String],
[example.getNumber, Function],
[example.getNumber(), Number],
[example.getString(), String],
[example.getBoolean(), Boolean],
[example.getArr(), Array],
[example.getArr()[0], Number],
[example.getArr()[1], String],
[example.getArr()[2], Boolean],
[example.getArr()[3], Array],
[example.getArr()[3][0], String],
[example.getFunctionFromFunction, Function],
[example.getFunctionFromFunction(), Promise],
[await example.getFunctionFromFunction(), Function],
[example.getPromise(), Promise],
[await example.getPromise(), Object],
[(await example.getPromise()).number, Number],
[(await example.getPromise()).string, String],
[(await example.getPromise()).boolean, Boolean],
[(await example.getPromise()).fn, Function],
[(await example.getPromise()).fn(), String],
[(await example.getPromise()).arr, Array],
[(await example.getPromise()).arr[0], Number],
[(await example.getPromise()).arr[1], String],
[(await example.getPromise()).arr[2], Boolean],
[(await example.getPromise()).arr[3], Array],
[(await example.getPromise()).arr[3][0], String],
[example.object, Object],
[example.object.number, Number],
[example.object.string, String],
[example.object.boolean, Boolean],
[example.object.arr, Array],
[example.object.arr[0], Number],
[example.object.arr[1], String],
[example.object.arr[2], Boolean],
[example.object.arr[3], Array],
[example.object.arr[3][0], String],
[await example.object.getPromise(), Object],
[(await example.object.getPromise()).number, Number],
[(await example.object.getPromise()).string, String],
[(await example.object.getPromise()).boolean, Boolean],
[(await example.object.getPromise()).fn, Function],
[(await example.object.getPromise()).fn(), String],
[(await example.object.getPromise()).arr, Array],
[(await example.object.getPromise()).arr[0], Number],
[(await example.object.getPromise()).arr[1], String],
[(await example.object.getPromise()).arr[2], Boolean],
[(await example.object.getPromise()).arr[3], Array],
[(await example.object.getPromise()).arr[3][0], String],
[arg, Object],
[arg.key, String]
]
return {
protoMatches: protoChecks.map(([a, Constructor]) => a.__proto__ === Constructor.prototype)
}
})
// Every protomatch should be true
expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true))
})
})
}
generateTests(true)
generateTests(false)
})

View File

@@ -21,13 +21,13 @@ export const waitForEvent = (target: EventTarget, eventName: string) => {
* @param {string} eventName
* @return {!Promise<!Array>} With Event as the first item.
*/
export const emittedOnce = (emitter: EventEmitter, eventName: string) => {
return emittedNTimes(emitter, eventName, 1).then(([result]) => result)
export const emittedOnce = (emitter: EventEmitter, eventName: string, trigger?: () => void) => {
return emittedNTimes(emitter, eventName, 1, trigger).then(([result]) => result)
}
export const emittedNTimes = (emitter: EventEmitter, eventName: string, times: number) => {
export const emittedNTimes = async (emitter: EventEmitter, eventName: string, times: number, trigger?: () => void) => {
const events: any[][] = []
return new Promise<any[][]>(resolve => {
const p = new Promise<any[][]>(resolve => {
const handler = (...args: any[]) => {
events.push(args)
if (events.length === times) {
@@ -37,4 +37,8 @@ export const emittedNTimes = (emitter: EventEmitter, eventName: string, times: n
}
emitter.on(eventName, handler)
})
if (trigger) {
await Promise.resolve(trigger())
}
return p
}

View File

@@ -0,0 +1,13 @@
const { contextBridge, ipcRenderer } = require('electron')
console.info(contextBridge)
let bound = false
try {
contextBridge.exposeInMainWorld('test', {})
bound = true
} catch {
// Ignore
}
ipcRenderer.send('context-bridge-bound', bound)

View File

@@ -0,0 +1 @@
<html></html>

View File

@@ -0,0 +1,4 @@
export const ifit = (condition: boolean) => (condition ? it : it.skip)
export const ifdescribe = (condition: boolean) => (condition ? describe : describe.skip)
export const delay = (time: number) => new Promise(r => setTimeout(r, time))

View File

@@ -3090,6 +3090,17 @@ describe('BrowserWindow module', () => {
w.setFullScreen(true)
})
it('does not crash when exiting simpleFullScreen', (done) => {
w.destroy()
w = new BrowserWindow()
w.setSimpleFullScreen(true)
setTimeout(() => {
w.setFullScreen(!w.isFullScreen())
done()
}, 1000)
})
it('should not be changed by setKiosk method', (done) => {
w.destroy()
w = new BrowserWindow()

View File

@@ -373,6 +373,16 @@ describe('crashReporter module', () => {
assert(!('hello' in crashReporter.getParameters()))
})
})
describe('when not started', () => {
it('does not prevent process from crashing', (done) => {
const appPath = path.join(fixtures, 'api', 'cookie-app')
const appProcess = childProcess.spawn(process.execPath, [appPath])
appProcess.once('close', () => {
done()
})
})
})
})
const waitForCrashReport = () => {

View File

@@ -280,7 +280,7 @@ describe('protocol module', () => {
})
describe('protocol.registerFileProtocol', () => {
const filePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'file1')
const filePath = path.join(__dirname, 'fixtures', 'test.asar', 'a.asar', 'file1')
const fileContent = require('fs').readFileSync(filePath)
const normalPath = path.join(__dirname, 'fixtures', 'pages', 'a.html')
const normalContent = require('fs').readFileSync(normalPath)
@@ -394,7 +394,7 @@ describe('protocol module', () => {
})
it('fails when sending unexist-file', (done) => {
const fakeFilePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'not-exist')
const fakeFilePath = path.join(__dirname, 'fixtures', 'test.asar', 'a.asar', 'not-exist')
const handler = (request, callback) => callback(fakeFilePath)
protocol.registerFileProtocol(protocolName, handler, (error) => {
if (error) return done(error)

View File

@@ -571,4 +571,32 @@ describe('remote module', () => {
w.loadURL('about:blank')
})
})
describe('remote references', () => {
it('render-view-deleted is sent when page is destroyed', (done) => {
w = new BrowserWindow({ show: false })
w.webContents.once('render-view-deleted', () => {
done()
})
w.destroy()
})
// The ELECTRON_BROWSER_CONTEXT_RELEASE message relies on this to work.
it('message can be sent on exit when page is being navigated', (done) => {
after(() => { ipcMain.removeAllListeners('SENT_ON_EXIT') })
ipcMain.once('SENT_ON_EXIT', () => {
done()
})
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
})
w.webContents.once('did-finish-load', () => {
w.webContents.loadURL('about:blank')
})
w.loadFile(path.join(fixtures, 'api', 'send-on-exit.html'))
})
})
})

View File

@@ -1279,6 +1279,25 @@ describe('webContents module', () => {
assert.notStrictEqual(data.length, 0)
})
it('does not crash when called multiple times', async () => {
w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true
}
})
await w.loadURL('data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E')
const promises = []
for (let i = 0; i < 2; i++) {
promises.push(w.webContents.printToPDF({}))
}
const results = await Promise.all(promises)
for (const data of results) {
expect(data).to.be.an.instanceof(Buffer).that.is.not.empty()
}
})
// TODO(miniak): remove when promisification is complete
it('can print to PDF (callback)', (done) => {
w.destroy()

File diff suppressed because it is too large Load Diff

View File

@@ -1568,9 +1568,9 @@ describe('font fallback', () => {
const fonts = await getRenderedFonts(html)
expect(fonts).to.be.an('array')
expect(fonts).to.have.length(1)
expect(fonts[0].familyName).to.equal({
'win32': 'Meiryo',
'darwin': 'Hiragino Kaku Gothic ProN'
expect(fonts[0].familyName).to.be.oneOf({
'win32': ['Meiryo', 'Yu Gothic'],
'darwin': ['Hiragino Kaku Gothic ProN']
}[process.platform])
})
})

11
spec/fixtures/api/send-on-exit.html vendored Normal file
View File

@@ -0,0 +1,11 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
const {ipcRenderer} = require('electron')
process.on('exit', () => {
ipcRenderer.send('SENT_ON_EXIT')
})
</script>
</body>
</html>

5
spec/fixtures/crash-app/main.js vendored Normal file
View File

@@ -0,0 +1,5 @@
const { app } = require('electron')
app.on('ready', () => {
process.crash()
})

4
spec/fixtures/crash-app/package.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"name": "electron-crash-app",
"main": "main.js"
}

View File

@@ -1,7 +1,7 @@
const fs = require('fs')
const path = require('path')
const stats = fs.statSync(path.join(__dirname, '..', 'asar', 'a.asar'))
const stats = fs.statSync(path.join(__dirname, '..', 'test.asar', 'a.asar'))
const details = {
isFile: stats.isFile(),

View File

@@ -25,7 +25,7 @@ app.once('ready', () => {
app.exit(1)
})
window.loadFile(path.resolve(__dirname, 'asar', 'video.asar', 'index.html'))
window.loadFile(path.resolve(__dirname, 'test.asar', 'video.asar', 'index.html'))
ipcMain.on('asar-video', (event, message, error) => {
if (message === 'ended') {

Some files were not shown because too many files have changed in this diff Show More