Merge branch 'master' into use-dot-env

This commit is contained in:
John Kleinschmidt 2018-07-13 16:02:49 -04:00 committed by GitHub
commit 6ea0e73b47
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 506 additions and 308 deletions

View file

@ -217,7 +217,8 @@ static_library("electron_lib") {
"//components/printing/common",
"//components/security_state/content",
"//components/viz/service",
"//content/public/browser",
"//content/public/app:both",
"//content/public/child",
"//device/geolocation",
"//gin",
"//net:net_resources",
@ -240,6 +241,14 @@ static_library("electron_lib") {
"brightray",
"build/node",
]
include_dirs = [
"chromium_src",
".",
"$target_gen_dir",
# TODO(nornagon): replace usage of SchemeRegistry by an actually exported
# API of blink, then delete this include dir.
"//third_party/WebKit/Source",
]
if (enable_desktop_capturer) {
deps += [ "//third_party/webrtc/modules/desktop_capture" ]
}
@ -274,20 +283,15 @@ static_library("electron_lib") {
defines = [
# Disable warnings for g_settings_list_schemas.
"GLIB_DISABLE_DEPRECATION_WARNINGS",
]
if (is_component_build) {
defines += [
# Import V8 symbols from shared library (node.dll / libnode.so)
"USING_V8_SHARED",
"USING_V8_PLATFORM_SHARED",
"USING_V8_BASE_SHARED",
]
include_dirs = [
"chromium_src",
".",
"$target_gen_dir",
# TODO(nornagon): replace usage of SchemeRegistry by an actually exported
# API of blink, then delete this include dir.
"//third_party/WebKit/Source",
]
}
if (is_linux || is_win) {
deps += [ "//third_party/breakpad:client" ]
include_dirs += [
@ -433,6 +437,7 @@ if (is_mac) {
]
}
if (is_component_build) {
bundle_data("electron_framework_libraries") {
public_deps = [ "build/node" ]
sources = [
@ -442,6 +447,7 @@ if (is_mac) {
"{{bundle_contents_dir}}/Libraries/{{source_file_part}}"
]
}
}
bundle_data("electron_crashpad_helper") {
sources = [
@ -465,13 +471,15 @@ if (is_mac) {
deps = [
"//base",
"//base:i18n",
":electron_framework_libraries",
":electron_framework_resources",
":electron_xibs",
]
if (!is_mas_build) {
deps += [ ":electron_crashpad_helper" ]
}
if (is_component_build) {
deps += [ ":electron_framework_libraries" ]
}
info_plist = "atom/common/resources/mac/Info.plist"
extra_substitutions = [
"ATOM_BUNDLE_ID=$electron_mac_bundle_id.framework",

2
DEPS
View file

@ -2,7 +2,7 @@ vars = {
'chromium_version':
'66.0.3359.181',
'libchromiumcontent_revision':
'0628cd925e513bfd873524621a91834b366899f1',
'a55a9ce536db60702630c4b9d94dcb2145fc3b24',
'node_version':
'v10.2.0-35-g4879332def',

View file

@ -50,11 +50,6 @@ class SystemPreferences : public mate::EventEmitter<SystemPreferences>
#if defined(OS_WIN)
bool IsAeroGlassEnabled();
typedef HRESULT(STDAPICALLTYPE* DwmGetColorizationColor)(DWORD*, BOOL*);
DwmGetColorizationColor dwmGetColorizationColor =
(DwmGetColorizationColor)GetProcAddress(LoadLibraryW(L"dwmapi.dll"),
"DwmGetColorizationColor");
std::string GetAccentColor();
std::string GetColor(const std::string& color, mate::Arguments* args);

View file

@ -2,6 +2,7 @@
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include <dwmapi.h>
#include <iomanip>
#include "atom/browser/api/atom_api_system_preferences.h"
@ -38,7 +39,7 @@ std::string SystemPreferences::GetAccentColor() {
DWORD color = 0;
BOOL opaque = FALSE;
if (FAILED(dwmGetColorizationColor(&color, &opaque))) {
if (FAILED(DwmGetColorizationColor(&color, &opaque))) {
return "";
}

View file

@ -1434,6 +1434,10 @@ bool WebContents::IsAudioMuted() {
return web_contents()->IsAudioMuted();
}
bool WebContents::IsCurrentlyAudible() {
return web_contents()->IsCurrentlyAudible();
}
void WebContents::Print(mate::Arguments* args) {
PrintSettings settings = {false, false, base::string16()};
if (args->Length() >= 1 && !args->GetNext(&settings)) {
@ -2018,6 +2022,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
.SetMethod("setIgnoreMenuShortcuts", &WebContents::SetIgnoreMenuShortcuts)
.SetMethod("setAudioMuted", &WebContents::SetAudioMuted)
.SetMethod("isAudioMuted", &WebContents::IsAudioMuted)
.SetMethod("isCurrentlyAudible", &WebContents::IsCurrentlyAudible)
.SetMethod("undo", &WebContents::Undo)
.SetMethod("redo", &WebContents::Redo)
.SetMethod("cut", &WebContents::Cut)

View file

@ -143,6 +143,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
void SetIgnoreMenuShortcuts(bool ignore);
void SetAudioMuted(bool muted);
bool IsAudioMuted();
bool IsCurrentlyAudible();
void Print(mate::Arguments* args);
std::vector<printing::PrinterBasicInfo> GetPrinterList();
void SetEmbedder(const WebContents* embedder);

View file

@ -26,7 +26,8 @@ FrameSubscriber::FrameSubscriber(v8::Isolate* isolate,
: content::WebContentsObserver(web_contents),
isolate_(isolate),
callback_(callback),
only_dirty_(only_dirty) {}
only_dirty_(only_dirty),
weak_ptr_factory_(this) {}
FrameSubscriber::~FrameSubscriber() = default;
@ -64,7 +65,7 @@ void FrameSubscriber::DidReceiveCompositorFrame() {
view->CopyFromSurface(
gfx::Rect(), view->GetViewBounds().size(),
base::BindOnce(&FrameSubscriber::Done, base::Unretained(this),
base::BindOnce(&FrameSubscriber::Done, weak_ptr_factory_.GetWeakPtr(),
GetDamageRect()));
}

View file

@ -8,6 +8,7 @@
#include "content/public/browser/web_contents.h"
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "components/viz/common/frame_sinks/copy_output_result.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/gfx/image/image.h"
@ -39,6 +40,8 @@ class FrameSubscriber : public content::WebContentsObserver {
FrameCaptureCallback callback_;
bool only_dirty_;
base::WeakPtrFactory<FrameSubscriber> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(FrameSubscriber);
};

View file

@ -12,17 +12,27 @@
#include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/node_includes.h"
#include "base/hash.h"
#include "base/process/process_handle.h"
#include "base/strings/stringprintf.h"
#include "native_mate/dictionary.h"
#include "url/origin.h"
#include "v8/include/v8-profiler.h"
// This is defined in later versions of Chromium, remove this if you see
// compiler complaining duplicate defines.
#if defined(OS_WIN) || defined(OS_FUCHSIA)
#define CrPRIdPid "ld"
#else
#define CrPRIdPid "d"
#endif
namespace std {
// The hash function used by DoubleIDWeakMap.
template <typename Type1, typename Type2>
struct hash<std::pair<Type1, Type2>> {
std::size_t operator()(std::pair<Type1, Type2> value) const {
return base::HashInts<Type1, Type2>(value.first, value.second);
return base::HashInts(base::Hash(value.first), value.second);
}
};
@ -90,6 +100,16 @@ int32_t GetObjectHash(v8::Local<v8::Object> object) {
return object->GetIdentityHash();
}
std::string GetContextID(v8::Isolate* isolate) {
// When a page is reloaded, V8 and blink may have optimizations that do not
// free blink::WebLocalFrame and v8::Context and reuse them for the new page,
// while we always recreate node::Environment when a page is loaded.
// So the only reliable way to return an identity for a page, is to return the
// address of the node::Environment instance.
node::Environment* env = node::Environment::GetCurrent(isolate);
return base::StringPrintf("%" CrPRIdPid "-%p", base::GetCurrentProcId(), env);
}
void TakeHeapSnapshot(v8::Isolate* isolate) {
isolate->GetHeapProfiler()->TakeHeapSnapshot();
}
@ -112,12 +132,14 @@ void Initialize(v8::Local<v8::Object> exports,
dict.SetMethod("setHiddenValue", &SetHiddenValue);
dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue);
dict.SetMethod("getObjectHash", &GetObjectHash);
dict.SetMethod("getContextId", &GetContextID);
dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot);
dict.SetMethod("setRemoteCallbackFreer", &atom::RemoteCallbackFreer::BindTo);
dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo);
dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap<int32_t>::Create);
dict.SetMethod("createDoubleIDWeakMap",
&atom::api::KeyWeakMap<std::pair<int64_t, int32_t>>::Create);
dict.SetMethod(
"createDoubleIDWeakMap",
&atom::api::KeyWeakMap<std::pair<std::string, int32_t>>::Create);
dict.SetMethod("requestGarbageCollectionForTesting",
&RequestGarbageCollectionForTesting);
dict.SetMethod("isSameOrigin", &IsSameOrigin);

View file

@ -15,17 +15,20 @@ namespace atom {
// static
void RemoteCallbackFreer::BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
const std::string& context_id,
int object_id,
content::WebContents* web_contents) {
new RemoteCallbackFreer(isolate, target, object_id, web_contents);
new RemoteCallbackFreer(isolate, target, context_id, object_id, web_contents);
}
RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
v8::Local<v8::Object> target,
const std::string& context_id,
int object_id,
content::WebContents* web_contents)
: ObjectLifeMonitor(isolate, target),
content::WebContentsObserver(web_contents),
context_id_(context_id),
object_id_(object_id) {}
RemoteCallbackFreer::~RemoteCallbackFreer() {}
@ -34,6 +37,7 @@ void RemoteCallbackFreer::RunDestructor() {
base::string16 channel =
base::ASCIIToUTF16("ELECTRON_RENDERER_RELEASE_CALLBACK");
base::ListValue args;
args.AppendString(context_id_);
args.AppendInteger(object_id_);
auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) {

View file

@ -4,6 +4,9 @@
#ifndef ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_
#define ATOM_COMMON_API_REMOTE_CALLBACK_FREER_H_
#include <string>
#include "atom/common/api/object_life_monitor.h"
#include "content/public/browser/web_contents_observer.h"
@ -14,12 +17,14 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
public:
static void BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
const std::string& context_id,
int object_id,
content::WebContents* web_conents);
protected:
RemoteCallbackFreer(v8::Isolate* isolate,
v8::Local<v8::Object> target,
const std::string& context_id,
int object_id,
content::WebContents* web_conents);
~RemoteCallbackFreer() override;
@ -30,6 +35,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
void RenderViewDeleted(content::RenderViewHost*) override;
private:
std::string context_id_;
int object_id_;
DISALLOW_COPY_AND_ASSIGN(RemoteCallbackFreer);

View file

@ -29,14 +29,17 @@ content::RenderFrame* GetCurrentRenderFrame() {
// static
void RemoteObjectFreer::BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
const std::string& context_id,
int object_id) {
new RemoteObjectFreer(isolate, target, object_id);
new RemoteObjectFreer(isolate, target, context_id, object_id);
}
RemoteObjectFreer::RemoteObjectFreer(v8::Isolate* isolate,
v8::Local<v8::Object> target,
const std::string& context_id,
int object_id)
: ObjectLifeMonitor(isolate, target),
context_id_(context_id),
object_id_(object_id),
routing_id_(MSG_ROUTING_NONE) {
content::RenderFrame* render_frame = GetCurrentRenderFrame();
@ -56,6 +59,7 @@ void RemoteObjectFreer::RunDestructor() {
base::string16 channel = base::ASCIIToUTF16("ipc-message");
base::ListValue args;
args.AppendString("ELECTRON_BROWSER_DEREFERENCE");
args.AppendString(context_id_);
args.AppendInteger(object_id_);
render_frame->Send(new AtomFrameHostMsg_Message(render_frame->GetRoutingID(),
channel, args));

View file

@ -5,6 +5,8 @@
#ifndef ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_
#define ATOM_COMMON_API_REMOTE_OBJECT_FREER_H_
#include <string>
#include "atom/common/api/object_life_monitor.h"
namespace atom {
@ -13,17 +15,20 @@ class RemoteObjectFreer : public ObjectLifeMonitor {
public:
static void BindTo(v8::Isolate* isolate,
v8::Local<v8::Object> target,
const std::string& context_id,
int object_id);
protected:
RemoteObjectFreer(v8::Isolate* isolate,
v8::Local<v8::Object> target,
const std::string& context_id,
int object_id);
~RemoteObjectFreer() override;
void RunDestructor() override;
private:
std::string context_id_;
int object_id_;
int routing_id_;

View file

@ -9,7 +9,9 @@
// Include common headers for using node APIs.
#ifdef NODE_SHARED_MODE
#define BUILDING_NODE_EXTENSION
#endif
// The following define makes sure that we do not include the macros
// again. But we still need the tracing functions, so declaring them.

View file

@ -240,33 +240,6 @@ bool ShowItemInFolder(const base::FilePath& full_path) {
if (dir.empty())
return false;
typedef HRESULT(WINAPI * SHOpenFolderAndSelectItemsFuncPtr)(
PCIDLIST_ABSOLUTE pidl_Folder, UINT cidl, PCUITEMID_CHILD_ARRAY pidls,
DWORD flags);
static SHOpenFolderAndSelectItemsFuncPtr open_folder_and_select_itemsPtr =
NULL;
static bool initialize_open_folder_proc = true;
if (initialize_open_folder_proc) {
initialize_open_folder_proc = false;
// The SHOpenFolderAndSelectItems API is exposed by shell32 version 6
// and does not exist in Win2K. We attempt to retrieve this function export
// from shell32 and if it does not exist, we just invoke ShellExecute to
// open the folder thus losing the functionality to select the item in
// the process.
HMODULE shell32_base = GetModuleHandle(L"shell32.dll");
if (!shell32_base) {
NOTREACHED() << " " << __FUNCTION__ << "(): Can't open shell32.dll";
return false;
}
open_folder_and_select_itemsPtr =
reinterpret_cast<SHOpenFolderAndSelectItemsFuncPtr>(
GetProcAddress(shell32_base, "SHOpenFolderAndSelectItems"));
}
if (!open_folder_and_select_itemsPtr) {
return ui::win::OpenFolderViaShell(dir);
}
Microsoft::WRL::ComPtr<IShellFolder> desktop;
HRESULT hr = SHGetDesktopFolder(desktop.GetAddressOf());
if (FAILED(hr))
@ -290,8 +263,8 @@ bool ShowItemInFolder(const base::FilePath& full_path) {
const ITEMIDLIST* highlight[] = {file_item};
hr = (*open_folder_and_select_itemsPtr)(dir_item, arraysize(highlight),
highlight, NULL);
hr = SHOpenFolderAndSelectItems(dir_item, arraysize(highlight), highlight,
NULL);
if (!FAILED(hr))
return true;

View file

@ -81,11 +81,19 @@ base::CommandLine::StringVector GetArgv() {
return base::CommandLine::ForCurrentProcess()->argv();
}
v8::Local<v8::Value> CreatePreloadScript(v8::Isolate* isolate,
v8::Local<v8::String> preloadSrc) {
auto script = v8::Script::Compile(preloadSrc);
auto func = script->Run();
return func;
}
void InitializeBindings(v8::Local<v8::Object> binding,
v8::Local<v8::Context> context) {
auto* isolate = context->GetIsolate();
mate::Dictionary b(isolate, binding);
b.SetMethod("get", GetBinding);
b.SetMethod("createPreloadScript", CreatePreloadScript);
b.SetMethod("crash", AtomBindings::Crash);
b.SetMethod("hang", AtomBindings::Hang);
b.SetMethod("getArgv", GetArgv);

View file

@ -13,7 +13,6 @@ action("configure_node") {
args = [
"--enable-static",
"--release-urlbase=https://atom.io/download/electron",
"--shared",
"--shared-openssl",
"--shared-openssl-includes=" + rebase_path("//third_party/boringssl/src/include"),
"--shared-openssl-libname=boringssl" + ssl_libname_suffix,
@ -29,6 +28,9 @@ action("configure_node") {
"--config-out-dir=" + rebase_path(target_gen_dir),
"--no-run-gyp",
]
if (is_component_build) {
args += [ "--shared" ]
}
outputs = [
"$target_gen_dir/config.gypi",
]
@ -75,6 +77,7 @@ action("gyp_node") {
"-D", "llvm_dir=" + rebase_path("//third_party/llvm-build/Release+Asserts"),
"-D", "libcxx_dir=" + rebase_path("//buildtools/third_party/libc++"),
"-D", "libcxxabi_dir=" + rebase_path("//buildtools/third_party/libc++abi"),
"-D", "is_component_build=$is_component_build",
"-Goutput_dir=./$target_out_dir", # bizarrely, gyp generates from the build root instead of from cwd
"-fninja",
rebase_path("//third_party/electron_node/node.gyp", root_build_dir),
@ -93,16 +96,53 @@ action("build_node") {
script = "//electron/build/run-ninja.py"
args = [
"-C", rebase_path(target_out_dir, root_build_dir) + "/$node_configuration",
"node_lib"
"node_lib",
"libuv", "nghttp2", "cares", "http_parser", "zlib"
]
if (is_mac) {
if (is_component_build) {
outputs = [ "$target_out_dir/$node_configuration/libnode.dylib" ]
} else {
outputs = [
"$target_out_dir/$node_configuration/libnode.a",
"$target_out_dir/$node_configuration/libcares.a",
"$target_out_dir/$node_configuration/libhttp_parser.a",
"$target_out_dir/$node_configuration/libnghttp2.a",
"$target_out_dir/$node_configuration/libuv.a",
"$target_out_dir/$node_configuration/libzlib.a",
]
}
}
if (is_linux) {
if (is_component_build) {
outputs = [ "$target_out_dir/$node_configuration/lib/libnode.so" ]
} else {
outputs = [
"$target_out_dir/$node_configuration/obj/third_party/electron_node/libnode.a",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/uv/libuv.a",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/nghttp2/libnghttp2.a",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/cares/libcares.a",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/http_parser/libhttp_parser.a",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/zlib/libzlib.a",
]
}
}
if (is_win) {
outputs = [ "$target_out_dir/$node_configuration/node.dll.lib", "$target_out_dir/$node_configuration/node.dll" ]
if (is_component_build) {
outputs = [
"$target_out_dir/$node_configuration/node.dll.lib",
"$target_out_dir/$node_configuration/node.dll",
]
} else {
outputs = [
"$target_out_dir/$node_configuration/obj/third_party/electron_node/node.lib",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/uv/libuv.lib",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/nghttp2/nghttp2.lib",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/cares/cares.lib",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/http_parser/http_parser.lib",
"$target_out_dir/$node_configuration/obj/third_party/electron_node/deps/zlib/zlib.lib",
]
}
}
}
@ -120,17 +160,25 @@ config("node_config") {
"//third_party/electron_node/deps/uv/include",
"//third_party/electron_node/deps/cares/include",
]
if (is_win && is_component_build) {
# Windows builds need both the .dll and the .dll.lib copied, but only the
# .dll.lib goes in the `libs` list.
libs = [ node_libs[0] ]
} else {
libs = node_libs
}
cflags_cc = [
"-Wno-deprecated-declarations",
]
defines = [
# We need to access internal implementations of Node.
"NODE_WANT_INTERNALS=1",
"NODE_SHARED_MODE",
"HAVE_OPENSSL=1",
"HAVE_INSPECTOR=1",
]
if (is_component_build) {
defines += [ "NODE_SHARED_MODE" ]
}
}
group("node") {

View file

@ -5,6 +5,8 @@
'v8_enable_inspector': 1,
'shlib_suffix': '<(shlib_suffix_gn)',
'is_component_build%': 'false',
},
'conditions': [
['OS=="linux"', {
@ -32,7 +34,13 @@
}]
]
}
}]
}],
['OS=="win"', {
'make_global_settings': [
['CC', '<(llvm_dir)/bin/clang-cl'],
# Also defining CXX doesn't appear to be necessary.
]
}],
],
'target_defaults': {
'target_conditions': [
@ -47,9 +55,12 @@
'EVP_CTRL_AEAD_SET_IVLEN=EVP_CTRL_GCM_SET_IVLEN',
'EVP_CTRL_CCM_SET_TAG=EVP_CTRL_GCM_SET_TAG',
'EVP_CTRL_AEAD_GET_TAG=EVP_CTRL_GCM_GET_TAG',
'WIN32_LEAN_AND_MEAN',
],
'conditions': [
['OS=="win"', {
'conditions': [
['is_component_build=="true"', {
'libraries': [
'-lv8.dll',
'-lv8_libbase.dll',
@ -70,7 +81,11 @@
],
},
},
}]
],
}, {
'conditions': [
['is_component_build=="true"', {
'libraries': [
'-lv8',
'-lv8_libbase',
@ -79,6 +94,8 @@
]
}]
]
}]
]
}],
],
},

View file

@ -832,6 +832,10 @@ Mute the audio on the current web page.
Returns `Boolean` - Whether this page has been muted.
#### `contents.isCurrentlyAudible()`
Returns `Boolean` - Whether audio is currently playing.
#### `contents.setZoomFactor(factor)`
* `factor` Number - Zoom factor.

View file

@ -450,6 +450,10 @@ Set guest page muted.
Returns `Boolean` - Whether guest page has been muted.
#### `<webview>.isCurrentlyAudible()`
Returns `Boolean` - Whether audio is currently playing.
### `<webview>.undo()`
Executes editing command `undo` in page.

View file

@ -17,11 +17,15 @@ Create a token from https://windows-ci.electronjs.org/api-token
If you don't have an account, ask a team member to add you.
* `CIRCLE_TOKEN`:
Create a token from "Personal API Tokens" at https://circleci.com/account/api
* `VSTS_TOKEN`:
Create a Personal Access Token at https://github.visualstudio.com/_usersSettings/tokens
with the scope of `Build (read and execute)`.
Once you've generated these tokens, put them in a `.env` file in the root directory
of the project. This file is gitignored, and will be loaded into the
environment by the release scripts.
## Determine which branch to release from
- **If releasing beta,** run the scripts below from `master`.
@ -87,8 +91,11 @@ $ ./script/bump-version.py --bump minor --dry-run
The `prepare-release` script will trigger the builds via API calls.
To monitor the build progress, see the following pages:
- [circleci.com/gh/electron/electron](https://circleci.com/gh/electron) for OS X and Linux
- [windows-ci.electronjs.org/project/AppVeyor/electron](https://windows-ci.electronjs.org/project/AppVeyor/electron) for Windows
- [electron-release-mas-x64](https://github.visualstudio.com/electron/_build/index?context=allDefinitions&path=%5C&definitionId=19&_a=completed) for MAS builds.
- [electron-release-osx-x64](https://github.visualstudio.com/electron/_build/index?context=allDefinitions&path=%5C&definitionId=18&_a=completed) for OSX builds.
- [circleci.com/gh/electron/electron](https://circleci.com/gh/electron) for Linux builds.
- [windows-ci.electronjs.org/project/AppVeyor/electron-39ng6](https://windows-ci.electronjs.org/project/AppVeyor/electron-39ng6) for Windows 32-bit builds.
- [windows-ci.electronjs.org/project/AppVeyor/electron](https://windows-ci.electronjs.org/project/AppVeyor/electron) for Windows 64-bit builds.
## Compile release notes
@ -195,35 +202,13 @@ under the `beta` tag and can be installed via `npm install electron@beta`.
1. Visit [the releases page] and you'll see a new draft release with placeholder
release notes.
2. Edit the release and add release notes.
3. Uncheck the `prerelease` checkbox if you're publishing a stable release;
leave it checked for beta releases.
4. Click 'Save draft'. **Do not click 'Publish release'!**
5. Wait for all builds to pass before proceeding.
6. In the `release` branch, verify that the release's files have been created:
3. Click 'Save draft'. **Do not click 'Publish release'!**
4. Wait for all builds to pass before proceeding.
5. In the branch, verify that the release's files have been created:
```sh
$ git rev-parse --abbrev-ref HEAD
release
$ npm run release -- --validateRelease
```
## Merge temporary branch (pre-2-0-x branches only)
Once the release builds have finished, merge the `release` branch back into
the source release branch using the `merge-release` script.
If the branch cannot be successfully merged back this script will automatically
rebase the `release` branch and push the changes which will trigger the release
builds again, which means you will need to wait for the release builds to run
again before proceeding.
### Merging back into master
```sh
npm run merge-release -- master
```
### Merging back into old release branch
```sh
npm run merge-release -- 1-7-x
```
## Publish the release
Once the merge has finished successfully, run the `release` script
@ -237,7 +222,6 @@ on Windows by node-gyp to build native modules.
5. Validate that all of the required files are present on GitHub and S3 and have
the correct checksums as specified in the SHASUMS files.
6. Publish the release on GitHub
7. Delete the `release` branch.
## Publish to npm
@ -267,16 +251,39 @@ electron
$ npm run publish-to-npm
```
Note: In general you should be using the latest Node during this
process; however, older versions of the `publish-to-npm` script
may have trouble with Node 7 or higher. If you have trouble with
this in an older branch, try running with an older version of Node,
e.g. a 6.x LTS.
[the releases page]: https://github.com/electron/electron/releases
[this bump commit]: https://github.com/electron/electron/commit/78ec1b8f89b3886b856377a1756a51617bc33f5a
[versioning]: /docs/tutorial/electron-versioning.md
# Troubleshooting
## Rerun broken builds
If a release build fails for some reason, you can use `script/ci-release-build.js` to rerun a release build:
### Rerun all linux builds:
```sh
node script/ci-release-build.js --ci=CircleCI --ghRelease TARGET_BRANCH
(TARGET_BRANCH) is the branch you are releasing from.
```
### Rerun all macOS builds:
```sh
node script/ci-release-build.js --ci=VSTS --ghRelease TARGET_BRANCH
(TARGET_BRANCH) is the branch you are releasing from.
```
### Rerun all Windows builds:
```sh
node script/ci-release-build.js --ci=AppVeyor --ghRelease TARGET_BRANCH
(TARGET_BRANCH) is the branch you are releasing from.
```
Additionally you can pass a job name to the script to run an individual job, eg:
````sh
node script/ci-release-build.js --ci=AppVeyor --ghRelease --job=electron-x64 TARGET_BRANCH
```
## Fix missing binaries of a release manually
In the case of a corrupted release with broken CI machines, we might have to

View file

@ -7,7 +7,7 @@ and then enable it in your application.
## Prepare a Copy of Flash Plugin
On macOS and Linux, the details of the Pepper Flash plugin can be found by
navigating to `chrome://plugins` in the Chrome browser. Its location and version
navigating to `chrome://flash` in the Chrome browser. Its location and version
are useful for Electron's Pepper Flash support. You can also copy it to another
location.

View file

@ -363,6 +363,7 @@
],
'link_settings': {
'libraries': [
'-ldwmapi.lib',
'-limm32.lib',
'-lgdi32.lib',
'-loleacc.lib',

View file

@ -17,16 +17,15 @@ class ObjectsRegistry {
// Register a new object and return its assigned ID. If the object is already
// registered then the already assigned ID would be returned.
add (webContents, obj) {
add (webContents, contextId, obj) {
// Get or assign an ID to the object.
const id = this.saveToStorage(obj)
// Add object to the set of referenced objects.
const webContentsId = webContents.getId()
let owner = this.owners[webContentsId]
let owner = this.owners[contextId]
if (!owner) {
owner = this.owners[webContentsId] = new Set()
this.registerDeleteListener(webContents, webContentsId)
owner = this.owners[contextId] = new Set()
this.registerDeleteListener(webContents, contextId)
}
if (!owner.has(id)) {
owner.add(id)
@ -43,25 +42,26 @@ class ObjectsRegistry {
}
// Dereference an object according to its ID.
remove (webContentsId, id) {
// Note that an object may be double-freed (cleared when page is reloaded, and
// then garbage collected in old page).
remove (contextId, id) {
let owner = this.owners[contextId]
if (owner) {
// Remove the reference in owner.
owner.delete(id)
// Dereference from the storage.
this.dereference(id)
// Also remove the reference in owner.
let owner = this.owners[webContentsId]
if (owner) {
owner.delete(id)
}
}
// Clear all references to objects refrenced by the WebContents.
clear (webContentsId) {
let owner = this.owners[webContentsId]
clear (contextId) {
let owner = this.owners[contextId]
if (!owner) return
for (let id of owner) this.dereference(id)
delete this.owners[webContentsId]
delete this.owners[contextId]
}
// Private: Saves the object into storage and assigns an ID for it.
@ -92,12 +92,12 @@ class ObjectsRegistry {
}
// Private: Clear the storage when webContents is reloaded/navigated.
registerDeleteListener (webContents, webContentsId) {
registerDeleteListener (webContents, contextId) {
const processId = webContents.getProcessId()
const listener = (event, deletedProcessId) => {
if (deletedProcessId === processId) {
webContents.removeListener('render-view-deleted', listener)
this.clear(webContentsId)
this.clear(contextId)
}
}
webContents.on('render-view-deleted', listener)

View file

@ -56,7 +56,7 @@ let getObjectPrototype = function (object) {
}
// Convert a real value into meta data.
let valueToMeta = function (sender, value, optimizeSimpleObject = false) {
let valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
// Determine the type of value.
const meta = { type: typeof value }
if (meta.type === 'object') {
@ -84,14 +84,14 @@ let valueToMeta = function (sender, value, optimizeSimpleObject = false) {
// Fill the meta object according to value's type.
if (meta.type === 'array') {
meta.members = value.map((el) => valueToMeta(sender, el, optimizeSimpleObject))
meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
} else if (meta.type === 'object' || meta.type === 'function') {
meta.name = value.constructor ? value.constructor.name : ''
// Reference the original value if it's an object, because when it's
// passed to renderer we would assume the renderer keeps a reference of
// it.
meta.id = objectsRegistry.add(sender, value)
meta.id = objectsRegistry.add(sender, contextId, value)
meta.members = getObjectMembers(value)
meta.proto = getObjectPrototype(value)
} else if (meta.type === 'buffer') {
@ -101,7 +101,7 @@ let valueToMeta = function (sender, value, optimizeSimpleObject = false) {
// Instead they should appear in the renderer process
value.then(function () {}, function () {})
meta.then = valueToMeta(sender, function (onFulfilled, onRejected) {
meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
value.then(onFulfilled, onRejected)
})
} else if (meta.type === 'error') {
@ -132,12 +132,12 @@ const plainObjectToMeta = function (obj) {
}
// Convert Error into meta data.
const exceptionToMeta = function (sender, error) {
const exceptionToMeta = function (sender, contextId, error) {
return {
type: 'exception',
message: error.message,
stack: error.stack || error,
cause: valueToMeta(sender, error.cause)
cause: valueToMeta(sender, contextId, error.cause)
}
}
@ -169,7 +169,7 @@ const removeRemoteListenersAndLogWarning = (sender, meta, callIntoRenderer) => {
}
// Convert array of meta data from renderer into array of real values.
const unwrapArgs = function (sender, args) {
const unwrapArgs = function (sender, contextId, args) {
const metaToValue = function (meta) {
switch (meta.type) {
case 'value':
@ -177,7 +177,7 @@ const unwrapArgs = function (sender, args) {
case 'remote-object':
return objectsRegistry.get(meta.id)
case 'array':
return unwrapArgs(sender, meta.value)
return unwrapArgs(sender, contextId, meta.value)
case 'buffer':
return bufferUtils.metaToBuffer(meta.value)
case 'date':
@ -201,26 +201,26 @@ const unwrapArgs = function (sender, args) {
return returnValue
}
case 'function': {
// Merge webContentsId and meta.id, since meta.id can be the same in
// Merge contextId and meta.id, since meta.id can be the same in
// different webContents.
const webContentsId = sender.getId()
const objectId = [webContentsId, meta.id]
const objectId = [contextId, meta.id]
// Cache the callbacks in renderer.
if (rendererFunctions.has(objectId)) {
return rendererFunctions.get(objectId)
}
const webContentsId = sender.getId()
let callIntoRenderer = function (...args) {
if (!sender.isDestroyed() && webContentsId === sender.getId()) {
sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args))
sender.send('ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
} else {
removeRemoteListenersAndLogWarning(this, meta, callIntoRenderer)
}
}
Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
v8Util.setRemoteCallbackFreer(callIntoRenderer, meta.id, sender)
v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
rendererFunctions.set(objectId, callIntoRenderer)
return callIntoRenderer
}
@ -233,18 +233,18 @@ const unwrapArgs = function (sender, args) {
// Call a function and send reply asynchronously if it's a an asynchronous
// style function and the caller didn't pass a callback.
const callFunction = function (event, func, caller, args) {
const callFunction = function (event, contextId, func, caller, args) {
const funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
const funcPassedCallback = typeof args[args.length - 1] === 'function'
try {
if (funcMarkedAsync && !funcPassedCallback) {
args.push(function (ret) {
event.returnValue = valueToMeta(event.sender, ret, true)
event.returnValue = valueToMeta(event.sender, contextId, ret, true)
})
func.apply(caller, args)
} else {
const ret = func.apply(caller, args)
event.returnValue = valueToMeta(event.sender, ret, true)
event.returnValue = valueToMeta(event.sender, contextId, ret, true)
}
} catch (error) {
// Catch functions thrown further down in function invocation and wrap
@ -257,105 +257,105 @@ const callFunction = function (event, func, caller, args) {
}
}
ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, module) {
ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, contextId, module) {
try {
event.returnValue = valueToMeta(event.sender, process.mainModule.require(module))
event.returnValue = valueToMeta(event.sender, contextId, process.mainModule.require(module))
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) {
ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, module) {
try {
event.returnValue = valueToMeta(event.sender, electron[module])
event.returnValue = valueToMeta(event.sender, contextId, electron[module])
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, name) {
ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, contextId, name) {
try {
event.returnValue = valueToMeta(event.sender, global[name])
event.returnValue = valueToMeta(event.sender, contextId, global[name])
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event) {
ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
try {
event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow())
event.returnValue = valueToMeta(event.sender, contextId, event.sender.getOwnerBrowserWindow())
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event) {
event.returnValue = valueToMeta(event.sender, event.sender)
ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
event.returnValue = valueToMeta(event.sender, contextId, event.sender)
})
ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) {
ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
try {
args = unwrapArgs(event.sender, args)
args = unwrapArgs(event.sender, contextId, args)
let constructor = objectsRegistry.get(id)
if (constructor == null) {
throwRPCError(`Cannot call constructor on missing remote object ${id}`)
}
event.returnValue = valueToMeta(event.sender, new constructor(...args))
event.returnValue = valueToMeta(event.sender, contextId, new constructor(...args))
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, id, args) {
ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
try {
args = unwrapArgs(event.sender, args)
args = unwrapArgs(event.sender, contextId, args)
let func = objectsRegistry.get(id)
if (func == null) {
throwRPCError(`Cannot call function on missing remote object ${id}`)
}
callFunction(event, func, global, args)
callFunction(event, contextId, func, global, args)
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, args) {
ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
try {
args = unwrapArgs(event.sender, args)
args = unwrapArgs(event.sender, contextId, args)
let object = objectsRegistry.get(id)
if (object == null) {
throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
}
event.returnValue = valueToMeta(event.sender, new object[method](...args))
event.returnValue = valueToMeta(event.sender, contextId, new object[method](...args))
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, id, method, args) {
ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
try {
args = unwrapArgs(event.sender, args)
args = unwrapArgs(event.sender, contextId, args)
let obj = objectsRegistry.get(id)
if (obj == null) {
throwRPCError(`Cannot call function '${method}' on missing remote object ${id}`)
}
callFunction(event, obj[method], obj, args)
callFunction(event, contextId, obj[method], obj, args)
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) {
ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
try {
args = unwrapArgs(event.sender, args)
args = unwrapArgs(event.sender, contextId, args)
let obj = objectsRegistry.get(id)
if (obj == null) {
@ -365,11 +365,11 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) {
obj[name] = args[0]
event.returnValue = null
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) {
ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
try {
let obj = objectsRegistry.get(id)
@ -377,14 +377,14 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) {
throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
}
event.returnValue = valueToMeta(event.sender, obj[name])
event.returnValue = valueToMeta(event.sender, contextId, obj[name])
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, id) {
objectsRegistry.remove(event.sender.getId(), id)
ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id) {
objectsRegistry.remove(contextId, id)
})
ipcMain.on('ELECTRON_BROWSER_CONTEXT_RELEASE', (e, contextId) => {
@ -392,16 +392,16 @@ ipcMain.on('ELECTRON_BROWSER_CONTEXT_RELEASE', (e, contextId) => {
e.returnValue = null
})
ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, guestInstanceId) {
ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
try {
let guestViewManager = require('./guest-view-manager')
event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId))
event.returnValue = valueToMeta(event.sender, contextId, guestViewManager.getGuest(guestInstanceId))
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})
ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, requestId, guestInstanceId, method, ...args) {
ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, contextId, requestId, guestInstanceId, method, ...args) {
try {
let guestViewManager = require('./guest-view-manager')
let guest = guestViewManager.getGuest(guestInstanceId)
@ -413,7 +413,7 @@ ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, request
}
guest[method].apply(guest, args)
} catch (error) {
event.returnValue = exceptionToMeta(event.sender, error)
event.returnValue = exceptionToMeta(event.sender, contextId, error)
}
})

View file

@ -9,6 +9,18 @@ const bufferUtils = require('../../common/buffer-utils')
const callbacksRegistry = new CallbacksRegistry()
const remoteObjectCache = v8Util.createIDWeakMap()
// An unique ID that can represent current context.
const contextId = v8Util.getContextId()
// Notify the main process when current context is going to be released.
// Note that when the renderer process is destroyed, the message may not be
// sent, we also listen to the "render-view-deleted" event in the main process
// to guard that situation.
process.on('exit', () => {
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
ipcRenderer.sendSync(command, contextId)
})
// Convert the arguments object into an array of meta data.
function wrapArgs (args, visited = new Set()) {
const valueToMeta = (value) => {
@ -107,7 +119,7 @@ function setObjectMembers (ref, object, metaId, members) {
} else {
command = 'ELECTRON_BROWSER_MEMBER_CALL'
}
const ret = ipcRenderer.sendSync(command, metaId, member.name, wrapArgs(args))
const ret = ipcRenderer.sendSync(command, contextId, metaId, member.name, wrapArgs(args))
return metaToValue(ret)
}
@ -126,7 +138,7 @@ function setObjectMembers (ref, object, metaId, members) {
} else if (member.type === 'get') {
descriptor.get = () => {
const command = 'ELECTRON_BROWSER_MEMBER_GET'
const meta = ipcRenderer.sendSync(command, metaId, member.name)
const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name)
return metaToValue(meta)
}
@ -134,7 +146,7 @@ function setObjectMembers (ref, object, metaId, members) {
descriptor.set = (value) => {
const args = wrapArgs([value])
const command = 'ELECTRON_BROWSER_MEMBER_SET'
const meta = ipcRenderer.sendSync(command, metaId, member.name, args)
const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name, args)
if (meta != null) metaToValue(meta)
return value
}
@ -164,7 +176,7 @@ function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
if (loaded) return
loaded = true
const command = 'ELECTRON_BROWSER_MEMBER_GET'
const meta = ipcRenderer.sendSync(command, metaId, name)
const meta = ipcRenderer.sendSync(command, contextId, metaId, name)
setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members)
}
@ -224,7 +236,7 @@ function metaToValue (meta) {
} else {
command = 'ELECTRON_BROWSER_FUNCTION_CALL'
}
const obj = ipcRenderer.sendSync(command, meta.id, wrapArgs(args))
const obj = ipcRenderer.sendSync(command, contextId, meta.id, wrapArgs(args))
return metaToValue(obj)
}
ret = remoteFunction
@ -237,7 +249,7 @@ function metaToValue (meta) {
Object.defineProperty(ret.constructor, 'name', { value: meta.name })
// Track delegate obj's lifetime & tell browser to clean up when object is GCed.
v8Util.setRemoteObjectFreer(ret, meta.id)
v8Util.setRemoteObjectFreer(ret, contextId, meta.id)
v8Util.setHiddenValue(ret, 'atomId', meta.id)
remoteObjectCache.set(meta.id, ret)
return ret
@ -264,60 +276,51 @@ function metaToException (meta) {
}
// Browser calls a callback in renderer.
ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', (event, id, args) => {
ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', (event, passedContextId, id, args) => {
if (passedContextId !== contextId) {
// The invoked callback belongs to an old page in this renderer.
return
}
callbacksRegistry.apply(id, metaToValue(args))
})
// A callback in browser is released.
ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', (event, id) => {
ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', (event, passedContextId, id) => {
if (passedContextId !== contextId) {
// The freed callback belongs to an old page in this renderer.
return
}
callbacksRegistry.remove(id)
})
process.on('exit', () => {
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
ipcRenderer.sendSync(command, initialContext)
})
exports.require = (module) => {
const command = 'ELECTRON_BROWSER_REQUIRE'
const meta = ipcRenderer.sendSync(command, module)
const meta = ipcRenderer.sendSync(command, contextId, module)
return metaToValue(meta)
}
// Alias to remote.require('electron').xxx.
exports.getBuiltin = (module) => {
const command = 'ELECTRON_BROWSER_GET_BUILTIN'
const meta = ipcRenderer.sendSync(command, module)
const meta = ipcRenderer.sendSync(command, contextId, module)
return metaToValue(meta)
}
exports.getCurrentWindow = () => {
const command = 'ELECTRON_BROWSER_CURRENT_WINDOW'
const meta = ipcRenderer.sendSync(command)
const meta = ipcRenderer.sendSync(command, contextId)
return metaToValue(meta)
}
// Get current WebContents object.
exports.getCurrentWebContents = () => {
return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS'))
}
const CONTEXT_ARG = '--context-id='
let initialContext = process.argv.find(arg => arg.startsWith(CONTEXT_ARG))
if (process.webContentsId) {
// set by sandbox renderer init script
initialContext = process.webContentsId
} else if (initialContext) {
initialContext = parseInt(initialContext.substr(CONTEXT_ARG.length), 10)
} else {
// if not available, pull from remote
initialContext = exports.getCurrentWebContents().getId()
return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', contextId))
}
// Get a global object in browser.
exports.getGlobal = (name) => {
const command = 'ELECTRON_BROWSER_GLOBAL'
const meta = ipcRenderer.sendSync(command, name)
const meta = ipcRenderer.sendSync(command, contextId, name)
return metaToValue(meta)
}
@ -334,7 +337,7 @@ exports.createFunctionWithReturnValue = (returnValue) => {
// Get the guest WebContents from guestInstanceId.
exports.getGuestWebContents = (guestInstanceId) => {
const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS'
const meta = ipcRenderer.sendSync(command, guestInstanceId)
const meta = ipcRenderer.sendSync(command, contextId, guestInstanceId)
return metaToValue(meta)
}

View file

@ -8,6 +8,9 @@ const webViewConstants = require('./web-view-constants')
const hasProp = {}.hasOwnProperty
// An unique ID that can represent current context.
const contextId = v8Util.getContextId()
// ID generator.
let nextId = 0
@ -344,6 +347,7 @@ const registerWebViewElement = function () {
'inspectElement',
'setAudioMuted',
'isAudioMuted',
'isCurrentlyAudible',
'undo',
'redo',
'cut',
@ -396,7 +400,7 @@ const registerWebViewElement = function () {
const createNonBlockHandler = function (m) {
return function (...args) {
const internal = v8Util.getHiddenValue(this, 'internal')
ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', null, internal.guestInstanceId, m, ...args)
ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', contextId, null, internal.guestInstanceId, m, ...args)
}
}
for (const method of nonblockMethods) {
@ -410,7 +414,7 @@ const registerWebViewElement = function () {
hasUserGesture = false
}
const requestId = getNextId()
ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture)
ipcRenderer.send('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', contextId, requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture)
ipcRenderer.once(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, function (event, result) {
if (callback) callback(result)
})

View file

@ -112,10 +112,8 @@ if (preloadSrc) {
${preloadSrc}
})`
// eval in window scope:
// http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2
const geval = eval
const preloadFn = geval(preloadWrapperSrc)
// eval in window scope
const preloadFn = binding.createPreloadScript(preloadWrapperSrc)
const {setImmediate, clearImmediate} = require('timers')
preloadFn(preloadRequire, preloadProcess, Buffer, global, setImmediate, clearImmediate)
} else if (preloadError) {

View file

@ -120,7 +120,7 @@ async function callAppVeyor (targetBranch, job, options) {
let appVeyorResponse = await makeRequest(requestOpts, true).catch(err => {
console.log('Error calling AppVeyor:', err)
})
const buildUrl = `https://windows-ci.electronjs.org/project/AppVeyor/electron/build/${appVeyorResponse.version}`
const buildUrl = `https://windows-ci.electronjs.org/project/AppVeyor/${appVeyorJobs[job]}/build/${appVeyorResponse.version}`
console.log(`AppVeyor release build request for ${job} successful. Check build status at ${buildUrl}`)
}

View file

@ -1,11 +1,13 @@
'use strict'
/* eslint-disable no-unused-expressions */
const {expect} = require('chai')
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const {nativeImage} = require('electron')
const path = require('path')
const {expect} = chai
chai.use(dirtyChai)
describe('nativeImage module', () => {
const ImageFormat = {
PNG: 'png',
@ -111,16 +113,16 @@ describe('nativeImage module', () => {
expect(empty.toDataURL()).to.equal('data:image/png;base64,')
expect(empty.toDataURL({scaleFactor: 2.0})).to.equal('data:image/png;base64,')
expect(empty.getSize()).to.deep.equal({width: 0, height: 0})
expect(empty.getBitmap()).to.be.empty
expect(empty.getBitmap({scaleFactor: 2.0})).to.be.empty
expect(empty.toBitmap()).to.be.empty
expect(empty.toBitmap({scaleFactor: 2.0})).to.be.empty
expect(empty.toJPEG(100)).to.be.empty
expect(empty.toPNG()).to.be.empty
expect(empty.toPNG({scaleFactor: 2.0})).to.be.empty
expect(empty.getBitmap()).to.be.empty()
expect(empty.getBitmap({scaleFactor: 2.0})).to.be.empty()
expect(empty.toBitmap()).to.be.empty()
expect(empty.toBitmap({scaleFactor: 2.0})).to.be.empty()
expect(empty.toJPEG(100)).to.be.empty()
expect(empty.toPNG()).to.be.empty()
expect(empty.toPNG({scaleFactor: 2.0})).to.be.empty()
if (process.platform === 'darwin') {
expect(empty.getNativeHandle()).to.be.empty
expect(empty.getNativeHandle()).to.be.empty()
}
})
})
@ -135,7 +137,7 @@ describe('nativeImage module', () => {
const imageB = nativeImage.createFromBuffer(imageA.toPNG())
expect(imageB.getSize()).to.deep.equal({width: 538, height: 190})
expect(imageA.toBitmap().equals(imageB.toBitmap())).to.be.true
expect(imageA.toBitmap().equals(imageB.toBitmap())).to.be.true()
const imageC = nativeImage.createFromBuffer(imageA.toJPEG(100))
expect(imageC.getSize()).to.deep.equal({width: 538, height: 190})
@ -214,7 +216,7 @@ describe('nativeImage module', () => {
expect(imageTwo.getSize()).to.deep.equal(
{width: imageData.width, height: imageData.height})
expect(imageOne.toBitmap().equals(imageTwo.toBitmap())).to.be.true
expect(imageOne.toBitmap().equals(imageTwo.toBitmap())).to.be.true()
})
it('supports a scale factor', () => {
@ -249,7 +251,7 @@ describe('nativeImage module', () => {
expect(imageC.getSize()).to.deep.equal(
{width: imageData.width, height: imageData.height})
expect(imageB.toBitmap().equals(imageC.toBitmap())).to.be.true
expect(imageB.toBitmap().equals(imageC.toBitmap())).to.be.true()
})
it('supports a scale factor', () => {
@ -280,21 +282,21 @@ describe('nativeImage module', () => {
it('loads images from paths relative to the current working directory', () => {
const imagePath = `.${path.sep}${path.join('spec', 'fixtures', 'assets', 'logo.png')}`
const image = nativeImage.createFromPath(imagePath)
expect(image.isEmpty()).to.be.false
expect(image.isEmpty()).to.be.false()
expect(image.getSize()).to.deep.equal({width: 538, height: 190})
})
it('loads images from paths with `.` segments', () => {
const imagePath = `${path.join(__dirname, 'fixtures')}${path.sep}.${path.sep}${path.join('assets', 'logo.png')}`
const image = nativeImage.createFromPath(imagePath)
expect(image.isEmpty()).to.be.false
expect(image.isEmpty()).to.be.false()
expect(image.getSize()).to.deep.equal({width: 538, height: 190})
})
it('loads images from paths with `..` segments', () => {
const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}`
const image = nativeImage.createFromPath(imagePath)
expect(image.isEmpty()).to.be.false
expect(image.isEmpty()).to.be.false()
expect(image.getSize()).to.deep.equal({width: 538, height: 190})
})
@ -325,7 +327,7 @@ describe('nativeImage module', () => {
const imagePath = path.join(__dirname, 'fixtures', 'assets', 'icon.ico')
const image = nativeImage.createFromPath(imagePath)
expect(image.isEmpty()).to.be.false
expect(image.isEmpty()).to.be.false()
expect(image.getSize()).to.deep.equal({width: 256, height: 256})
})
})
@ -355,7 +357,7 @@ describe('nativeImage module', () => {
}
const image = nativeImage.createFromNamedImage('NSActionTemplate')
expect(image.isEmpty()).to.be.false
expect(image.isEmpty()).to.be.false()
})
it('returns allows an HSL shift for a valid image on darwin', function () {
@ -366,7 +368,7 @@ describe('nativeImage module', () => {
}
const image = nativeImage.createFromNamedImage('NSActionTemplate', [0.5, 0.2, 0.8])
expect(image.isEmpty()).to.be.false
expect(image.isEmpty()).to.be.false()
})
})
@ -425,7 +427,7 @@ describe('nativeImage module', () => {
const cropB = image.crop({width: 25, height: 64, x: 30, y: 40})
expect(cropA.getSize()).to.deep.equal({width: 25, height: 64})
expect(cropB.getSize()).to.deep.equal({width: 25, height: 64})
expect(cropA.toPNG().equals(cropB.toPNG())).to.be.false
expect(cropA.toPNG().equals(cropB.toPNG())).to.be.false()
})
})
@ -471,7 +473,7 @@ describe('nativeImage module', () => {
buffer: 'invalid'
})
expect(image.isEmpty()).to.be.false
expect(image.isEmpty()).to.be.false()
expect(image.getSize()).to.deep.equal({width: 1, height: 1})
expect(image.toDataURL({scaleFactor: 1.0})).to.equal(imageDataOne.dataUrl)
@ -506,7 +508,7 @@ describe('nativeImage module', () => {
dataURL: 'invalid'
})
expect(image.isEmpty()).to.be.false
expect(image.isEmpty()).to.be.false()
expect(image.getSize()).to.deep.equal({width: 1, height: 1})
expect(image.toDataURL({scaleFactor: 1.0})).to.equal(imageDataOne.dataUrl)

View file

@ -1,4 +1,5 @@
const assert = require('assert')
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const http = require('http')
const fs = require('fs')
const os = require('os')
@ -10,13 +11,15 @@ const appPath = path.join(__dirname, 'fixtures', 'api', 'net-log')
const dumpFile = path.join(os.tmpdir(), 'net_log.json')
const dumpFileDynamic = path.join(os.tmpdir(), 'net_log_dynamic.json')
const {expect} = chai
chai.use(dirtyChai)
const isCI = remote.getGlobal('isCi')
describe('netLog module', () => {
let server
const connections = new Set()
before((done) => {
before(done => {
server = http.createServer()
server.listen(0, '127.0.0.1', () => {
server.url = `http://127.0.0.1:${server.address().port}`
@ -33,7 +36,7 @@ describe('netLog module', () => {
})
})
after((done) => {
after(done => {
for (const connection of connections) {
connection.destroy()
}
@ -52,36 +55,35 @@ describe('netLog module', () => {
}
})
it('should begin and end logging to file when .startLogging() and .stopLogging() is called', (done) => {
assert(!netLog.currentlyLogging)
assert.equal(netLog.currentlyLoggingPath, '')
it('should begin and end logging to file when .startLogging() and .stopLogging() is called', done => {
expect(netLog.currentlyLogging).to.be.false()
expect(netLog.currentlyLoggingPath).to.equal('')
netLog.startLogging(dumpFileDynamic)
assert(netLog.currentlyLogging)
assert.equal(netLog.currentlyLoggingPath, dumpFileDynamic)
expect(netLog.currentlyLogging).to.be.true()
expect(netLog.currentlyLoggingPath).to.equal(dumpFileDynamic)
netLog.stopLogging((path) => {
assert(!netLog.currentlyLogging)
assert.equal(netLog.currentlyLoggingPath, '')
expect(netLog.currentlyLogging).to.be.false()
expect(netLog.currentlyLoggingPath).to.equal('')
assert.equal(path, dumpFileDynamic)
assert(fs.existsSync(dumpFileDynamic))
expect(path).to.equal(dumpFileDynamic)
expect(fs.existsSync(dumpFileDynamic)).to.be.true()
done()
})
})
it('should silence when .stopLogging() is called without calling .startLogging()', (done) => {
assert(!netLog.currentlyLogging)
assert.equal(netLog.currentlyLoggingPath, '')
it('should silence when .stopLogging() is called without calling .startLogging()', done => {
expect(netLog.currentlyLogging).to.be.false()
expect(netLog.currentlyLoggingPath).to.equal('')
netLog.stopLogging((path) => {
assert(!netLog.currentlyLogging)
assert.equal(netLog.currentlyLoggingPath, '')
netLog.stopLogging(path => {
expect(netLog.currentlyLogging).to.be.false()
expect(netLog.currentlyLoggingPath).to.equal('')
assert.equal(path, '')
expect(path).to.equal('')
done()
})
@ -89,7 +91,7 @@ describe('netLog module', () => {
// The following tests are skipped on Linux CI
it('should begin and end logging automatically when --log-net-log is passed', (done) => {
it('should begin and end logging automatically when --log-net-log is passed', done => {
if (isCI && process.platform === 'linux') {
done()
return
@ -103,12 +105,12 @@ describe('netLog module', () => {
})
appProcess.once('exit', () => {
assert(fs.existsSync(dumpFile))
expect(fs.existsSync(dumpFile)).to.be.true()
done()
})
})
it('should begin and end logging automtically when --log-net-log is passed, and behave correctly when .startLogging() and .stopLogging() is called', (done) => {
it('should begin and end logging automtically when --log-net-log is passed, and behave correctly when .startLogging() and .stopLogging() is called', done => {
if (isCI && process.platform === 'linux') {
done()
return
@ -123,18 +125,18 @@ describe('netLog module', () => {
}
})
appProcess.stdout.on('data', (data) => {
appProcess.stdout.on('data', data => {
console.log(data.toString())
})
appProcess.once('exit', () => {
assert(fs.existsSync(dumpFile))
assert(fs.existsSync(dumpFileDynamic))
expect(fs.existsSync(dumpFile)).to.be.true()
expect(fs.existsSync(dumpFileDynamic)).to.be.true()
done()
})
})
it('should end logging automatically when only .startLogging() is called', (done) => {
it('should end logging automatically when only .startLogging() is called', done => {
if (isCI && process.platform === 'linux') {
done()
return
@ -149,7 +151,7 @@ describe('netLog module', () => {
})
appProcess.once('exit', () => {
assert(fs.existsSync(dumpFileDynamic))
expect(fs.existsSync(dumpFileDynamic)).to.be.true()
done()
})
})

View file

@ -6,7 +6,7 @@
//
// See https://pypi.python.org/pypi/python-dbusmock to read about dbusmock.
const assert = require('assert')
const {expect} = require('chai')
const dbus = require('dbus-native')
const Promise = require('bluebird')
@ -27,12 +27,12 @@ const skip = process.platform !== 'linux' ||
before(async () => {
// init app
app.setName(appName)
app.setDesktopName(appName + '.desktop')
app.setDesktopName(`${appName}.desktop`)
// init dbus
const path = '/org/freedesktop/Notifications'
const iface = 'org.freedesktop.DBus.Mock'
const bus = dbus.sessionBus()
console.log('session bus: ' + process.env.DBUS_SESSION_BUS_ADDRESS)
console.log(`session bus: ${process.env.DBUS_SESSION_BUS_ADDRESS}`)
const service = bus.getService(serviceName)
const getInterface = Promise.promisify(service.getInterface, {context: service})
mock = await getInterface(path, iface)
@ -48,10 +48,10 @@ const skip = process.platform !== 'linux' ||
app.setVersion(realAppVersion)
})
describe('Notification module using ' + serviceName, () => {
describe(`Notification module using ${serviceName}`, () => {
function onMethodCalled (done) {
function cb (name) {
console.log('onMethodCalled: ' + name)
console.log(`onMethodCalled: ${name}`)
if (name === 'Notify') {
mock.removeListener('MethodCalled', cb)
console.log('done')
@ -83,7 +83,7 @@ const skip = process.platform !== 'linux' ||
}
}
before((done) => {
before(done => {
mock.on('MethodCalled', onMethodCalled(done))
// lazy load Notification after we listen to MethodCalled mock signal
Notification = require('electron').remote.Notification
@ -98,14 +98,16 @@ const skip = process.platform !== 'linux' ||
n.show()
})
it('should call ' + serviceName + ' to show notifications', async () => {
it(`should call ${serviceName} to show notifications`, async () => {
const calls = await getCalls()
assert(calls.length >= 1)
expect(calls).to.be.an('array').of.lengthOf.at.least(1)
let lastCall = calls[calls.length - 1]
let methodName = lastCall[1]
assert.equal(methodName, 'Notify')
expect(methodName).to.equal('Notify')
let args = unmarshalDBusNotifyArgs(lastCall[2])
assert.deepEqual(args, {
expect(args).to.deep.equal({
app_name: appName,
replaces_id: 0,
app_icon: '',

View file

@ -4,12 +4,18 @@ const assert = require('assert')
const http = require('http')
const path = require('path')
const {closeWindow} = require('./window-helpers')
const {emittedOnce} = require('./events-helpers')
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const {ipcRenderer, remote} = require('electron')
const {BrowserWindow, webContents, ipcMain, session} = remote
const {expect} = chai
const isCi = remote.getGlobal('isCi')
chai.use(dirtyChai)
/* The whole webContents API doesn't use standard callbacks */
/* eslint-disable standard/no-callback-literal */
@ -116,6 +122,21 @@ describe('webContents module', () => {
})
})
describe('isCurrentlyAudible() API', () => {
it('returns whether audio is playing', async () => {
w.loadURL(`file://${path.join(__dirname, 'fixtures', 'api', 'is-currently-audible.html')}`)
w.show()
await emittedOnce(w.webContents, 'did-finish-load')
expect(w.webContents.isCurrentlyAudible()).to.be.false()
w.webContents.send('play')
await emittedOnce(ipcMain, 'playing')
expect(w.webContents.isCurrentlyAudible()).to.be.true()
})
})
describe('getWebPreferences() API', () => {
it('should not crash when called for devTools webContents', (done) => {
w.webContents.openDevTools()

View file

@ -0,0 +1,21 @@
<html>
<body>
<div id="video"></div>
<script type="text/javascript" charset="utf-8">
const {ipcRenderer} = window.top != null ? window.top.require('electron') : require('electron')
ipcRenderer.on('play', (event) => {
const context = new window.AudioContext();
const oscillator = context.createOscillator();
// A beep
oscillator.type = 'sine';
oscillator.frequency.value = 440
oscillator.connect(context.destination)
oscillator.start()
// It'll take a few ms before the beep shows up
setTimeout(() => event.sender.send('playing'), 100)
})
</script>
</body>
</html>

10
spec/fixtures/module/preload-context.js vendored Normal file
View file

@ -0,0 +1,10 @@
var test = 'test' // eslint-disable-line
const types = {
require: typeof require,
electron: typeof electron,
window: typeof window,
localVar: typeof window.test
}
console.log(JSON.stringify(types))

View file

@ -249,6 +249,22 @@ describe('<webview> tag', function () {
})
})
it('runs in the correct scope when sandboxed', async () => {
const message = await startLoadingWebViewAndWaitForMessage(webview, {
preload: `${fixtures}/module/preload-context.js`,
src: `file://${fixtures}/api/blank.html`,
webpreferences: 'sandbox=yes'
})
const types = JSON.parse(message)
expect(types).to.include({
require: 'function', // arguments passed to it should be availale
electron: 'undefined', // objects from the scope it is called from should not be available
window: 'object', // the window object should be available
localVar: 'undefined' // but local variables should not be exposed to the window
})
})
it('preload script can require modules that still use "process" and "Buffer" when nodeintegration is off', async () => {
const message = await startLoadingWebViewAndWaitForMessage(webview, {
preload: `${fixtures}/module/preload-node-off-wrapper.js`,

@ -1 +1 @@
Subproject commit 5ad14b556198ba9a5422154aba2f9b7914dc528c
Subproject commit a55a9ce536db60702630c4b9d94dcb2145fc3b24