mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c25b619a6 | ||
|
|
c2c25f1ee3 | ||
|
|
8cf8c8fe63 | ||
|
|
61fdda3e8f | ||
|
|
4a5f89d5c7 | ||
|
|
36fd0e9b37 | ||
|
|
512736542b | ||
|
|
b144900c00 | ||
|
|
dabaa7557a | ||
|
|
c6a794d738 | ||
|
|
aa863bc323 | ||
|
|
e3a79d6c52 | ||
|
|
14ba3ac63e | ||
|
|
6a07825c47 | ||
|
|
70b5e673bb | ||
|
|
6359db712a | ||
|
|
268cd3939c | ||
|
|
3ca62d9432 | ||
|
|
9901700a91 | ||
|
|
d5b088bc26 | ||
|
|
a12de693b0 | ||
|
|
e257981a6d | ||
|
|
ffb96acab0 | ||
|
|
fd0b57f219 | ||
|
|
1e50380fab | ||
|
|
31ba6c203e | ||
|
|
03d16f37d2 | ||
|
|
e501930d38 | ||
|
|
28eb7b0532 | ||
|
|
15e611a0ff | ||
|
|
3176e323e4 | ||
|
|
99a0581d0d | ||
|
|
4e417e21b8 | ||
|
|
e472efbea2 | ||
|
|
886e636b13 | ||
|
|
0450ee9524 | ||
|
|
ce31dc3591 | ||
|
|
15c89c8262 | ||
|
|
dbd72b2265 | ||
|
|
10a9e9c043 | ||
|
|
690271e38c | ||
|
|
fc7ef4cc1c | ||
|
|
f2d1abd0e3 | ||
|
|
e3be323962 | ||
|
|
407747b48c | ||
|
|
17b8b551ac |
@@ -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
|
||||
|
||||
4
BUILD.gn
4
BUILD.gn
@@ -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 += [
|
||||
|
||||
@@ -1 +1 @@
|
||||
6.0.8
|
||||
6.1.3
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
87
atom/common/native_mate_converters/once_callback.h
Normal file
87
atom/common/native_mate_converters/once_callback.h
Normal 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_
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
513
atom/renderer/api/atom_api_context_bridge.cc
Normal file
513
atom/renderer/api/atom_api_context_bridge.cc
Normal 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)
|
||||
41
atom/renderer/api/atom_api_context_bridge.h
Normal file
41
atom/renderer/api/atom_api_context_bridge.h
Normal 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_
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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()
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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_
|
||||
|
||||
|
||||
@@ -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 application’s 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 application’s 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
111
docs/api/context-bridge.md
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 #}}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
20
lib/renderer/api/context-bridge.ts
Normal file
20
lib/renderer/api/context-bridge.ts
Normal 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
|
||||
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
@@ -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_) {
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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': {
|
||||
29
patches/node/fix_enable_worker_threads.patch
Normal file
29
patches/node/fix_enable_worker_threads.patch
Normal 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);
|
||||
|
||||
{
|
||||
76
patches/node/fix_uv_fs_mkdir_for_invalid_names.patch
Normal file
76
patches/node/fix_uv_fs_mkdir_for_invalid_names.patch
Normal 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)
|
||||
158
patches/node/fsevents-regression-in-watching.patch
Normal file
158
patches/node/fsevents-regression-in-watching.patch
Normal 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);
|
||||
120
patches/node/fsevents-stop-using-fsevents-to-watch-files.patch
Normal file
120
patches/node/fsevents-stop-using-fsevents-to-watch-files.patch
Normal 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);
|
||||
@@ -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
|
||||
@@ -225,7 +225,6 @@ function runRelease (targetBranch, options) {
|
||||
} else {
|
||||
buildCircleCI(targetBranch, options)
|
||||
buildAppVeyor(targetBranch, options)
|
||||
buildVSTS(targetBranch, options)
|
||||
}
|
||||
console.log(`${jobRequestedCount} jobs were requested.`)
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 }
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
680
spec-main/api-context-bridge-spec.ts
Normal file
680
spec-main/api-context-bridge-spec.ts
Normal 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)
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
|
||||
13
spec-main/fixtures/api/context-bridge/can-bind-preload.js
Normal file
13
spec-main/fixtures/api/context-bridge/can-bind-preload.js
Normal 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)
|
||||
1
spec-main/fixtures/api/context-bridge/empty.html
Normal file
1
spec-main/fixtures/api/context-bridge/empty.html
Normal file
@@ -0,0 +1 @@
|
||||
<html></html>
|
||||
4
spec-main/spec-helpers.ts
Normal file
4
spec-main/spec-helpers.ts
Normal 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))
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
@@ -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
11
spec/fixtures/api/send-on-exit.html
vendored
Normal 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
5
spec/fixtures/crash-app/main.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
const { app } = require('electron')
|
||||
|
||||
app.on('ready', () => {
|
||||
process.crash()
|
||||
})
|
||||
4
spec/fixtures/crash-app/package.json
vendored
Normal file
4
spec/fixtures/crash-app/package.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "electron-crash-app",
|
||||
"main": "main.js"
|
||||
}
|
||||
2
spec/fixtures/module/no-asar.js
vendored
2
spec/fixtures/module/no-asar.js
vendored
@@ -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(),
|
||||
|
||||
2
spec/fixtures/no-proprietary-codecs.js
vendored
2
spec/fixtures/no-proprietary-codecs.js
vendored
@@ -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
Reference in New Issue
Block a user