Compare commits

...

22 Commits

Author SHA1 Message Date
Zeke Sikelianos
8d9775b0b1 Bump v3.0.0-beta.3 2018-07-17 09:38:50 -07:00
John Kleinschmidt
9237d46dba make sure tests don't run for GitHub releases. (#13701)
Also fix VSTS slack integration and
Update to latest libchromiumcontent

(cherry picked from commit 1c70a1cccd)
2018-07-17 10:57:59 -05:00
John Kleinschmidt
8f8d198c5a Merge pull request #13539 from trop-bot/3-0-x-bp-enable-webview-in-sandbox-renderer-1530547750986
Backport (3-0-x) - Enable webview in sandbox renderer
2018-07-17 10:01:40 -04:00
trop[bot]
fcdbb8c4a7 fix: create a window capturer correctly (backport: 3-0-x) (#13693)
* fix: create a window capturer correctly

We were incorrectly creating two screen capturers instead of a window
capturer

* spec: ensure dc tests pass

* spec: enable all tests after verifying dc tests work
2018-07-17 15:30:33 +10:00
trop[bot]
b49b071a1f fix: remove race condition for executeJavaScript (#13692)
Replaces 'did-finish-load' with 'did-stop-loading' which semantically
maps to the events inside Chromium.  Before I think we were relying
on a natural 99% winnable race condition.

Fixes #13504
2018-07-16 21:22:27 -07:00
trop[bot]
1feb45da29 docs: update second-instance event docs (#13682) 2018-07-16 13:57:58 -07:00
John Kleinschmidt
2c43dbdb25 Merge pull request #13680 from trop-bot/3-0-x-bp-fix-crash-when-opening-devtools-for-frameless-window-1531748442042
Fix crash when opening devtools for frameless window (backport: 3-0-x)
2018-07-16 10:48:37 -04:00
Cheng Zhao
7eff91dde1 spec: openDevTools should not crash for frameless window 2018-07-16 13:40:48 +00:00
Cheng Zhao
780483ff35 fix: use InspectableWebContentsView as content view 2018-07-16 13:40:48 +00:00
trop[bot]
c2f770df02 Update Windows VS2017 requirement (#13665) 2018-07-13 18:17:53 -05:00
John Kleinschmidt
74e6e063d4 Merge pull request #13658 from trop-bot/3-0-x-bp-fix--don't-invoke-callback-after-framesubscriber-is-destroyed-1531475760693
fix: don't invoke callback after FrameSubscriber is destroyed (backport: 3-0-x)
2018-07-13 11:51:07 -04:00
Cheng Zhao
7702cc1090 fix: don't invoke callback after FrameSubscriber is destroyed 2018-07-13 09:56:07 +00:00
trop[bot]
97319e5a3a fix: remember the render_process_id when permission requests occur on the IO thread (#13651)
Fixes #13620
2018-07-13 12:50:57 +10:00
Jeremy Apthorp
041c952574 fix: enable spell checking, which broke in upgrade to ch66
Chromium commit [03563dd163][1] changed the way that the
spellcheck-enabled status was checked, defaulting to false.

Added the first (!) test for spellchecking, too.

Fixes #13608.

[1]: 03563dd163
2018-07-12 13:28:15 -07:00
John Kleinschmidt
6c9b6f9cac Merge pull request #13642 from trop-bot/3-0-x-bp-chore--updates-for-release-process-1531400155960
chore: Updates for release process (backport: 3-0-x)
2018-07-12 09:37:12 -04:00
John Kleinschmidt
c4884da601 Update docs to reflect current process
Also added troubleshooting steps.
2018-07-12 12:56:02 +00:00
John Kleinschmidt
59b05ed661 Fix Appveyor URL 2018-07-12 12:56:02 +00:00
Cheng Zhao
e3204a5ec1 Merge pull request #13637 from electron/fix-remote-3-0-x
fix: guard against double-freeing remote references (3-0-x)
2018-07-12 16:01:52 +09:00
Cheng Zhao
adf49daaac fix double-freeing remote references
After the page does navigations, garbage collection can still happen in
the old context. This commit changes to store references to remote objects
by _pages_, instead of by _WebContents_.
2018-07-12 11:17:26 +09:00
Cheng Zhao
9431677e79 add API to return an unique ID for page 2018-07-12 11:16:13 +09:00
Hari Juturu
f63e5ffa65 cache lastwebprefs 2018-07-02 16:09:17 +00:00
Hari Juturu
32e40cb4c3 Enable webview in sandbox renderer Security: Inherit embedder prefs onto webview 2018-07-02 16:09:17 +00:00
37 changed files with 375 additions and 218 deletions

View File

@@ -81,9 +81,13 @@ build-steps: &build-steps
mkdir junit
script/test.py --ci --rebuild_native_modules
else
echo 'Testing Electron release build'
mkdir junit
script/test.py --ci --rebuild_native_modules -c R
if [ "$UPLOAD_TO_S3" == "1" ]; then
echo 'Testing Electron release build'
mkdir junit
script/test.py --ci --rebuild_native_modules -c R
else
echo 'Skipping tests on GitHub release'
fi
fi
else
echo 'Skipping tests due to configuration'
@@ -118,10 +122,7 @@ build-steps: &build-steps
- store_artifacts:
path: junit
- store_artifacts:
path: out/electron.d.ts
- store_artifacts:
path: out/electron-api.json
path: out
build-defaults: &build-defaults
docker:

View File

@@ -1,25 +1,22 @@
version: 1.0.{build}
branches:
except:
- /^release$|^release-\d-\d-x$/
build_cloud: electron-16
image: electron-16-vs2017
environment:
DISABLE_CRASH_REPORTER_TESTS: true
matrix:
- TARGET_ARCH: ia32
- TARGET_ARCH: x64
build_script:
- ps: >-
if(($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME -split "/")[0] -eq ($env:APPVEYOR_REPO_NAME -split "/")[0]) {
Write-warning "Skipping PR build for branch"; Exit-AppveyorBuild
} else {
Add-Path "$env:ProgramFiles (x86)\Windows Kits\10\Debuggers\x64"
$env:path = "$env:ProgramFiles (x86)\Windows Kits\10\Debuggers\x64;$env:path"
if($env:APPVEYOR_SCHEDULED_BUILD -eq 'True') {
$env:RUN_RELEASE_BUILD = "1"
}
$Message = (git log --format=%B -n 1 HEAD) | Out-String
if ((Test-Path Env:\RUN_RELEASE_BUILD)) {
$env:ELECTRON_RELEASE = '1'
Write-Output "release build triggered from api"
@@ -39,8 +36,8 @@ build_script:
} else {
"Build succeeded."
}
Push-AppveyorArtifact out
}
test_script:
- ps: >-
if (Test-Path Env:\ELECTRON_RELEASE) {
@@ -54,7 +51,6 @@ test_script:
Write-Output "Tests succeeded."
}
python script\verify-ffmpeg.py
if ($LASTEXITCODE -ne '0') {
throw "Verify ffmpeg failed with exit code $LASTEXITCODE"
} else {
@@ -73,22 +69,5 @@ deploy_script:
} else {
Write-Output "Uploading Electron release distribution to github releases"
& python script\upload.py
if (Test-Path Env:\AUTO_RELEASE) {
node script\release.js --validateRelease --automaticRelease
if ($? -eq 'True') {
echo 'Release is ready to go; now running release'
node script\release.js --automaticRelease
if ($? -eq 'True') {
echo 'Release successful, now publishing to npm'
$npmfile = "$HOME\.npmrc"
"//registry.npmjs.org/:_authToken=$env:ELECTRON_NPM_TOKEN" > $npmfile
npm run publish-to-npm
}
} else {
echo 'Release is not complete, skipping publish for now.'
}
}
}
} else {
Write-Output "Skipping upload distribution because build is not for release"
}

View File

@@ -87,7 +87,7 @@ BrowserWindow::BrowserWindow(v8::Isolate* isolate,
#if defined(OS_MACOSX)
if (!window()->has_frame())
OverrideNSWindowContentView();
OverrideNSWindowContentView(web_contents->managed_web_contents());
#endif
// Init window after everything has been setup.

View File

@@ -81,7 +81,7 @@ class BrowserWindow : public TopLevelWindow,
private:
#if defined(OS_MACOSX)
void OverrideNSWindowContentView();
void OverrideNSWindowContentView(brightray::InspectableWebContents* iwc);
#endif
// Helpers.

View File

@@ -10,6 +10,7 @@
#include "atom/browser/native_window_mac.h"
#include "atom/common/draggable_region.h"
#include "base/mac/scoped_nsobject.h"
#include "brightray/browser/inspectable_web_contents_view.h"
@interface NSView (WebContentsView)
- (void)setMouseDownCanMoveWindow:(BOOL)can_move;
@@ -54,11 +55,12 @@ std::vector<gfx::Rect> CalculateNonDraggableRegions(
} // namespace
void BrowserWindow::OverrideNSWindowContentView() {
void BrowserWindow::OverrideNSWindowContentView(
brightray::InspectableWebContents* iwc) {
// Make NativeWindow use a NSView as content view.
static_cast<NativeWindowMac*>(window())->OverrideNSWindowContentView();
// Add webview to contentView.
NSView* webView = web_contents()->GetNativeView();
NSView* webView = iwc->GetView()->GetNativeView();
NSView* contentView = [window()->GetNativeWindow() contentView];
[webView setFrame:[contentView bounds]];
[contentView addSubview:webView];

View File

@@ -69,7 +69,7 @@ void StartHandlingTask(bool capture_window,
capture_screen ? content::desktop_capture::CreateScreenCapturer()
: nullptr);
std::unique_ptr<webrtc::DesktopCapturer> window_capturer(
capture_window ? content::desktop_capture::CreateScreenCapturer()
capture_window ? content::desktop_capture::CreateWindowCapturer()
: nullptr);
cap->media_list_.reset(new NativeDesktopMediaList(
std::move(screen_capturer), std::move(window_capturer)));

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

@@ -56,8 +56,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,0,0,2
PRODUCTVERSION 3,0,0,2
FILEVERSION 3,0,0,3
PRODUCTVERSION 3,0,0,3
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L

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

@@ -8,7 +8,7 @@
#define ATOM_MAJOR_VERSION 3
#define ATOM_MINOR_VERSION 0
#define ATOM_PATCH_VERSION 0
#define ATOM_PRE_RELEASE_VERSION -beta.2
#define ATOM_PRE_RELEASE_VERSION -beta.3
#ifndef ATOM_STRINGIFY
#define ATOM_STRINGIFY(n) ATOM_STRINGIFY_HELPER(n)

View File

@@ -115,6 +115,10 @@ void SpellCheckClient::RequestCheckingOfText(
base::Owned(pending_request_param_.release())));
}
bool SpellCheckClient::IsSpellCheckingEnabled() const {
return true;
}
void SpellCheckClient::ShowSpellingUI(bool show) {}
bool SpellCheckClient::IsShowingSpellingUI() {

View File

@@ -46,6 +46,7 @@ class SpellCheckClient : public blink::WebSpellCheckPanelHostClient,
void RequestCheckingOfText(
const blink::WebString& textToCheck,
blink::WebTextCheckingCompletion* completionCallback) override;
bool IsSpellCheckingEnabled() const override;
// blink::WebSpellCheckPanelHostClient:
void ShowSpellingUI(bool show) override;

View File

@@ -92,6 +92,12 @@ void InitializeBindings(v8::Local<v8::Object> binding,
b.SetMethod("getHeapStatistics", &AtomBindings::GetHeapStatistics);
b.SetMethod("getProcessMemoryInfo", &AtomBindings::GetProcessMemoryInfo);
b.SetMethod("getSystemMemoryInfo", &AtomBindings::GetSystemMemoryInfo);
// Pass in CLI flags needed to setup the renderer
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kGuestInstanceID))
b.Set(options::kGuestInstanceID,
command_line->GetSwitchValueASCII(switches::kGuestInstanceID));
}
class AtomSandboxedRenderFrameObserver : public AtomRenderFrameObserver {

View File

@@ -90,6 +90,7 @@ PlatformNotificationService::CheckPermissionOnIOThread(
content::ResourceContext* resource_context,
const GURL& origin,
int render_process_id) {
render_process_id_ = render_process_id;
return blink::mojom::PermissionStatus::GRANTED;
}

View File

@@ -386,6 +386,7 @@ app.on('session-created', (event, session) => {
Returns:
* `event` Event
* `argv` String[] - An array of the second instance's command line arguments
* `workingDirectory` String - The second instance's working directory

View File

@@ -5,7 +5,7 @@ Follow the guidelines below for building Electron on Windows.
## Prerequisites
* Windows 7 / Server 2008 R2 or higher
* Visual Studio 2017 - [download VS 2017 Community Edition for
* Visual Studio 2017 15.7.2 or higher - [download VS 2017 Community Edition for
free](https://www.visualstudio.com/vs/)
* [Python 2.7](http://www.python.org/download/releases/2.7/)
* [Node.js](https://nodejs.org/download/)

View File

@@ -2,23 +2,6 @@
This document describes the process for releasing a new version of Electron.
## Determine which branch to release from
- **If releasing beta,** run the scripts below from `master`.
- **If releasing a stable version,** run the scripts below from the branch
you're stabilizing.
## Find out what version change is needed
Run `npm run prepare-release -- --notesOnly` to view auto generated release
notes. The notes generated should help you determine if this is a major, minor,
patch, or beta version change. Read the
[Version Change Rules](../tutorial/electron-versioning.md#semver) for more information.
**NB:** If releasing from a branch, e.g. 1-8-x, check out the branch with
`git checkout 1-8-x` rather than `git checkout -b remotes/origin/1-8-x`.
The scripts need `git rev-parse --abbrev-ref HEAD` to return a short name,
e.g. no `remotes/origin/`
## Set your tokens and environment variables
You'll need Electron S3 credentials in order to create and
upload an Electron release. Contact a team member for more
@@ -35,6 +18,26 @@ 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)`.
## Determine which branch to release from
- **If releasing beta,** run the scripts below from `master`.
- **If releasing a stable version,** run the scripts below from the branch
you're stabilizing.
## Find out what version change is needed
Run `npm run prepare-release -- --notesOnly` to view auto generated release
notes. The notes generated should help you determine if this is a major, minor,
patch, or beta version change. Read the
[Version Change Rules](../tutorial/electron-versioning.md#semver) for more information.
**NB:** If releasing from a branch, e.g. 1-8-x, check out the branch with
`git checkout 1-8-x` rather than `git checkout -b remotes/origin/1-8-x`.
The scripts need `git rev-parse --abbrev-ref HEAD` to return a short name,
e.g. no `remotes/origin/`
## Run the prepare-release script
The prepare release script will do the following:
@@ -84,8 +87,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
@@ -192,35 +198,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
@@ -234,7 +218,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
@@ -264,16 +247,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

@@ -4,7 +4,7 @@
'product_name%': 'Electron',
'company_name%': 'GitHub, Inc',
'company_abbr%': 'github',
'version%': '3.0.0-beta.2',
'version%': '3.0.0-beta.3',
'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c',
},
'includes': [

View File

@@ -184,7 +184,7 @@ WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callba
return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture)
} else {
return new Promise((resolve, reject) => {
this.once('did-finish-load', () => {
this.once('did-stop-loading', () => {
asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture).then(resolve).catch(reject)
})
})

View File

@@ -240,6 +240,23 @@ const attachGuest = function (event, elementInstanceId, guestInstanceId, params)
webPreferences.disablePopups = true
}
// Security options that guest will always inherit from embedder
const inheritedWebPreferences = new Map([
['contextIsolation', true],
['javascript', false],
['nativeWindowOpen', true],
['nodeIntegration', false],
['sandbox', true]
])
// Inherit certain option values from embedder
const lastWebPreferences = embedder.getLastWebPreferences()
for (const [name, value] of inheritedWebPreferences) {
if (lastWebPreferences[name] === value) {
webPreferences[name] = value
}
}
embedder.emit('will-attach-webview', event, webPreferences, params)
if (event.defaultPrevented) {
if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId

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) {
// Dereference from the storage.
this.dereference(id)
// Also remove the reference in owner.
let owner = this.owners[webContentsId]
// 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)
}
}
// 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) {
let i, len, member, ref, returnValue
switch (meta.type) {
@@ -178,7 +178,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':
@@ -204,26 +204,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
}
@@ -236,19 +236,19 @@ 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) {
let err, funcMarkedAsync, funcName, funcPassedCallback, ref, ret
funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
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 {
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
@@ -261,45 +261,45 @@ 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) {
@@ -309,30 +309,30 @@ ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) {
// Call new with array of arguments.
// http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
event.returnValue = valueToMeta(event.sender, obj)
event.returnValue = valueToMeta(event.sender, contextId, obj)
} 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) {
@@ -342,30 +342,30 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, a
// Call new with array of arguments.
let constructor = object[method]
let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
event.returnValue = valueToMeta(event.sender, obj)
event.returnValue = valueToMeta(event.sender, contextId, obj)
} 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) {
@@ -375,11 +375,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)
@@ -387,14 +387,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) => {
@@ -402,16 +402,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)
@@ -423,7 +423,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
@@ -396,7 +399,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 +413,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

@@ -78,6 +78,16 @@ if (window.location.protocol === 'chrome-devtools:') {
require('../renderer/inspector')
}
if (binding.guestInstanceId) {
process.guestInstanceId = parseInt(binding.guestInstanceId)
}
if (!process.guestInstanceId && preloadProcess.argv.indexOf('--webview-tag=true') !== -1) {
// don't allow recursive `<webview>`
require('../renderer/web-view/web-view')
require('../renderer/web-view/web-view-attributes')
}
// Wrap the script into a function executed in global scope. It won't have
// access to the current scope, so we'll expose a few objects as arguments:
//

View File

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

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

@@ -8,7 +8,7 @@ const os = require('os')
const qs = require('querystring')
const http = require('http')
const {closeWindow} = require('./window-helpers')
const {emittedOnce} = require('./events-helpers')
const {ipcRenderer, remote, screen} = require('electron')
const {app, ipcMain, BrowserWindow, BrowserView, protocol, session, webContents} = remote
@@ -839,6 +839,14 @@ describe('BrowserWindow module', () => {
})
})
describe('BrowserWindow.openDevTools()', () => {
it('does not crash for frameless window', () => {
w.destroy()
w = new BrowserWindow({ show: false })
w.openDevTools()
})
})
describe('BrowserWindow.fromBrowserView(browserView)', () => {
let bv = null
@@ -1573,6 +1581,23 @@ describe('BrowserWindow module', () => {
})
w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html'))
})
it('webview in sandbox renderer', async () => {
w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true,
preload: preload,
webviewTag: true
}
})
w.loadURL(`file://${fixtures}/pages/webview-did-attach-event.html`)
const [, webContents] = await emittedOnce(w.webContents, 'did-attach-webview')
const [, id] = await emittedOnce(ipcMain, 'webview-dom-ready')
expect(webContents.id).to.equal(id)
})
})
describe('nativeWindowOpen option', () => {

View File

@@ -69,7 +69,11 @@ describe('desktopCapturer', () => {
return done()
}
const { BrowserWindow } = remote
const w = new BrowserWindow({ width: 200, height: 200 })
desktopCapturer.getSources({types: ['window']}, (error, sources) => {
w.destroy()
expect(error).to.be.null()
expect(sources).to.be.an('array').that.is.not.empty()
for (const {display_id: displayId} of sources) {

View File

@@ -1,8 +1,14 @@
const assert = require('assert')
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const path = require('path')
const {closeWindow} = require('./window-helpers')
const {remote, webFrame} = require('electron')
const {BrowserWindow, protocol, ipcMain} = remote
const {emittedOnce} = require('./events-helpers')
const {expect} = chai
chai.use(dirtyChai)
/* Most of the APIs here don't use standard callbacks */
/* eslint-disable standard/no-callback-literal */
@@ -138,4 +144,18 @@ describe('webFrame module', function () {
webFrame.setLayoutZoomLevelLimits(0, 25)
})
})
it('calls a spellcheck provider', async () => {
w = new BrowserWindow({show: false})
w.loadURL(`file://${fixtures}/pages/webframe-spell-check.html`)
await emittedOnce(w.webContents, 'did-finish-load')
const spellCheckerFeedback = emittedOnce(ipcMain, 'spec-spell-check')
const misspelledWord = 'spleling'
for (const keyCode of [...misspelledWord, ' ']) {
w.webContents.sendInputEvent({type: 'char', keyCode})
}
const [, text] = await spellCheckerFeedback
expect(text).to.equal(misspelledWord)
})
})

View File

@@ -0,0 +1,13 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
const {ipcRenderer, webFrame} = require('electron')
webFrame.setSpellCheckProvider('en-US', true, {
spellCheck: text => {
ipcRenderer.send('spec-spell-check', text)
}
})
</script>
<input autofocus />
</body>
</html>

View File

@@ -54,6 +54,7 @@ steps:
script/test.py --ci --rebuild_native_modules
fi
name: Test
condition: or(ne(variables['ELECTRON_RELEASE'], '1'), eq(variables['UPLOAD_TO_S3'], '1'))
- bash: |
echo 'Verifying ffmpeg on build'
@@ -63,6 +64,7 @@ steps:
script/verify-ffmpeg.py
fi
name: Verify_FFmpeg
condition: or(ne(variables['ELECTRON_RELEASE'], '1'), eq(variables['UPLOAD_TO_S3'], '1'))
- task: PublishTestResults@2
displayName: Publish Test Results
@@ -71,12 +73,26 @@ steps:
searchFolder: junit
condition: and(always(), ne(variables['ELECTRON_RELEASE'], '1'))
- task: kasunkodagoda.slack-notification.slack-notification-task.SlackNotification@3
displayName: Post Slack Notification
- bash: |
export BUILD_URL="${SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${SYSTEM_TEAMPROJECT}/_build/results?buildId=${BUILD_BUILDID}"
export MESSAGE="Build failed for *<$BUILD_URL|$BUILD_DEFINITIONNAME>* nightly build."
curl -g -H "Content-Type: application/json" -X POST \
-d "{\"text\": \"$MESSAGE\", \"attachments\": [{\"color\": \"#FC5C3C\",\"title\": \"$BUILD_DEFINITIONNAME nightly build results\",\"title_link\": \"$BUILD_URL\"}]}" $(slack_webhook)
name: Post_Slack_Notification_on_Failure
condition: failed()
- bash: |
export BUILD_URL="${SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${SYSTEM_TEAMPROJECT}/_build/results?buildId=${BUILD_BUILDID}"
export MESSAGE="Build succeeded for *<$BUILD_URL|$BUILD_DEFINITIONNAME>* nightly build."
curl -g -H "Content-Type: application/json" -X POST \
-d "{\"text\": \"$MESSAGE\", \"attachments\": [{\"color\": \"#FC5C3C\",\"title\": \"$BUILD_DEFINITIONNAME nightly build results\",\"title_link\": \"$BUILD_URL\"}]}" $(slack_webhook)
name: Post_Slack_Notification_on_Success
condition: succeeded()
- task: PublishBuildArtifacts@1
displayName: Publish Build Artifacts
inputs:
SlackApiToken: '$(slack_token)'
Channel: '#bot-nightly-releases'
Message: '$(Build.DefinitionName)-$(Build.BuildNumber) finished with a $(Agent.JobStatus) status.'
condition: and(always(), eq(variables['Build.Reason'], 'Schedule'))
PathtoPublish: '$(Build.SourcesDirectory)/out'
ArtifactName: out
- task: mspremier.PostBuildCleanup.PostBuildCleanup-task.PostBuildCleanup@3