Merge branch 'master' of github.com:electron/electron into branch

This commit is contained in:
JinHyeok Yeon 2017-11-21 17:22:17 +09:00
commit 4a42738db9
75 changed files with 2774 additions and 443 deletions

View file

@ -9,16 +9,9 @@ jobs:
resource_class: xlarge resource_class: xlarge
steps: steps:
- checkout - checkout
- run:
name: Setup for headless testing
command: sh -e /etc/init.d/xvfb start
- run: - run:
name: Check for release name: Check for release
command: | command: |
MESSAGE="$(git log --format=%B -n 1 HEAD)"
case ${MESSAGE} in
Bump* ) echo 'export ELECTRON_RELEASE=1' >> $BASH_ENV
esac
if [ -n "${RUN_RELEASE_BUILD}" ]; then if [ -n "${RUN_RELEASE_BUILD}" ]; then
echo 'release build triggered from api' echo 'release build triggered from api'
echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV
@ -73,16 +66,9 @@ jobs:
resource_class: xlarge resource_class: xlarge
steps: steps:
- checkout - checkout
- run:
name: Setup for headless testing
command: sh -e /etc/init.d/xvfb start
- run: - run:
name: Check for release name: Check for release
command: | command: |
MESSAGE="$(git log --format=%B -n 1 HEAD)"
case ${MESSAGE} in
Bump* ) echo 'export ELECTRON_RELEASE=1' >> $BASH_ENV
esac
if [ -n "${RUN_RELEASE_BUILD}" ]; then if [ -n "${RUN_RELEASE_BUILD}" ]; then
echo 'release build triggered from api' echo 'release build triggered from api'
echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV
@ -137,16 +123,9 @@ jobs:
resource_class: xlarge resource_class: xlarge
steps: steps:
- checkout - checkout
- run:
name: Setup for headless testing
command: sh -e /etc/init.d/xvfb start
- run: - run:
name: Check for release name: Check for release
command: | command: |
MESSAGE="$(git log --format=%B -n 1 HEAD)"
case ${MESSAGE} in
Bump* ) echo 'export ELECTRON_RELEASE=1' >> $BASH_ENV
esac
if [ -n "${RUN_RELEASE_BUILD}" ]; then if [ -n "${RUN_RELEASE_BUILD}" ]; then
echo 'release build triggered from api' echo 'release build triggered from api'
echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV
@ -199,6 +178,7 @@ jobs:
- image: electronbuilds/electron:0.0.3 - image: electronbuilds/electron:0.0.3
environment: environment:
TARGET_ARCH: x64 TARGET_ARCH: x64
DISPLAY: ':99.0'
resource_class: xlarge resource_class: xlarge
steps: steps:
- checkout - checkout
@ -208,10 +188,6 @@ jobs:
- run: - run:
name: Check for release name: Check for release
command: | command: |
MESSAGE="$(git log --format=%B -n 1 HEAD)"
case ${MESSAGE} in
Bump* ) echo 'export ELECTRON_RELEASE=1' >> $BASH_ENV
esac
if [ -n "${RUN_RELEASE_BUILD}" ]; then if [ -n "${RUN_RELEASE_BUILD}" ]; then
echo 'release build triggered from api' echo 'release build triggered from api'
echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV echo 'export ELECTRON_RELEASE=1 TRIGGERED_BY_API=1' >> $BASH_ENV

View file

@ -1,21 +1,22 @@
[![Electron Logo](https://electron.atom.io/images/electron-logo.svg)](https://electron.atom.io/) [![Electron Logo](https://electronjs.org/images/electron-logo.svg)](https://electronjs.org)
[![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron) [![Travis Build Status](https://travis-ci.org/electron/electron.svg?branch=master)](https://travis-ci.org/electron/electron)
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/bc56v83355fi3369/branch/master?svg=true)](https://ci.appveyor.com/project/electron-bot/electron/branch/master) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/bc56v83355fi3369/branch/master?svg=true)](https://ci.appveyor.com/project/electron-bot/electron/branch/master)
[![devDependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron?type=dev) [![devDependency Status](https://david-dm.org/electron/electron/dev-status.svg)](https://david-dm.org/electron/electron?type=dev)
[![Join the Electron Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/) [![Join the Electron Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/)
:memo: Available Translations: [Korean](https://github.com/electron/electron/tree/master/docs-translations/ko-KR/project/README.md) | [Simplified Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-CN/project/README.md) | [Brazilian Portuguese](https://github.com/electron/electron/tree/master/docs-translations/pt-BR/project/README.md) | [Traditional Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-TW/project/README.md) | [Spanish](https://github.com/electron/electron/tree/master/docs-translations/es/project/README.md) | [Turkish](https://github.com/electron/electron/tree/master/docs-translations/tr-TR/project/README.md) | [German](https://github.com/electron/electron/tree/master/docs-translations/de-DE/project/README.md) :memo: Available Translations: 🇨🇳 🇹🇼 🇧🇷 🇪🇸 🇰🇷 🇯🇵 🇷🇺 🇫🇷 🇹🇭 🇳🇱 🇹🇷 🇮🇩 🇺🇦 🇨🇿 🇮🇹.
View these docs in other languages at [electron/electron-i18n](https://github.com/electron/electron-i18n/tree/master/content/).
The Electron framework lets you write cross-platform desktop applications The Electron framework lets you write cross-platform desktop applications
using JavaScript, HTML and CSS. It is based on [Node.js](https://nodejs.org/) and using JavaScript, HTML and CSS. It is based on [Node.js](https://nodejs.org/) and
[Chromium](http://www.chromium.org) and is used by the [Atom [Chromium](http://www.chromium.org) and is used by the [Atom
editor](https://github.com/atom/atom) and many other [apps](https://electron.atom.io/apps). editor](https://github.com/atom/atom) and many other [apps](https://electronjs.org/apps).
Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important
announcements. announcements.
This project adheres to the Contributor Covenant This project adheres to the Contributor Covenant
[code of conduct](https://github.com/electron/electron/tree/master/CODE_OF_CONDUCT.md). [code of conduct](https://github.com/electron/electron/tree/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable By participating, you are expected to uphold this code. Please report unacceptable
behavior to [electron@github.com](mailto:electron@github.com). behavior to [electron@github.com](mailto:electron@github.com).
@ -32,29 +33,29 @@ npm install electron --save-dev --save-exact
The `--save-exact` flag is recommended as Electron does not follow semantic The `--save-exact` flag is recommended as Electron does not follow semantic
versioning. For info on how to manage Electron versions in your apps, see versioning. For info on how to manage Electron versions in your apps, see
[Electron versioning](https://electron.atom.io/docs/tutorial/electron-versioning/). [Electron versioning](https://electronjs.org/docs/tutorial/electron-versioning).
For more installation options and troubleshooting tips, see For more installation options and troubleshooting tips, see
[installation](https://electron.atom.io/docs/tutorial/installation/). [installation](https://electronjs.org/docs/tutorial/installation).
## Quick Start ## Quick start
Clone and run the Clone and run the
[electron/electron-quick-start](https://github.com/electron/electron-quick-start) [electron/electron-quick-start](https://github.com/electron/electron-quick-start)
repository to see a minimal Electron app in action: repository to see a minimal Electron app in action:
``` ```sh
git clone https://github.com/electron/electron-quick-start git clone https://github.com/electron/electron-quick-start
cd electron-quick-start cd electron-quick-start
npm install npm install
npm start npm start
``` ```
## Resources for Learning Electron ## Resources for learning Electron
- [electron.atom.io/docs](http://electron.atom.io/docs) - all of Electron's documentation - [electronjs.org/docs](https://electronjs.org/docs) - all of Electron's documentation
- [electron/electron-quick-start](https://github.com/electron/electron-quick-start) - a very basic starter Electron app - [electron/electron-quick-start](https://github.com/electron/electron-quick-start) - a very basic starter Electron app
- [electron.atom.io/community/#boilerplates](http://electron.atom.io/community/#boilerplates) - sample starter apps created by the community - [electronjs.org/community#boilerplates](https://electronjs.org/community#boilerplates) - sample starter apps created by the community
- [electron/simple-samples](https://github.com/electron/simple-samples) - small applications with ideas for taking them further - [electron/simple-samples](https://github.com/electron/simple-samples) - small applications with ideas for taking them further
- [electron/electron-api-demos](https://github.com/electron/electron-api-demos) - an Electron app that teaches you how to use Electron - [electron/electron-api-demos](https://github.com/electron/electron-api-demos) - an Electron app that teaches you how to use Electron
- [hokein/electron-sample-apps](https://github.com/hokein/electron-sample-apps) - small demo apps for the various Electron APIs - [hokein/electron-sample-apps](https://github.com/hokein/electron-sample-apps) - small demo apps for the various Electron APIs

View file

@ -633,12 +633,17 @@ void App::OnLogin(LoginHandler* login_handler,
const base::DictionaryValue& request_details) { const base::DictionaryValue& request_details) {
v8::Locker locker(isolate()); v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate()); v8::HandleScope handle_scope(isolate());
bool prevent_default = Emit( bool prevent_default = false;
"login", content::WebContents* web_contents = login_handler->GetWebContents();
WebContents::CreateFrom(isolate(), login_handler->GetWebContents()), if (web_contents) {
request_details, prevent_default =
login_handler->auth_info(), Emit("login",
base::Bind(&PassLoginInformation, make_scoped_refptr(login_handler))); WebContents::CreateFrom(isolate(), web_contents),
request_details,
login_handler->auth_info(),
base::Bind(&PassLoginInformation,
make_scoped_refptr(login_handler)));
}
// Default behavior is to always cancel the auth. // Default behavior is to always cancel the auth.
if (!prevent_default) if (!prevent_default)

View file

@ -10,6 +10,7 @@
#include "atom/browser/net/url_request_async_asar_job.h" #include "atom/browser/net/url_request_async_asar_job.h"
#include "atom/browser/net/url_request_buffer_job.h" #include "atom/browser/net/url_request_buffer_job.h"
#include "atom/browser/net/url_request_fetch_job.h" #include "atom/browser/net/url_request_fetch_job.h"
#include "atom/browser/net/url_request_stream_job.h"
#include "atom/browser/net/url_request_string_job.h" #include "atom/browser/net/url_request_string_job.h"
#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/callback.h"
#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/native_mate_converters/value_converter.h"
@ -208,6 +209,8 @@ void Protocol::BuildPrototype(
&Protocol::RegisterProtocol<URLRequestAsyncAsarJob>) &Protocol::RegisterProtocol<URLRequestAsyncAsarJob>)
.SetMethod("registerHttpProtocol", .SetMethod("registerHttpProtocol",
&Protocol::RegisterProtocol<URLRequestFetchJob>) &Protocol::RegisterProtocol<URLRequestFetchJob>)
.SetMethod("registerStreamProtocol",
&Protocol::RegisterProtocol<URLRequestStreamJob>)
.SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol) .SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol)
.SetMethod("isProtocolHandled", &Protocol::IsProtocolHandled) .SetMethod("isProtocolHandled", &Protocol::IsProtocolHandled)
.SetMethod("interceptStringProtocol", .SetMethod("interceptStringProtocol",
@ -218,6 +221,8 @@ void Protocol::BuildPrototype(
&Protocol::InterceptProtocol<URLRequestAsyncAsarJob>) &Protocol::InterceptProtocol<URLRequestAsyncAsarJob>)
.SetMethod("interceptHttpProtocol", .SetMethod("interceptHttpProtocol",
&Protocol::InterceptProtocol<URLRequestFetchJob>) &Protocol::InterceptProtocol<URLRequestFetchJob>)
.SetMethod("interceptStreamProtocol",
&Protocol::InterceptProtocol<URLRequestStreamJob>)
.SetMethod("uninterceptProtocol", &Protocol::UninterceptProtocol); .SetMethod("uninterceptProtocol", &Protocol::UninterceptProtocol);
} }

View file

@ -78,6 +78,10 @@ class Protocol : public mate::TrackableObject<Protocol> {
net::URLRequestJob* MaybeCreateJob( net::URLRequestJob* MaybeCreateJob(
net::URLRequest* request, net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override { net::NetworkDelegate* network_delegate) const override {
if (!request->initiator().has_value()) {
// Don't intercept this request as it was created by `net.request`.
return nullptr;
}
RequestJob* request_job = new RequestJob(request, network_delegate); RequestJob* request_job = new RequestJob(request, network_delegate);
request_job->SetHandlerInfo(isolate_, request_context_.get(), handler_); request_job->SetHandlerInfo(isolate_, request_context_.get(), handler_);
return request_job; return request_job;

View file

@ -0,0 +1,121 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include <string>
#include "atom/browser/api/event_subscriber.h"
#include "atom/common/native_mate_converters/callback.h"
namespace {
// A FunctionTemplate lifetime is bound to the v8 context, so it can be safely
// stored as a global here since there's only one for the main process.
v8::Global<v8::FunctionTemplate> g_cached_template;
struct JSHandlerData {
JSHandlerData(v8::Isolate* isolate,
mate::internal::EventSubscriberBase* subscriber)
: handle_(isolate, v8::External::New(isolate, this)),
subscriber_(subscriber) {
handle_.SetWeak(this, GC, v8::WeakCallbackType::kFinalizer);
}
static void GC(const v8::WeakCallbackInfo<JSHandlerData>& data) {
delete data.GetParameter();
}
v8::Global<v8::External> handle_;
mate::internal::EventSubscriberBase* subscriber_;
};
void InvokeCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Locker locker(info.GetIsolate());
v8::HandleScope handle_scope(info.GetIsolate());
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
v8::Context::Scope context_scope(context);
mate::Arguments args(info);
v8::Local<v8::Value> handler, event;
args.GetNext(&handler);
args.GetNext(&event);
DCHECK(handler->IsExternal());
DCHECK(event->IsString());
JSHandlerData* handler_data = static_cast<JSHandlerData*>(
v8::Local<v8::External>::Cast(handler)->Value());
handler_data->subscriber_->EventEmitted(mate::V8ToString(event), &args);
}
} // namespace
namespace mate {
namespace internal {
EventSubscriberBase::EventSubscriberBase(v8::Isolate* isolate,
v8::Local<v8::Object> emitter)
: isolate_(isolate), emitter_(isolate, emitter) {
if (g_cached_template.IsEmpty()) {
g_cached_template = v8::Global<v8::FunctionTemplate>(
isolate_, v8::FunctionTemplate::New(isolate_, InvokeCallback));
}
}
EventSubscriberBase::~EventSubscriberBase() {
if (!isolate_) {
return;
}
RemoveAllListeners();
emitter_.Reset();
DCHECK_EQ(js_handlers_.size(), 0);
}
void EventSubscriberBase::On(const std::string& event_name) {
DCHECK(js_handlers_.find(event_name) == js_handlers_.end());
v8::Locker locker(isolate_);
v8::Isolate::Scope isolate_scope(isolate_);
v8::HandleScope handle_scope(isolate_);
auto fn_template = g_cached_template.Get(isolate_);
auto event = mate::StringToV8(isolate_, event_name);
auto js_handler_data = new JSHandlerData(isolate_, this);
v8::Local<v8::Value> fn = internal::BindFunctionWith(
isolate_, isolate_->GetCurrentContext(), fn_template->GetFunction(),
js_handler_data->handle_.Get(isolate_), event);
js_handlers_.insert(
std::make_pair(event_name, v8::Global<v8::Value>(isolate_, fn)));
internal::ValueVector converted_args = {event, fn};
internal::CallMethodWithArgs(isolate_, emitter_.Get(isolate_), "on",
&converted_args);
}
void EventSubscriberBase::Off(const std::string& event_name) {
v8::Locker locker(isolate_);
v8::Isolate::Scope isolate_scope(isolate_);
v8::HandleScope handle_scope(isolate_);
auto js_handler = js_handlers_.find(event_name);
DCHECK(js_handler != js_handlers_.end());
RemoveListener(js_handler);
}
void EventSubscriberBase::RemoveAllListeners() {
v8::Locker locker(isolate_);
v8::Isolate::Scope isolate_scope(isolate_);
v8::HandleScope handle_scope(isolate_);
while (!js_handlers_.empty()) {
RemoveListener(js_handlers_.begin());
}
}
std::map<std::string, v8::Global<v8::Value>>::iterator
EventSubscriberBase::RemoveListener(
std::map<std::string, v8::Global<v8::Value>>::iterator it) {
internal::ValueVector args = {StringToV8(isolate_, it->first),
it->second.Get(isolate_)};
internal::CallMethodWithArgs(
isolate_, v8::Local<v8::Object>::Cast(emitter_.Get(isolate_)),
"removeListener", &args);
it->second.Reset();
return js_handlers_.erase(it);
}
} // namespace internal
} // namespace mate

View file

@ -0,0 +1,132 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_
#define ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_
#include <map>
#include <string>
#include "atom/common/api/event_emitter_caller.h"
#include "base/synchronization/lock.h"
#include "content/public/browser/browser_thread.h"
#include "native_mate/native_mate/arguments.h"
namespace mate {
namespace internal {
class EventSubscriberBase {
public:
EventSubscriberBase(v8::Isolate* isolate, v8::Local<v8::Object> emitter);
virtual ~EventSubscriberBase();
virtual void EventEmitted(const std::string& event_name,
mate::Arguments* args) = 0;
protected:
void On(const std::string& event_name);
void Off(const std::string& event_name);
void RemoveAllListeners();
private:
std::map<std::string, v8::Global<v8::Value>>::iterator RemoveListener(
std::map<std::string, v8::Global<v8::Value>>::iterator it);
v8::Isolate* isolate_;
v8::Global<v8::Object> emitter_;
std::map<std::string, v8::Global<v8::Value>> js_handlers_;
DISALLOW_COPY_AND_ASSIGN(EventSubscriberBase);
};
} // namespace internal
template <typename HandlerType>
class EventSubscriber : internal::EventSubscriberBase {
public:
using EventCallback = void (HandlerType::*)(mate::Arguments* args);
// Alias to unique_ptr with deleter.
using unique_ptr = std::unique_ptr<EventSubscriber<HandlerType>,
void (*)(EventSubscriber<HandlerType>*)>;
// EventSubscriber should only be created/deleted in the main thread since it
// communicates with the V8 engine. This smart pointer makes it simpler to
// bind the lifetime of EventSubscriber with a class whose lifetime is managed
// by a non-UI thread.
class SafePtr : public unique_ptr {
public:
SafePtr() : SafePtr(nullptr) {}
explicit SafePtr(EventSubscriber<HandlerType>* ptr)
: unique_ptr(ptr, Deleter) {}
private:
// Custom deleter that schedules destructor invocation to the main thread.
static void Deleter(EventSubscriber<HandlerType>* ptr) {
DCHECK(
!::content::BrowserThread::CurrentlyOn(::content::BrowserThread::UI));
DCHECK(ptr);
// Acquire handler lock and reset handler_ to ensure that any new events
// emitted will be ignored after this function returns
base::AutoLock auto_lock(ptr->handler_lock_);
ptr->handler_ = nullptr;
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(
[](EventSubscriber<HandlerType>* subscriber) {
delete subscriber;
},
ptr));
}
};
EventSubscriber(HandlerType* handler,
v8::Isolate* isolate,
v8::Local<v8::Object> emitter)
: EventSubscriberBase(isolate, emitter), handler_(handler) {
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
}
void On(const std::string& event, EventCallback callback) {
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
EventSubscriberBase::On(event);
callbacks_.insert(std::make_pair(event, callback));
}
void Off(const std::string& event) {
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
EventSubscriberBase::Off(event);
DCHECK(callbacks_.find(event) != callbacks_.end());
callbacks_.erase(callbacks_.find(event));
}
void RemoveAllListeners() {
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
EventSubscriberBase::RemoveAllListeners();
callbacks_.clear();
}
private:
void EventEmitted(const std::string& event_name,
mate::Arguments* args) override {
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
base::AutoLock auto_lock(handler_lock_);
if (!handler_) {
// handler_ was probably destroyed by another thread and we should not
// access it.
return;
}
auto it = callbacks_.find(event_name);
if (it != callbacks_.end()) {
auto method = it->second;
(handler_->*method)(args);
}
}
HandlerType* handler_;
base::Lock handler_lock_;
std::map<std::string, EventCallback> callbacks_;
};
} // namespace mate
#endif // ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_

View file

@ -13,7 +13,6 @@
#include "atom/browser/net/about_protocol_handler.h" #include "atom/browser/net/about_protocol_handler.h"
#include "atom/browser/net/asar/asar_protocol_handler.h" #include "atom/browser/net/asar/asar_protocol_handler.h"
#include "atom/browser/net/atom_cert_verifier.h" #include "atom/browser/net/atom_cert_verifier.h"
#include "atom/browser/net/atom_ct_delegate.h"
#include "atom/browser/net/atom_network_delegate.h" #include "atom/browser/net/atom_network_delegate.h"
#include "atom/browser/net/atom_url_request_job_factory.h" #include "atom/browser/net/atom_url_request_job_factory.h"
#include "atom/browser/net/http_protocol_handler.h" #include "atom/browser/net/http_protocol_handler.h"
@ -72,7 +71,6 @@ AtomBrowserContext::AtomBrowserContext(const std::string& partition,
bool in_memory, bool in_memory,
const base::DictionaryValue& options) const base::DictionaryValue& options)
: brightray::BrowserContext(partition, in_memory), : brightray::BrowserContext(partition, in_memory),
ct_delegate_(new AtomCTDelegate),
network_delegate_(new AtomNetworkDelegate), network_delegate_(new AtomNetworkDelegate),
cookie_delegate_(new AtomCookieDelegate) { cookie_delegate_(new AtomCookieDelegate) {
// Construct user agent string. // Construct user agent string.
@ -192,8 +190,9 @@ content::PermissionManager* AtomBrowserContext::GetPermissionManager() {
return permission_manager_.get(); return permission_manager_.get();
} }
std::unique_ptr<net::CertVerifier> AtomBrowserContext::CreateCertVerifier() { std::unique_ptr<net::CertVerifier> AtomBrowserContext::CreateCertVerifier(
return base::WrapUnique(new AtomCertVerifier(ct_delegate_.get())); brightray::RequireCTDelegate* ct_delegate) {
return base::WrapUnique(new AtomCertVerifier(ct_delegate));
} }
std::vector<std::string> AtomBrowserContext::GetCookieableSchemes() { std::vector<std::string> AtomBrowserContext::GetCookieableSchemes() {
@ -204,11 +203,6 @@ std::vector<std::string> AtomBrowserContext::GetCookieableSchemes() {
return default_schemes; return default_schemes;
} }
net::TransportSecurityState::RequireCTDelegate*
AtomBrowserContext::GetRequireCTDelegate() {
return ct_delegate_.get();
}
void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) {
pref_registry->RegisterFilePathPref(prefs::kSelectFileLastDirectory, pref_registry->RegisterFilePathPref(prefs::kSelectFileLastDirectory,
base::FilePath()); base::FilePath());

View file

@ -15,7 +15,6 @@
namespace atom { namespace atom {
class AtomBlobReader; class AtomBlobReader;
class AtomCTDelegate;
class AtomDownloadManagerDelegate; class AtomDownloadManagerDelegate;
class AtomNetworkDelegate; class AtomNetworkDelegate;
class AtomPermissionManager; class AtomPermissionManager;
@ -40,10 +39,9 @@ class AtomBrowserContext : public brightray::BrowserContext {
content::ProtocolHandlerMap* protocol_handlers) override; content::ProtocolHandlerMap* protocol_handlers) override;
net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory( net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory(
const base::FilePath& base_path) override; const base::FilePath& base_path) override;
std::unique_ptr<net::CertVerifier> CreateCertVerifier() override; std::unique_ptr<net::CertVerifier> CreateCertVerifier(
brightray::RequireCTDelegate* ct_delegate) override;
std::vector<std::string> GetCookieableSchemes() override; std::vector<std::string> GetCookieableSchemes() override;
net::TransportSecurityState::RequireCTDelegate* GetRequireCTDelegate()
override;
// content::BrowserContext: // content::BrowserContext:
content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; content::DownloadManagerDelegate* GetDownloadManagerDelegate() override;
@ -69,7 +67,6 @@ class AtomBrowserContext : public brightray::BrowserContext {
std::unique_ptr<WebViewManager> guest_manager_; std::unique_ptr<WebViewManager> guest_manager_;
std::unique_ptr<AtomPermissionManager> permission_manager_; std::unique_ptr<AtomPermissionManager> permission_manager_;
std::unique_ptr<AtomBlobReader> blob_reader_; std::unique_ptr<AtomBlobReader> blob_reader_;
std::unique_ptr<AtomCTDelegate> ct_delegate_;
std::string user_agent_; std::string user_agent_;
bool use_cache_; bool use_cache_;

View file

@ -14,6 +14,7 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h" #include "content/public/browser/download_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/stream_info.h" #include "content/public/browser/stream_info.h"
#include "net/base/escape.h" #include "net/base/escape.h"
#include "net/ssl/client_cert_store.h" #include "net/ssl/client_cert_store.h"
@ -34,8 +35,7 @@ namespace atom {
namespace { namespace {
void OnOpenExternal(const GURL& escaped_url, void OnOpenExternal(const GURL& escaped_url, bool allowed) {
bool allowed) {
if (allowed) if (allowed)
platform_util::OpenExternal( platform_util::OpenExternal(
#if defined(OS_WIN) #if defined(OS_WIN)
@ -66,6 +66,8 @@ void HandleExternalProtocolInUI(
void OnPdfResourceIntercepted( void OnPdfResourceIntercepted(
const GURL& original_url, const GURL& original_url,
int render_process_host_id,
int render_frame_id,
const content::ResourceRequestInfo::WebContentsGetter& const content::ResourceRequestInfo::WebContentsGetter&
web_contents_getter) { web_contents_getter) {
content::WebContents* web_contents = web_contents_getter.Run(); content::WebContents* web_contents = web_contents_getter.Run();
@ -75,7 +77,7 @@ void OnPdfResourceIntercepted(
if (!WebContentsPreferences::IsPluginsEnabled(web_contents)) { if (!WebContentsPreferences::IsPluginsEnabled(web_contents)) {
auto browser_context = web_contents->GetBrowserContext(); auto browser_context = web_contents->GetBrowserContext();
auto download_manager = auto download_manager =
content::BrowserContext::GetDownloadManager(browser_context); content::BrowserContext::GetDownloadManager(browser_context);
download_manager->DownloadUrl( download_manager->DownloadUrl(
content::DownloadUrlParameters::CreateForWebContentsMainFrame( content::DownloadUrlParameters::CreateForWebContentsMainFrame(
@ -86,26 +88,29 @@ void OnPdfResourceIntercepted(
// The URL passes the original pdf resource url, that will be requested // The URL passes the original pdf resource url, that will be requested
// by the webui page. // by the webui page.
// chrome://pdf-viewer/index.html?src=https://somepage/123.pdf // chrome://pdf-viewer/index.html?src=https://somepage/123.pdf
content::NavigationController::LoadURLParams params( content::NavigationController::LoadURLParams params(GURL(base::StringPrintf(
GURL(base::StringPrintf( "%sindex.html?%s=%s", kPdfViewerUIOrigin, kPdfPluginSrc,
"%sindex.html?%s=%s", net::EscapeUrlEncodedData(original_url.spec(), false).c_str())));
kPdfViewerUIOrigin,
kPdfPluginSrc, content::RenderFrameHost* frame_host =
net::EscapeUrlEncodedData(original_url.spec(), false).c_str()))); content::RenderFrameHost::FromID(render_process_host_id, render_frame_id);
if (!frame_host) {
return;
}
params.frame_tree_node_id = frame_host->GetFrameTreeNodeId();
web_contents->GetController().LoadURLWithParams(params); web_contents->GetController().LoadURLWithParams(params);
} }
} // namespace } // namespace
AtomResourceDispatcherHostDelegate::AtomResourceDispatcherHostDelegate() { AtomResourceDispatcherHostDelegate::AtomResourceDispatcherHostDelegate() {}
}
bool AtomResourceDispatcherHostDelegate::HandleExternalProtocol( bool AtomResourceDispatcherHostDelegate::HandleExternalProtocol(
const GURL& url, const GURL& url,
content::ResourceRequestInfo* info) { content::ResourceRequestInfo* info) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&HandleExternalProtocolInUI, base::Bind(&HandleExternalProtocolInUI, url,
url,
info->GetWebContentsGetterForRequest(), info->GetWebContentsGetterForRequest(),
info->HasUserGesture())); info->HasUserGesture()));
return true; return true;
@ -121,16 +126,16 @@ AtomResourceDispatcherHostDelegate::CreateLoginDelegate(
std::unique_ptr<net::ClientCertStore> std::unique_ptr<net::ClientCertStore>
AtomResourceDispatcherHostDelegate::CreateClientCertStore( AtomResourceDispatcherHostDelegate::CreateClientCertStore(
content::ResourceContext* resource_context) { content::ResourceContext* resource_context) {
#if defined(USE_NSS_CERTS) #if defined(USE_NSS_CERTS)
return std::unique_ptr<net::ClientCertStore>(new net::ClientCertStoreNSS( return std::unique_ptr<net::ClientCertStore>(new net::ClientCertStoreNSS(
net::ClientCertStoreNSS::PasswordDelegateFactory())); net::ClientCertStoreNSS::PasswordDelegateFactory()));
#elif defined(OS_WIN) #elif defined(OS_WIN)
return std::unique_ptr<net::ClientCertStore>(new net::ClientCertStoreWin()); return std::unique_ptr<net::ClientCertStore>(new net::ClientCertStoreWin());
#elif defined(OS_MACOSX) #elif defined(OS_MACOSX)
return std::unique_ptr<net::ClientCertStore>(new net::ClientCertStoreMac()); return std::unique_ptr<net::ClientCertStore>(new net::ClientCertStoreMac());
#elif defined(USE_OPENSSL) #elif defined(USE_OPENSSL)
return std::unique_ptr<net::ClientCertStore>(); return std::unique_ptr<net::ClientCertStore>();
#endif #endif
} }
bool AtomResourceDispatcherHostDelegate::ShouldInterceptResourceAsStream( bool AtomResourceDispatcherHostDelegate::ShouldInterceptResourceAsStream(
@ -141,11 +146,20 @@ bool AtomResourceDispatcherHostDelegate::ShouldInterceptResourceAsStream(
std::string* payload) { std::string* payload) {
const content::ResourceRequestInfo* info = const content::ResourceRequestInfo* info =
content::ResourceRequestInfo::ForRequest(request); content::ResourceRequestInfo::ForRequest(request);
if (mime_type == "application/pdf" && info->IsMainFrame()) {
int render_process_host_id;
int render_frame_id;
if (!info->GetAssociatedRenderFrame(&render_process_host_id,
&render_frame_id)) {
return false;
}
if (mime_type == "application/pdf") {
*origin = GURL(kPdfViewerUIOrigin); *origin = GURL(kPdfViewerUIOrigin);
content::BrowserThread::PostTask( content::BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE, BrowserThread::UI, FROM_HERE,
base::Bind(&OnPdfResourceIntercepted, request->url(), base::Bind(&OnPdfResourceIntercepted, request->url(),
render_process_host_id, render_frame_id,
info->GetWebContentsGetterForRequest())); info->GetWebContentsGetterForRequest()));
return true; return true;
} }

View file

@ -337,7 +337,7 @@ bool ScopedDisableResize::disable_resize_ = false;
} }
- (void)windowWillEnterFullScreen:(NSNotification*)notification { - (void)windowWillEnterFullScreen:(NSNotification*)notification {
// Setting resizable to true before entering fullscreen // Setting resizable to true before entering fullscreen
is_resizable_ = shell_->IsResizable(); is_resizable_ = shell_->IsResizable();
shell_->SetResizable(true); shell_->SetResizable(true);
// Hide the native toolbar before entering fullscreen, so there is no visual // Hide the native toolbar before entering fullscreen, so there is no visual
@ -962,10 +962,16 @@ NativeWindowMac::NativeWindowMac(
// We will manage window's lifetime ourselves. // We will manage window's lifetime ourselves.
[window_ setReleasedWhenClosed:NO]; [window_ setReleasedWhenClosed:NO];
// Hide the title bar background
if (title_bar_style_ != NORMAL) {
if (base::mac::IsAtLeastOS10_10()) {
[window_ setTitlebarAppearsTransparent:YES];
}
}
// Hide the title bar. // Hide the title bar.
if (title_bar_style_ == HIDDEN_INSET) { if (title_bar_style_ == HIDDEN_INSET) {
if (base::mac::IsAtLeastOS10_10()) { if (base::mac::IsAtLeastOS10_10()) {
[window_ setTitlebarAppearsTransparent:YES];
base::scoped_nsobject<NSToolbar> toolbar( base::scoped_nsobject<NSToolbar> toolbar(
[[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]);
[toolbar setShowsBaselineSeparator:NO]; [toolbar setShowsBaselineSeparator:NO];

View file

@ -5,11 +5,11 @@
#include "atom/browser/net/atom_cert_verifier.h" #include "atom/browser/net/atom_cert_verifier.h"
#include "atom/browser/browser.h" #include "atom/browser/browser.h"
#include "atom/browser/net/atom_ct_delegate.h"
#include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/net_converter.h"
#include "base/containers/linked_list.h" #include "base/containers/linked_list.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "brightray/browser/net/require_ct_delegate.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "net/base/net_errors.h" #include "net/base/net_errors.h"
#include "net/cert/cert_verify_result.h" #include "net/cert/cert_verify_result.h"
@ -147,7 +147,7 @@ class CertVerifierRequest : public AtomCertVerifier::Request {
base::WeakPtrFactory<CertVerifierRequest> weak_ptr_factory_; base::WeakPtrFactory<CertVerifierRequest> weak_ptr_factory_;
}; };
AtomCertVerifier::AtomCertVerifier(AtomCTDelegate* ct_delegate) AtomCertVerifier::AtomCertVerifier(brightray::RequireCTDelegate* ct_delegate)
: default_cert_verifier_(net::CertVerifier::CreateDefault()), : default_cert_verifier_(net::CertVerifier::CreateDefault()),
ct_delegate_(ct_delegate) {} ct_delegate_(ct_delegate) {}

View file

@ -11,9 +11,14 @@
#include "net/cert/cert_verifier.h" #include "net/cert/cert_verifier.h"
namespace brightray {
class RequireCTDelegate;
} // namespace brightray
namespace atom { namespace atom {
class AtomCTDelegate;
class CertVerifierRequest; class CertVerifierRequest;
struct VerifyRequestParams { struct VerifyRequestParams {
@ -25,7 +30,7 @@ struct VerifyRequestParams {
class AtomCertVerifier : public net::CertVerifier { class AtomCertVerifier : public net::CertVerifier {
public: public:
explicit AtomCertVerifier(AtomCTDelegate* ct_delegate); explicit AtomCertVerifier(brightray::RequireCTDelegate* ct_delegate);
virtual ~AtomCertVerifier(); virtual ~AtomCertVerifier();
using VerifyProc = base::Callback<void(const VerifyRequestParams& request, using VerifyProc = base::Callback<void(const VerifyRequestParams& request,
@ -34,7 +39,7 @@ class AtomCertVerifier : public net::CertVerifier {
void SetVerifyProc(const VerifyProc& proc); void SetVerifyProc(const VerifyProc& proc);
const VerifyProc verify_proc() const { return verify_proc_; } const VerifyProc verify_proc() const { return verify_proc_; }
AtomCTDelegate* ct_delegate() const { return ct_delegate_; } brightray::RequireCTDelegate* ct_delegate() const { return ct_delegate_; }
net::CertVerifier* default_verifier() const { net::CertVerifier* default_verifier() const {
return default_cert_verifier_.get(); return default_cert_verifier_.get();
} }
@ -58,7 +63,7 @@ class AtomCertVerifier : public net::CertVerifier {
std::map<RequestParams, CertVerifierRequest*> inflight_requests_; std::map<RequestParams, CertVerifierRequest*> inflight_requests_;
VerifyProc verify_proc_; VerifyProc verify_proc_;
std::unique_ptr<net::CertVerifier> default_cert_verifier_; std::unique_ptr<net::CertVerifier> default_cert_verifier_;
AtomCTDelegate* ct_delegate_; brightray::RequireCTDelegate* ct_delegate_;
DISALLOW_COPY_AND_ASSIGN(AtomCertVerifier); DISALLOW_COPY_AND_ASSIGN(AtomCertVerifier);
}; };

View file

@ -71,6 +71,7 @@ class JsAsker : public RequestJob {
void Start() override { void Start() override {
std::unique_ptr<base::DictionaryValue> request_details( std::unique_ptr<base::DictionaryValue> request_details(
new base::DictionaryValue); new base::DictionaryValue);
request_start_time_ = base::TimeTicks::Now();
FillRequestDetails(request_details.get(), RequestJob::request()); FillRequestDetails(request_details.get(), RequestJob::request());
content::BrowserThread::PostTask( content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE, content::BrowserThread::UI, FROM_HERE,
@ -86,6 +87,15 @@ class JsAsker : public RequestJob {
int GetResponseCode() const override { return net::HTTP_OK; } int GetResponseCode() const override { return net::HTTP_OK; }
// NOTE: We have to implement this method or risk a crash in blink for
// redirects!
void GetLoadTimingInfo(net::LoadTimingInfo* load_timing_info) const override {
load_timing_info->send_start = request_start_time_;
load_timing_info->send_end = request_start_time_;
load_timing_info->request_start = request_start_time_;
load_timing_info->receive_headers_end = response_start_time_;
}
void GetResponseInfo(net::HttpResponseInfo* info) override { void GetResponseInfo(net::HttpResponseInfo* info) override {
info->headers = new net::HttpResponseHeaders(""); info->headers = new net::HttpResponseHeaders("");
} }
@ -93,6 +103,7 @@ class JsAsker : public RequestJob {
// Called when the JS handler has sent the response, we need to decide whether // Called when the JS handler has sent the response, we need to decide whether
// to start, or fail the job. // to start, or fail the job.
void OnResponse(bool success, std::unique_ptr<base::Value> value) { void OnResponse(bool success, std::unique_ptr<base::Value> value) {
response_start_time_ = base::TimeTicks::Now();
int error = net::ERR_NOT_IMPLEMENTED; int error = net::ERR_NOT_IMPLEMENTED;
if (success && value && !internal::IsErrorOptions(value.get(), &error)) { if (success && value && !internal::IsErrorOptions(value.get(), &error)) {
StartAsync(std::move(value)); StartAsync(std::move(value));
@ -105,6 +116,8 @@ class JsAsker : public RequestJob {
v8::Isolate* isolate_; v8::Isolate* isolate_;
net::URLRequestContextGetter* request_context_getter_; net::URLRequestContextGetter* request_context_getter_;
JavaScriptHandler handler_; JavaScriptHandler handler_;
base::TimeTicks request_start_time_;
base::TimeTicks response_start_time_;
base::WeakPtrFactory<JsAsker> weak_factory_; base::WeakPtrFactory<JsAsker> weak_factory_;

View file

@ -0,0 +1,204 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include <algorithm>
#include <ostream>
#include <string>
#include "atom/browser/net/url_request_stream_job.h"
#include "atom/common/api/event_emitter_caller.h"
#include "atom/common/atom_constants.h"
#include "atom/common/native_mate_converters/net_converter.h"
#include "atom/common/node_includes.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "net/filter/gzip_source_stream.h"
namespace atom {
URLRequestStreamJob::URLRequestStreamJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate)
: JsAsker<net::URLRequestJob>(request, network_delegate),
ended_(false),
errored_(false),
pending_io_buf_(nullptr),
pending_io_buf_size_(0),
response_headers_(nullptr),
weak_factory_(this) {}
void URLRequestStreamJob::BeforeStartInUI(v8::Isolate* isolate,
v8::Local<v8::Value> value) {
if (value->IsNull() || value->IsUndefined() || !value->IsObject()) {
// Invalid opts.
ended_ = true;
errored_ = true;
return;
}
mate::Dictionary opts(isolate, v8::Local<v8::Object>::Cast(value));
int status_code;
if (!opts.Get("statusCode", &status_code)) {
// assume HTTP OK if statusCode is not passed.
status_code = 200;
}
std::string status("HTTP/1.1 ");
status.append(base::IntToString(status_code));
status.append(" ");
status.append(
net::GetHttpReasonPhrase(static_cast<net::HttpStatusCode>(status_code)));
status.append("\0\0", 2);
response_headers_ = new net::HttpResponseHeaders(status);
if (opts.Get("headers", &value)) {
mate::Converter<net::HttpResponseHeaders*>::FromV8(isolate, value,
response_headers_.get());
}
if (!opts.Get("data", &value)) {
// Assume the opts is already a stream
value = opts.GetHandle();
} else if (value->IsNullOrUndefined()) {
// "data" was explicitly passed as null or undefined, assume the user wants
// to send an empty body.
ended_ = true;
return;
}
mate::Dictionary data(isolate, v8::Local<v8::Object>::Cast(value));
if (!data.Get("on", &value) || !value->IsFunction() ||
!data.Get("removeListener", &value) || !value->IsFunction()) {
// If data is passed but it is not a stream, signal an error.
ended_ = true;
errored_ = true;
return;
}
subscriber_.reset(new mate::EventSubscriber<URLRequestStreamJob>(
this, isolate, data.GetHandle()));
subscriber_->On("data", &URLRequestStreamJob::OnData);
subscriber_->On("end", &URLRequestStreamJob::OnEnd);
subscriber_->On("error", &URLRequestStreamJob::OnError);
}
void URLRequestStreamJob::StartAsync(std::unique_ptr<base::Value> options) {
NotifyHeadersComplete();
}
void URLRequestStreamJob::OnData(mate::Arguments* args) {
v8::Local<v8::Value> node_data;
args->GetNext(&node_data);
if (node_data->IsUint8Array()) {
const char* data = node::Buffer::Data(node_data);
size_t data_size = node::Buffer::Length(node_data);
std::copy(data, data + data_size, std::back_inserter(buffer_));
} else {
NOTREACHED();
}
if (pending_io_buf_) {
CopyMoreData(pending_io_buf_, pending_io_buf_size_);
}
}
void URLRequestStreamJob::OnEnd(mate::Arguments* args) {
ended_ = true;
if (pending_io_buf_) {
CopyMoreData(pending_io_buf_, pending_io_buf_size_);
}
}
void URLRequestStreamJob::OnError(mate::Arguments* args) {
errored_ = true;
if (pending_io_buf_) {
CopyMoreData(pending_io_buf_, pending_io_buf_size_);
}
}
int URLRequestStreamJob::ReadRawData(net::IOBuffer* dest, int dest_size) {
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&URLRequestStreamJob::CopyMoreData, weak_factory_.GetWeakPtr(),
make_scoped_refptr(dest), dest_size));
return net::ERR_IO_PENDING;
}
void URLRequestStreamJob::DoneReading() {
subscriber_.reset();
buffer_.clear();
ended_ = true;
}
void URLRequestStreamJob::DoneReadingRedirectResponse() {
DoneReading();
}
void URLRequestStreamJob::CopyMoreDataDone(scoped_refptr<net::IOBuffer> io_buf,
int status) {
if (status <= 0) {
subscriber_.reset();
}
ReadRawDataComplete(status);
io_buf = nullptr;
}
void URLRequestStreamJob::CopyMoreData(scoped_refptr<net::IOBuffer> io_buf,
int io_buf_size) {
// reset any instance references to io_buf
pending_io_buf_ = nullptr;
pending_io_buf_size_ = 0;
int read_count = 0;
if (buffer_.size()) {
size_t count = std::min((size_t)io_buf_size, buffer_.size());
std::copy(buffer_.begin(), buffer_.begin() + count, io_buf->data());
buffer_.erase(buffer_.begin(), buffer_.begin() + count);
read_count = count;
} else if (!ended_ && !errored_) {
// No data available yet, save references to the IOBuffer, which will be
// passed back to this function when OnData/OnEnd/OnError are called
pending_io_buf_ = io_buf;
pending_io_buf_size_ = io_buf_size;
}
if (!pending_io_buf_) {
// Only call CopyMoreDataDone if we have read something.
int status = (errored_ && !read_count) ? net::ERR_FAILED : read_count;
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&URLRequestStreamJob::CopyMoreDataDone,
weak_factory_.GetWeakPtr(), io_buf, status));
}
}
std::unique_ptr<net::SourceStream> URLRequestStreamJob::SetUpSourceStream() {
std::unique_ptr<net::SourceStream> source =
net::URLRequestJob::SetUpSourceStream();
size_t i = 0;
std::string type;
while (response_headers_->EnumerateHeader(&i, "Content-Encoding", &type)) {
if (base::LowerCaseEqualsASCII(type, "gzip") ||
base::LowerCaseEqualsASCII(type, "x-gzip")) {
return net::GzipSourceStream::Create(std::move(source),
net::SourceStream::TYPE_GZIP);
} else if (base::LowerCaseEqualsASCII(type, "deflate")) {
return net::GzipSourceStream::Create(std::move(source),
net::SourceStream::TYPE_DEFLATE);
}
}
return source;
}
bool URLRequestStreamJob::GetMimeType(std::string* mime_type) const {
return response_headers_->GetMimeType(mime_type);
}
int URLRequestStreamJob::GetResponseCode() const {
return response_headers_->response_code();
}
void URLRequestStreamJob::GetResponseInfo(net::HttpResponseInfo* info) {
info->headers = response_headers_;
}
} // namespace atom

View file

@ -0,0 +1,66 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NET_URL_REQUEST_STREAM_JOB_H_
#define ATOM_BROWSER_NET_URL_REQUEST_STREAM_JOB_H_
#include <deque>
#include <string>
#include "atom/browser/api/event_subscriber.h"
#include "atom/browser/net/js_asker.h"
#include "base/memory/ref_counted_memory.h"
#include "native_mate/persistent_dictionary.h"
#include "net/base/io_buffer.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_request_context_getter.h"
#include "v8/include/v8.h"
namespace atom {
class URLRequestStreamJob : public JsAsker<net::URLRequestJob> {
public:
URLRequestStreamJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate);
void OnData(mate::Arguments* args);
void OnEnd(mate::Arguments* args);
void OnError(mate::Arguments* args);
// URLRequestJob
void GetResponseInfo(net::HttpResponseInfo* info) override;
protected:
// URLRequestJob
int ReadRawData(net::IOBuffer* buf, int buf_size) override;
void DoneReading() override;
void DoneReadingRedirectResponse() override;
std::unique_ptr<net::SourceStream> SetUpSourceStream() override;
bool GetMimeType(std::string* mime_type) const override;
int GetResponseCode() const override;
private:
// JSAsker
void BeforeStartInUI(v8::Isolate*, v8::Local<v8::Value>) override;
void StartAsync(std::unique_ptr<base::Value> options) override;
void OnResponse(bool success, std::unique_ptr<base::Value> value);
// Callback after data is asynchronously read from the file into |buf|.
void CopyMoreData(scoped_refptr<net::IOBuffer> io_buf, int io_buf_size);
void CopyMoreDataDone(scoped_refptr<net::IOBuffer> io_buf, int read_count);
std::deque<char> buffer_;
bool ended_;
bool errored_;
scoped_refptr<net::IOBuffer> pending_io_buf_;
int pending_io_buf_size_;
scoped_refptr<net::HttpResponseHeaders> response_headers_;
mate::EventSubscriber<URLRequestStreamJob>::SafePtr subscriber_;
base::WeakPtrFactory<URLRequestStreamJob> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(URLRequestStreamJob);
};
} // namespace atom
#endif // ATOM_BROWSER_NET_URL_REQUEST_STREAM_JOB_H_

View file

@ -58,7 +58,9 @@ void PopulateStreamInfo(base::DictionaryValue* stream_info,
PdfViewerHandler::PdfViewerHandler(const std::string& src) PdfViewerHandler::PdfViewerHandler(const std::string& src)
: stream_(nullptr), original_url_(src) {} : stream_(nullptr), original_url_(src) {}
PdfViewerHandler::~PdfViewerHandler() {} PdfViewerHandler::~PdfViewerHandler() {
RemoveObserver();
}
void PdfViewerHandler::SetPdfResourceStream(content::StreamInfo* stream) { void PdfViewerHandler::SetPdfResourceStream(content::StreamInfo* stream) {
stream_ = stream; stream_ = stream;
@ -90,15 +92,11 @@ void PdfViewerHandler::RegisterMessages() {
} }
void PdfViewerHandler::OnJavascriptAllowed() { void PdfViewerHandler::OnJavascriptAllowed() {
auto zoom_controller = WebContentsZoomController::FromWebContents( AddObserver();
web_ui()->GetWebContents());
zoom_controller->AddObserver(this);
} }
void PdfViewerHandler::OnJavascriptDisallowed() { void PdfViewerHandler::OnJavascriptDisallowed() {
auto zoom_controller = WebContentsZoomController::FromWebContents( RemoveObserver();
web_ui()->GetWebContents());
zoom_controller->RemoveObserver(this);
} }
void PdfViewerHandler::Initialize(const base::ListValue* args) { void PdfViewerHandler::Initialize(const base::ListValue* args) {
@ -214,4 +212,16 @@ void PdfViewerHandler::OnZoomLevelChanged(content::WebContents* web_contents,
} }
} }
void PdfViewerHandler::AddObserver() {
auto zoom_controller =
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
zoom_controller->AddObserver(this);
}
void PdfViewerHandler::RemoveObserver() {
auto zoom_controller =
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
zoom_controller->RemoveObserver(this);
}
} // namespace atom } // namespace atom

View file

@ -45,7 +45,8 @@ class PdfViewerHandler : public content::WebUIMessageHandler,
void Reload(const base::ListValue* args); void Reload(const base::ListValue* args);
void OnZoomLevelChanged(content::WebContents* web_contents, double level, void OnZoomLevelChanged(content::WebContents* web_contents, double level,
bool is_temporary); bool is_temporary);
void AddObserver();
void RemoveObserver();
std::unique_ptr<base::Value> initialize_callback_id_; std::unique_ptr<base::Value> initialize_callback_id_;
content::StreamInfo* stream_; content::StreamInfo* stream_;
std::string original_url_; std::string original_url_;

View file

@ -20,8 +20,18 @@ v8::Local<v8::Value> CallMethodWithArgs(v8::Isolate* isolate,
v8::MicrotasksScope::kRunMicrotasks); v8::MicrotasksScope::kRunMicrotasks);
// Use node::MakeCallback to call the callback, and it will also run pending // Use node::MakeCallback to call the callback, and it will also run pending
// tasks in Node.js. // tasks in Node.js.
return node::MakeCallback(isolate, obj, method, args->size(), &args->front(), v8::MaybeLocal<v8::Value> ret = node::MakeCallback(isolate, obj, method,
{0, 0}).ToLocalChecked(); args->size(),
&args->front(), {0, 0});
// If the JS function throws an exception (doesn't return a value) the result
// of MakeCallback will be empty and therefore ToLocal will be false, in this
// case we need to return "false" as that indicates that the event emitter did
// not handle the event
v8::Local<v8::Value> localRet;
if (ret.ToLocal(&localRet)) {
return localRet;
}
return v8::Boolean::New(isolate, false);
} }
} // namespace internal } // namespace internal

View file

@ -8,11 +8,10 @@
#define ATOM_MAJOR_VERSION 1 #define ATOM_MAJOR_VERSION 1
#define ATOM_MINOR_VERSION 8 #define ATOM_MINOR_VERSION 8
#define ATOM_PATCH_VERSION 2 #define ATOM_PATCH_VERSION 2
#define ATOM_PRE_RELEASE_VERSION -beta.2
#define ATOM_VERSION_IS_RELEASE 1 #ifndef ATOM_PRE_RELEASE_VERSION
# define ATOM_PRE_RELEASE_VERSION ""
#ifndef ATOM_TAG
# define ATOM_TAG ""
#endif #endif
#ifndef ATOM_STRINGIFY #ifndef ATOM_STRINGIFY
@ -20,17 +19,10 @@
#define ATOM_STRINGIFY_HELPER(n) #n #define ATOM_STRINGIFY_HELPER(n) #n
#endif #endif
#if ATOM_VERSION_IS_RELEASE
# define ATOM_VERSION_STRING ATOM_STRINGIFY(ATOM_MAJOR_VERSION) "." \ # define ATOM_VERSION_STRING ATOM_STRINGIFY(ATOM_MAJOR_VERSION) "." \
ATOM_STRINGIFY(ATOM_MINOR_VERSION) "." \ ATOM_STRINGIFY(ATOM_MINOR_VERSION) "." \
ATOM_STRINGIFY(ATOM_PATCH_VERSION) \ ATOM_STRINGIFY(ATOM_PATCH_VERSION) \
ATOM_TAG ATOM_STRINGIFY(ATOM_PRE_RELEASE_VERSION)
#else
# define ATOM_VERSION_STRING ATOM_STRINGIFY(ATOM_MAJOR_VERSION) "." \
ATOM_STRINGIFY(ATOM_MINOR_VERSION) "." \
ATOM_STRINGIFY(ATOM_PATCH_VERSION) \
ATOM_TAG "-pre"
#endif
#define ATOM_VERSION "v" ATOM_VERSION_STRING #define ATOM_VERSION "v" ATOM_VERSION_STRING

View file

@ -38,22 +38,6 @@ void CallTranslater(v8::Local<v8::External> external,
delete holder; delete holder;
} }
// func.bind(func, arg1).
// NB(zcbenz): Using C++11 version crashes VS.
v8::Local<v8::Value> BindFunctionWith(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Function> func,
v8::Local<v8::Value> arg1,
v8::Local<v8::Value> arg2) {
v8::MaybeLocal<v8::Value> bind = func->Get(mate::StringToV8(isolate, "bind"));
CHECK(!bind.IsEmpty());
v8::Local<v8::Function> bind_func =
v8::Local<v8::Function>::Cast(bind.ToLocalChecked());
v8::Local<v8::Value> converted[] = { func, arg1, arg2 };
return bind_func->Call(
context, func, arraysize(converted), converted).ToLocalChecked();
}
} // namespace } // namespace
// Destroy the class on UI thread when possible. // Destroy the class on UI thread when possible.
@ -130,6 +114,22 @@ v8::Local<v8::Value> CreateFunctionFromTranslater(
v8::Object::New(isolate)); v8::Object::New(isolate));
} }
// func.bind(func, arg1).
// NB(zcbenz): Using C++11 version crashes VS.
v8::Local<v8::Value> BindFunctionWith(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Function> func,
v8::Local<v8::Value> arg1,
v8::Local<v8::Value> arg2) {
v8::MaybeLocal<v8::Value> bind = func->Get(mate::StringToV8(isolate, "bind"));
CHECK(!bind.IsEmpty());
v8::Local<v8::Function> bind_func =
v8::Local<v8::Function>::Cast(bind.ToLocalChecked());
v8::Local<v8::Value> converted[] = {func, arg1, arg2};
return bind_func->Call(context, func, arraysize(converted), converted)
.ToLocalChecked();
}
} // namespace internal } // namespace internal
} // namespace mate } // namespace mate

View file

@ -111,6 +111,11 @@ struct V8FunctionInvoker<ReturnType(ArgTypes...)> {
using Translater = base::Callback<void(Arguments* args)>; using Translater = base::Callback<void(Arguments* args)>;
v8::Local<v8::Value> CreateFunctionFromTranslater( v8::Local<v8::Value> CreateFunctionFromTranslater(
v8::Isolate* isolate, const Translater& translater); v8::Isolate* isolate, const Translater& translater);
v8::Local<v8::Value> BindFunctionWith(v8::Isolate* isolate,
v8::Local<v8::Context> context,
v8::Local<v8::Function> func,
v8::Local<v8::Value> arg1,
v8::Local<v8::Value> arg2);
// Calls callback with Arguments. // Calls callback with Arguments.
template <typename Sig> template <typename Sig>

View file

@ -165,6 +165,35 @@ v8::Local<v8::Value> Converter<net::HttpResponseHeaders*>::ToV8(
return ConvertToV8(isolate, response_headers); return ConvertToV8(isolate, response_headers);
} }
bool Converter<net::HttpResponseHeaders*>::FromV8(
v8::Isolate* isolate,
v8::Local<v8::Value> val,
net::HttpResponseHeaders* out) {
if (!val->IsObject()) {
return false;
}
auto context = isolate->GetCurrentContext();
auto headers = v8::Local<v8::Object>::Cast(val);
auto keys = headers->GetOwnPropertyNames();
for (uint32_t i = 0; i < keys->Length(); i++) {
v8::Local<v8::String> key, value;
if (!keys->Get(i)->ToString(context).ToLocal(&key)) {
return false;
}
if (!headers->Get(key)->ToString(context).ToLocal(&value)) {
return false;
}
v8::String::Utf8Value key_utf8(key);
v8::String::Utf8Value value_utf8(value);
std::string k(*key_utf8, key_utf8.length());
std::string v(*value_utf8, value_utf8.length());
std::ostringstream tmp;
tmp << k << ": " << v;
out->AddHeader(tmp.str());
}
return true;
}
} // namespace mate } // namespace mate
namespace atom { namespace atom {
@ -180,6 +209,13 @@ void FillRequestDetails(base::DictionaryValue* details,
GetUploadData(list.get(), request); GetUploadData(list.get(), request);
if (!list->empty()) if (!list->empty())
details->Set("uploadData", std::move(list)); details->Set("uploadData", std::move(list));
std::unique_ptr<base::DictionaryValue> headers_value(
new base::DictionaryValue);
for (net::HttpRequestHeaders::Iterator it(request->extra_request_headers());
it.GetNext();) {
headers_value->SetString(it.name(), it.value());
}
details->Set("headers", std::move(headers_value));
} }
void GetUploadData(base::ListValue* upload_data_list, void GetUploadData(base::ListValue* upload_data_list,

View file

@ -49,6 +49,9 @@ template <>
struct Converter<net::HttpResponseHeaders*> { struct Converter<net::HttpResponseHeaders*> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
net::HttpResponseHeaders* headers); net::HttpResponseHeaders* headers);
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
net::HttpResponseHeaders* out);
}; };
} // namespace mate } // namespace mate

View file

@ -1,28 +1,28 @@
// Copyright (c) 2016 GitHub, Inc. // Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be // Use of this source code is governed by the MIT license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "atom/browser/net/atom_ct_delegate.h" #include "brightray/browser/net/require_ct_delegate.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
namespace atom { namespace brightray {
AtomCTDelegate::AtomCTDelegate() {} RequireCTDelegate::RequireCTDelegate() {}
AtomCTDelegate::~AtomCTDelegate() {} RequireCTDelegate::~RequireCTDelegate() {}
void AtomCTDelegate::AddCTExcludedHost(const std::string& host) { void RequireCTDelegate::AddCTExcludedHost(const std::string& host) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ct_excluded_hosts_.insert(host); ct_excluded_hosts_.insert(host);
} }
void AtomCTDelegate::ClearCTExcludedHostsList() { void RequireCTDelegate::ClearCTExcludedHostsList() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
ct_excluded_hosts_.clear(); ct_excluded_hosts_.clear();
} }
AtomCTDelegate::CTRequirementLevel AtomCTDelegate::IsCTRequiredForHost( RequireCTDelegate::CTRequirementLevel RequireCTDelegate::IsCTRequiredForHost(
const std::string& host) { const std::string& host) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!ct_excluded_hosts_.empty() && if (!ct_excluded_hosts_.empty() &&
@ -31,4 +31,4 @@ AtomCTDelegate::CTRequirementLevel AtomCTDelegate::IsCTRequiredForHost(
return CTRequirementLevel::DEFAULT; return CTRequirementLevel::DEFAULT;
} }
} // namespace atom } // namespace brightray

View file

@ -1,21 +1,22 @@
// Copyright (c) 2016 GitHub, Inc. // Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be // Use of this source code is governed by the MIT license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef ATOM_BROWSER_NET_ATOM_CT_DELEGATE_H_ #ifndef BRIGHTRAY_BROWSER_NET_REQUIRE_CT_DELEGATE_H_
#define ATOM_BROWSER_NET_ATOM_CT_DELEGATE_H_ #define BRIGHTRAY_BROWSER_NET_REQUIRE_CT_DELEGATE_H_
#include <set> #include <set>
#include <string> #include <string>
#include "net/http/transport_security_state.h" #include "net/http/transport_security_state.h"
namespace atom { namespace brightray {
class AtomCTDelegate : public net::TransportSecurityState::RequireCTDelegate { class RequireCTDelegate
: public net::TransportSecurityState::RequireCTDelegate {
public: public:
AtomCTDelegate(); RequireCTDelegate();
~AtomCTDelegate() override; ~RequireCTDelegate() override;
void AddCTExcludedHost(const std::string& host); void AddCTExcludedHost(const std::string& host);
void ClearCTExcludedHostsList(); void ClearCTExcludedHostsList();
@ -25,9 +26,9 @@ class AtomCTDelegate : public net::TransportSecurityState::RequireCTDelegate {
private: private:
std::set<std::string> ct_excluded_hosts_; std::set<std::string> ct_excluded_hosts_;
DISALLOW_COPY_AND_ASSIGN(AtomCTDelegate); DISALLOW_COPY_AND_ASSIGN(RequireCTDelegate);
}; };
} // namespace atom } // namespace brightray
#endif // ATOM_BROWSER_NET_ATOM_CT_DELEGATE_H_ #endif // BRIGHTRAY_BROWSER_NET_REQUIRE_CT_DELEGATE_H_

View file

@ -14,6 +14,7 @@
#include "base/threading/worker_pool.h" #include "base/threading/worker_pool.h"
#include "brightray/browser/net/devtools_network_controller_handle.h" #include "brightray/browser/net/devtools_network_controller_handle.h"
#include "brightray/browser/net/devtools_network_transaction_factory.h" #include "brightray/browser/net/devtools_network_transaction_factory.h"
#include "brightray/browser/net/require_ct_delegate.h"
#include "brightray/browser/net_log.h" #include "brightray/browser/net_log.h"
#include "brightray/browser/network_delegate.h" #include "brightray/browser/network_delegate.h"
#include "brightray/common/switches.h" #include "brightray/common/switches.h"
@ -107,7 +108,8 @@ URLRequestContextGetter::Delegate::CreateHttpCacheBackendFactory(
} }
std::unique_ptr<net::CertVerifier> std::unique_ptr<net::CertVerifier>
URLRequestContextGetter::Delegate::CreateCertVerifier() { URLRequestContextGetter::Delegate::CreateCertVerifier(
RequireCTDelegate* ct_delegate) {
return net::CertVerifier::CreateDefault(); return net::CertVerifier::CreateDefault();
} }
@ -170,6 +172,7 @@ net::URLRequestContext* URLRequestContextGetter::GetURLRequestContext() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!url_request_context_.get()) { if (!url_request_context_.get()) {
ct_delegate_.reset(new RequireCTDelegate);
auto& command_line = *base::CommandLine::ForCurrentProcess(); auto& command_line = *base::CommandLine::ForCurrentProcess();
url_request_context_.reset(new net::URLRequestContext); url_request_context_.reset(new net::URLRequestContext);
@ -280,10 +283,10 @@ net::URLRequestContext* URLRequestContextGetter::GetURLRequestContext() {
std::unique_ptr<net::TransportSecurityState> transport_security_state = std::unique_ptr<net::TransportSecurityState> transport_security_state =
base::WrapUnique(new net::TransportSecurityState); base::WrapUnique(new net::TransportSecurityState);
transport_security_state->SetRequireCTDelegate( transport_security_state->SetRequireCTDelegate(ct_delegate_.get());
delegate_->GetRequireCTDelegate());
storage_->set_transport_security_state(std::move(transport_security_state)); storage_->set_transport_security_state(std::move(transport_security_state));
storage_->set_cert_verifier(delegate_->CreateCertVerifier()); storage_->set_cert_verifier(
delegate_->CreateCertVerifier(ct_delegate_.get()));
storage_->set_ssl_config_service(delegate_->CreateSSLConfigService()); storage_->set_ssl_config_service(delegate_->CreateSSLConfigService());
storage_->set_http_auth_handler_factory(std::move(auth_handler_factory)); storage_->set_http_auth_handler_factory(std::move(auth_handler_factory));
std::unique_ptr<net::HttpServerProperties> server_properties( std::unique_ptr<net::HttpServerProperties> server_properties(

View file

@ -33,6 +33,7 @@ class URLRequestJobFactory;
namespace brightray { namespace brightray {
class RequireCTDelegate;
class DevToolsNetworkControllerHandle; class DevToolsNetworkControllerHandle;
class MediaDeviceIDSalt; class MediaDeviceIDSalt;
class NetLog; class NetLog;
@ -53,13 +54,10 @@ class URLRequestContextGetter : public net::URLRequestContextGetter {
CreateURLRequestJobFactory(content::ProtocolHandlerMap* protocol_handlers); CreateURLRequestJobFactory(content::ProtocolHandlerMap* protocol_handlers);
virtual net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory( virtual net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory(
const base::FilePath& base_path); const base::FilePath& base_path);
virtual std::unique_ptr<net::CertVerifier> CreateCertVerifier(); virtual std::unique_ptr<net::CertVerifier> CreateCertVerifier(
RequireCTDelegate* ct_delegate);
virtual net::SSLConfigService* CreateSSLConfigService(); virtual net::SSLConfigService* CreateSSLConfigService();
virtual std::vector<std::string> GetCookieableSchemes(); virtual std::vector<std::string> GetCookieableSchemes();
virtual net::TransportSecurityState::RequireCTDelegate*
GetRequireCTDelegate() {
return nullptr;
}
virtual MediaDeviceIDSalt* GetMediaDeviceIDSalt() { return nullptr; } virtual MediaDeviceIDSalt* GetMediaDeviceIDSalt() { return nullptr; }
}; };
@ -98,6 +96,7 @@ class URLRequestContextGetter : public net::URLRequestContextGetter {
std::string user_agent_; std::string user_agent_;
std::unique_ptr<RequireCTDelegate> ct_delegate_;
std::unique_ptr<net::ProxyConfigService> proxy_config_service_; std::unique_ptr<net::ProxyConfigService> proxy_config_service_;
std::unique_ptr<net::NetworkDelegate> network_delegate_; std::unique_ptr<net::NetworkDelegate> network_delegate_;
std::unique_ptr<net::URLRequestContextStorage> storage_; std::unique_ptr<net::URLRequestContextStorage> storage_;

View file

@ -61,6 +61,8 @@
'browser/net/devtools_network_transaction.h', 'browser/net/devtools_network_transaction.h',
'browser/net/devtools_network_upload_data_stream.cc', 'browser/net/devtools_network_upload_data_stream.cc',
'browser/net/devtools_network_upload_data_stream.h', 'browser/net/devtools_network_upload_data_stream.h',
'browser/net/require_ct_delegate.cc',
'browser/net/require_ct_delegate.h',
'browser/net_log.cc', 'browser/net_log.cc',
'browser/net_log.h', 'browser/net_log.h',
'browser/network_delegate.cc', 'browser/network_delegate.cc',

View file

@ -204,7 +204,7 @@
<nav> <nav>
<div class="linkcol"> <div class="linkcol">
<a class="hero-link" href="https://electron.atom.io/blog"> <a class="hero-link" href="https://electronjs.org/blog">
<span class="octicon hero-octicon octicon-gist" aria-hidden="true"></span> <span class="octicon hero-octicon octicon-gist" aria-hidden="true"></span>
<h4>Blog</h4> <h4>Blog</h4>
</a> </a>
@ -216,7 +216,7 @@
</a> </a>
</div> </div>
<div class="linkcol"> <div class="linkcol">
<a class="hero-link" href="https://electron.atom.io/docs"> <a class="hero-link" href="https://electronjs.org/docs">
<span class="octicon hero-octicon octicon-gear" aria-hidden="true"></span> <span class="octicon hero-octicon octicon-gear" aria-hidden="true"></span>
<h4>Docs</h4> <h4>Docs</h4>
</a> </a>

View file

@ -138,7 +138,7 @@ app.once('ready', () => {
{ {
label: 'Learn More', label: 'Learn More',
click () { click () {
shell.openExternal('https://electron.atom.io') shell.openExternal('https://electronjs.org')
} }
}, },
{ {

View file

@ -26,8 +26,8 @@ git clone https://github.com/electron/electron-i18n
vmd electron-i18n/content/zh-CN vmd electron-i18n/content/zh-CN
``` ```
[crowdin.com/projects/electron]: https://crowdin.com/projects/electron [crowdin.com/project/electron]: https://crowdin.com/project/electron
[Crowdin]: https://crowdin.com/projects/electron [Crowdin]: https://crowdin.com/project/electron
[electron/electron-i18n]: https://github.com/electron/electron-i18n#readme [electron/electron-i18n]: https://github.com/electron/electron-i18n#readme
[electron/electron-i18n/tree/master/content]: https://github.com/electron/electron-i18n/tree/master/content [electron/electron-i18n/tree/master/content]: https://github.com/electron/electron-i18n/tree/master/content
[vmd]: http://ghub.io/vmd [vmd]: http://ghub.io/vmd

View file

@ -30,7 +30,7 @@ let view = new BrowserView({
}) })
win.setBrowserView(view) win.setBrowserView(view)
view.setBounds({ x: 0, y: 0, width: 300, height: 300 }) view.setBounds({ x: 0, y: 0, width: 300, height: 300 })
view.webContents.loadURL('https://electron.atom.io') view.webContents.loadURL('https://electronjs.org')
``` ```
### `new BrowserView([options])` _Experimental_ ### `new BrowserView([options])` _Experimental_

View file

@ -103,7 +103,7 @@ clipboard.
```js ```js
clipboard.write({ clipboard.write({
text: 'https://electron.atom.io', text: 'https://electronjs.org',
bookmark: 'Electron Homepage' bookmark: 'Electron Homepage'
}) })
``` ```

View file

@ -1,6 +1,5 @@
## Class: Menu ## Class: Menu
> Create native application menus and context menus. > Create native application menus and context menus.
Process: [Main](../glossary.md#main-process) Process: [Main](../glossary.md#main-process)
@ -15,7 +14,7 @@ The `menu` class has the following static methods:
#### `Menu.setApplicationMenu(menu)` #### `Menu.setApplicationMenu(menu)`
* `menu` Menu * `menu` Menu | null
Sets `menu` as the application menu on macOS. On Windows and Linux, the Sets `menu` as the application menu on macOS. On Windows and Linux, the
`menu` will be set as each window's top menu. `menu` will be set as each window's top menu.
@ -27,7 +26,7 @@ effect on macOS.
#### `Menu.getApplicationMenu()` #### `Menu.getApplicationMenu()`
Returns `Menu` - The application menu, if set, or `null`, if not set. Returns `Menu | null` - The application menu, if set, or `null`, if not set.
**Note:** The returned `Menu` instance doesn't support dynamic addition or **Note:** The returned `Menu` instance doesn't support dynamic addition or
removal of menu items. [Instance properties](#instance-properties) can still removal of menu items. [Instance properties](#instance-properties) can still
@ -167,7 +166,7 @@ const template = [
submenu: [ submenu: [
{ {
label: 'Learn More', label: 'Learn More',
click () { require('electron').shell.openExternal('https://electron.atom.io') } click () { require('electron').shell.openExternal('https://electronjs.org') }
} }
] ]
} }

View file

@ -194,6 +194,67 @@ request to have a different session you should set `session` to `null`.
For POST requests the `uploadData` object must be provided. For POST requests the `uploadData` object must be provided.
### `protocol.registerStreamProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` Object
* `url` String
* `headers` Object
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `stream` (ReadableStream | [StreamProtocolResponse](structures/stream-protocol-response.md)) (optional)
* `completion` Function (optional)
* `error` Error
Registers a protocol of `scheme` that will send a `Readable` as a response.
The usage is similar to the other `register{Any}Protocol`, except that the
`callback` should be called with either a `Readable` object or an object that
has the `data`, `statusCode`, and `headers` properties.
Example:
```javascript
const {protocol} = require('electron')
const {PassThrough} = require('stream')
function createStream (text) {
const rv = new PassThrough() // PassThrough is also a Readable stream
rv.push(text)
rv.push(null)
return rv
}
protocol.registerStreamProtocol('atom', (request, callback) => {
callback({
statusCode: 200,
headers: {
'content-type': 'text/html'
},
data: createStream('<h5>Response</h5>')
})
}, (error) => {
if (error) console.error('Failed to register protocol')
})
```
It is possible to pass any object that implements the readable stream API (emits
`data`/`end`/`error` events). For example, here's how a file could be returned:
```javascript
const {protocol} = require('electron')
const fs = require('fs')
protocol.registerStreamProtocol('atom', (request, callback) => {
callback(fs.createReadStream('index.html'))
}, (error) => {
if (error) console.error('Failed to register protocol')
})
```
### `protocol.unregisterProtocol(scheme[, completion])` ### `protocol.unregisterProtocol(scheme[, completion])`
* `scheme` String * `scheme` String
@ -285,6 +346,24 @@ which sends a `Buffer` as a response.
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
which sends a new HTTP request as a response. which sends a new HTTP request as a response.
### `protocol.interceptStreamProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` Object
* `url` String
* `headers` Object
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `stream` (ReadableStream | [StreamProtocolResponse](structures/stream-protocol-response.md)) (optional)
* `completion` Function (optional)
* `error` Error
Same as `protocol.registerStreamProtocol`, except that it replaces an existing
protocol handler.
### `protocol.uninterceptProtocol(scheme[, completion])` ### `protocol.uninterceptProtocol(scheme[, completion])`
* `scheme` String * `scheme` String

View file

@ -259,7 +259,7 @@ the original network configuration.
* `verificationResult` Integer - Value can be one of certificate error codes * `verificationResult` Integer - Value can be one of certificate error codes
from [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). from [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h).
Apart from the certificate error codes, the following special codes can be used. Apart from the certificate error codes, the following special codes can be used.
* `0` - Indicates success and disables Certificate Transperancy verification. * `0` - Indicates success and disables Certificate Transparency verification.
* `-2` - Indicates failure. * `-2` - Indicates failure.
* `-3` - Uses the verification result from chromium. * `-3` - Uses the verification result from chromium.
@ -287,7 +287,7 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
#### `ses.setPermissionRequestHandler(handler)` #### `ses.setPermissionRequestHandler(handler)`
* `handler` Function * `handler` Function | null
* `webContents` [WebContents](web-contents.md) - WebContents requesting the permission. * `webContents` [WebContents](web-contents.md) - WebContents requesting the permission.
* `permission` String - Enum of 'media', 'geolocation', 'notifications', 'midiSysex', * `permission` String - Enum of 'media', 'geolocation', 'notifications', 'midiSysex',
'pointerLock', 'fullscreen', 'openExternal'. 'pointerLock', 'fullscreen', 'openExternal'.
@ -296,6 +296,7 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
Sets the handler which can be used to respond to permission requests for the `session`. Sets the handler which can be used to respond to permission requests for the `session`.
Calling `callback(true)` will allow the permission and `callback(false)` will reject it. Calling `callback(true)` will allow the permission and `callback(false)` will reject it.
To clear the handler, call `setPermissionRequestHandler(null)`.
```javascript ```javascript
const {session} = require('electron') const {session} = require('electron')

View file

@ -0,0 +1,5 @@
# StreamProtocolResponse Object
* `statusCode` Number - The HTTP response code
* `headers` Object - An object containing the response headers
* `data` ReadableStream - A Node.js readable stream representing the response body

View file

@ -177,7 +177,7 @@ Web security is enabled by default.
```html ```html
<webview src="https://github.com" partition="persist:github"></webview> <webview src="https://github.com" partition="persist:github"></webview>
<webview src="https://electron.atom.io" partition="electron"></webview> <webview src="https://electronjs.org" partition="electron"></webview>
``` ```
Sets the session used by the page. If `partition` starts with `persist:`, the Sets the session used by the page. If `partition` starts with `persist:`, the

View file

@ -149,18 +149,19 @@ information may help you.
### Building `libchromiumcontent` locally ### Building `libchromiumcontent` locally
To avoid using the prebuilt binaries of `libchromiumcontent`, you can build `libchromiumcontent` locally. To do so, follow these steps: To avoid using the prebuilt binaries of `libchromiumcontent`, you can build `libchromiumcontent` locally. To do so, follow these steps:
1. Install [depot_tools](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md#Install)
2. Install [additional build dependencies](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md#Install-additional-build-dependencies)
3. Fetch the git submodules:
```bash
$ git submodule update --init --recursive
```
4. Pass the `--build_release_libcc` switch to `bootstrap.py` script:
```bash 1. Install [depot_tools](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md#Install)
$ ./script/bootstrap.py -v --build_release_libcc 2. Install [additional build dependencies](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md#Install-additional-build-dependencies)
``` 3. Fetch the git submodules:
```bash
$ git submodule update --init --recursive
```
4. Pass the `--build_release_libcc` switch to `bootstrap.py` script:
```bash
$ ./script/bootstrap.py -v --build_release_libcc
```
Note that by default the `shared_library` configuration is not built, so you can Note that by default the `shared_library` configuration is not built, so you can
only build `Release` version of Electron if you use this mode: only build `Release` version of Electron if you use this mode:
@ -218,4 +219,4 @@ custom the building configurations:
* `LDFLAGS` * `LDFLAGS`
The environment variables have to be set when executing the `bootstrap.py` The environment variables have to be set when executing the `bootstrap.py`
script, it won't work in the `build.py` script. script, it won't work in the `build.py` script.

View file

@ -64,7 +64,7 @@ The following rules only apply to the documentation of APIs.
Each page must use the actual object name returned by `require('electron')` Each page must use the actual object name returned by `require('electron')`
as the title, such as `BrowserWindow`, `autoUpdater`, and `session`. as the title, such as `BrowserWindow`, `autoUpdater`, and `session`.
Under the page tile must be a one-line description starting with `>`. Under the page title must be a one-line description starting with `>`.
Using `session` as example: Using `session` as example:

View file

@ -1,10 +1,10 @@
# About Electron # About Electron
[Electron](https://electron.atom.io) is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining [Chromium](https://www.chromium.org/Home) and [Node.js](https://nodejs.org) into a single runtime and apps can be packaged for Mac, Windows, and Linux. [Electron](https://electronjs.org) is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining [Chromium](https://www.chromium.org/Home) and [Node.js](https://nodejs.org) into a single runtime and apps can be packaged for Mac, Windows, and Linux.
Electron began in 2013 as the framework on which [Atom](https://atom.io), GitHub's hackable text editor, would be built. The two were open sourced in the Spring of 2014. Electron began in 2013 as the framework on which [Atom](https://atom.io), GitHub's hackable text editor, would be built. The two were open sourced in the Spring of 2014.
It has since become a popular tool used by open source developers, startups, and established companies. [See who is building on Electron](https://electron.atom.io/apps/). It has since become a popular tool used by open source developers, startups, and established companies. [See who is building on Electron](https://electronjs.org/apps).
Read on to learn more about the contributors and releases of Electron or get started building with Electron in the [Quick Start Guide](quick-start.md). Read on to learn more about the contributors and releases of Electron or get started building with Electron in the [Quick Start Guide](quick-start.md).
@ -27,13 +27,13 @@ In Electron, Node.js and Chromium share a single V8 instance—usually the versi
### Versioning ### Versioning
Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](https://electron.atom.io/docs/tutorial/electron-versioning/) or see the [versions currently in use](https://electron.atom.io/#electron-versions). Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](https://electronjs.org/docs/tutorial/electron-versioning) or see the [versions currently in use](https://electronjs.org/#electron-versions).
### LTS ### LTS
Long term support of older versions of Electron does not currently exist. If your current version of Electron works for you, you can stay on it for as long as you'd like. If you want to make use of new features as they come in you should upgrade to a newer version. Long term support of older versions of Electron does not currently exist. If your current version of Electron works for you, you can stay on it for as long as you'd like. If you want to make use of new features as they come in you should upgrade to a newer version.
A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](https://electron.atom.io/blog/2016/05/11/electron-1-0). A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](https://electronjs.org/blog/electron-1-0).
## Core Philosophy ## Core Philosophy
@ -41,7 +41,7 @@ In order to keep Electron small (file size) and sustainable (the spread of depen
For instance, Electron uses just the rendering library from Chromium rather than all of Chromium. This makes it easier to upgrade Chromium but also means some browser features found in Google Chrome do not exist in Electron. For instance, Electron uses just the rendering library from Chromium rather than all of Chromium. This makes it easier to upgrade Chromium but also means some browser features found in Google Chrome do not exist in Electron.
New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](https://electron.atom.io/community). New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](https://electronjs.org/community).
## History ## History
@ -52,6 +52,6 @@ Below are milestones in Electron's history.
| **April 2013**| [Atom Shell is started](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| | **April 2013**| [Atom Shell is started](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).|
| **May 2014** | [Atom Shell is open sourced](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | | **May 2014** | [Atom Shell is open sourced](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). |
| **April 2015** | [Atom Shell is re-named Electron](https://github.com/electron/electron/pull/1389). | | **April 2015** | [Atom Shell is re-named Electron](https://github.com/electron/electron/pull/1389). |
| **May 2016** | [Electron releases `v1.0.0`](https://electron.atom.io/blog/2016/05/11/electron-1-0).| | **May 2016** | [Electron releases `v1.0.0`](https://electronjs.org/blog/electron-1-0).|
| **May 2016** | [Electron apps compatible with Mac App Store](https://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| | **May 2016** | [Electron apps compatible with Mac App Store](https://electronjs.org/docs/tutorial/mac-app-store-submission-guide).|
| **August 2016** | [Windows Store support for Electron apps](https://electron.atom.io/docs/tutorial/windows-store-guide).| | **August 2016** | [Windows Store support for Electron apps](https://electronjs.org/docs/tutorial/windows-store-guide).|

View file

@ -6,7 +6,7 @@ Making accessible applications is important and we're happy to introduce new fun
Accessibility concerns in Electron applications are similar to those of websites because they're both ultimately HTML. With Electron apps, however, you can't use the online resources for accessibility audits because your app doesn't have a URL to point the auditor to. Accessibility concerns in Electron applications are similar to those of websites because they're both ultimately HTML. With Electron apps, however, you can't use the online resources for accessibility audits because your app doesn't have a URL to point the auditor to.
These new features bring those auditing tools to your Electron app. You can choose to add audits to your tests with Spectron or use them within DevTools with Devtron. Read on for a summary of the tools or checkout our [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) for more information. These new features bring those auditing tools to your Electron app. You can choose to add audits to your tests with Spectron or use them within DevTools with Devtron. Read on for a summary of the tools or checkout our [accessibility documentation](https://electronjs.org/docs/tutorial/accessibility) for more information.
## Spectron ## Spectron
@ -30,7 +30,7 @@ In Devtron, there is a new accessibility tab which will allow you to audit a pag
Both of these tools are using the [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) library built by Google for Chrome. You can learn more about the accessibility audit rules this library uses on that [repository's wiki](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules). Both of these tools are using the [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) library built by Google for Chrome. You can learn more about the accessibility audit rules this library uses on that [repository's wiki](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules).
If you know of other great accessibility tools for Electron, add them to the [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) with a pull request. If you know of other great accessibility tools for Electron, add them to the [accessibility documentation](https://electronjs.org/docs/tutorial/accessibility) with a pull request.
## Enabling Accessibility ## Enabling Accessibility

View file

@ -11,7 +11,7 @@ npm install electron --save-dev
``` ```
See the See the
[Electron versioning doc](https://electron.atom.io/docs/tutorial/electron-versioning/) [Electron versioning doc](https://electronjs.org/docs/tutorial/electron-versioning)
for info on how to manage Electron versions in your apps. for info on how to manage Electron versions in your apps.
## Global Installation ## Global Installation

View file

@ -244,7 +244,7 @@ $ npm start
``` ```
For more example apps, see the For more example apps, see the
[list of boilerplates](https://electron.atom.io/community/#boilerplates) [list of boilerplates](https://electronjs.org/community#boilerplates)
created by the awesome electron community. created by the awesome electron community.
[share-data]: ../faq.md#how-to-share-data-between-web-pages [share-data]: ../faq.md#how-to-share-data-between-web-pages

View file

@ -21,6 +21,7 @@ and can be deployed for free on [Now](https://zeit.co/now).
but caches app updates on disk and supports private repositories. but caches app updates on disk and supports private repositories.
- [electron-release-server](https://github.com/ArekSredzki/electron-release-server) - [electron-release-server](https://github.com/ArekSredzki/electron-release-server)
Provides a dashboard for handling releases Provides a dashboard for handling releases
- [Nucleus](https://github.com/atlassian/nucleus) - A complete update server for Electron apps maintained by Atlassian. Supports multiple applications and channels; uses a static file store to minify server cost.
If your app is packaged with [electron-builder][electron-builder-lib] you can use the If your app is packaged with [electron-builder][electron-builder-lib] you can use the
[electron-updater] module, which does not require a server and allows for updates [electron-updater] module, which does not require a server and allows for updates

View file

@ -163,6 +163,8 @@
'atom/browser/api/event.h', 'atom/browser/api/event.h',
'atom/browser/api/event_emitter.cc', 'atom/browser/api/event_emitter.cc',
'atom/browser/api/event_emitter.h', 'atom/browser/api/event_emitter.h',
'atom/browser/api/event_subscriber.cc',
'atom/browser/api/event_subscriber.h',
'atom/browser/api/trackable_object.cc', 'atom/browser/api/trackable_object.cc',
'atom/browser/api/trackable_object.h', 'atom/browser/api/trackable_object.h',
'atom/browser/api/frame_subscriber.cc', 'atom/browser/api/frame_subscriber.cc',
@ -248,8 +250,6 @@
'atom/browser/net/asar/url_request_asar_job.h', 'atom/browser/net/asar/url_request_asar_job.h',
'atom/browser/net/atom_cert_verifier.cc', 'atom/browser/net/atom_cert_verifier.cc',
'atom/browser/net/atom_cert_verifier.h', 'atom/browser/net/atom_cert_verifier.h',
'atom/browser/net/atom_ct_delegate.cc',
'atom/browser/net/atom_ct_delegate.h',
'atom/browser/net/atom_cookie_delegate.cc', 'atom/browser/net/atom_cookie_delegate.cc',
'atom/browser/net/atom_cookie_delegate.h', 'atom/browser/net/atom_cookie_delegate.h',
'atom/browser/net/atom_network_delegate.cc', 'atom/browser/net/atom_network_delegate.cc',
@ -272,6 +272,8 @@
'atom/browser/net/url_request_buffer_job.h', 'atom/browser/net/url_request_buffer_job.h',
'atom/browser/net/url_request_fetch_job.cc', 'atom/browser/net/url_request_fetch_job.cc',
'atom/browser/net/url_request_fetch_job.h', 'atom/browser/net/url_request_fetch_job.h',
'atom/browser/net/url_request_stream_job.cc',
'atom/browser/net/url_request_stream_job.h',
'atom/browser/node_debugger.cc', 'atom/browser/node_debugger.cc',
'atom/browser/node_debugger.h', 'atom/browser/node_debugger.h',
'atom/browser/relauncher_linux.cc', 'atom/browser/relauncher_linux.cc',

View file

@ -2,7 +2,6 @@
const url = require('url') const url = require('url')
const {EventEmitter} = require('events') const {EventEmitter} = require('events')
const util = require('util')
const {Readable} = require('stream') const {Readable} = require('stream')
const {app} = require('electron') const {app} = require('electron')
const {Session} = process.atomBinding('session') const {Session} = process.atomBinding('session')
@ -115,7 +114,7 @@ class ClientRequest extends EventEmitter {
if (typeof options === 'string') { if (typeof options === 'string') {
options = url.parse(options) options = url.parse(options)
} else { } else {
options = util._extend({}, options) options = Object.assign({}, options)
} }
const method = (options.method || 'GET').toUpperCase() const method = (options.method || 'GET').toUpperCase()

View file

@ -112,6 +112,16 @@ const webFrameMethods = [
] ]
const webFrameMethodsWithResult = [] const webFrameMethodsWithResult = []
const errorConstructors = {
Error,
EvalError,
RangeError,
ReferenceError,
SyntaxError,
TypeError,
URIError
}
const asyncWebFrameMethods = function (requestId, method, callback, ...args) { const asyncWebFrameMethods = function (requestId, method, callback, ...args) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args)
@ -120,7 +130,14 @@ const asyncWebFrameMethods = function (requestId, method, callback, ...args) {
if (typeof callback === 'function') callback(result) if (typeof callback === 'function') callback(result)
resolve(result) resolve(result)
} else { } else {
reject(error) if (error.__ELECTRON_SERIALIZED_ERROR__ && errorConstructors[error.name]) {
const rehydratedError = new errorConstructors[error.name](error.message)
rehydratedError.stack = error.stack
reject(rehydratedError)
} else {
reject(error)
}
} }
}) })
}) })

View file

@ -10,26 +10,25 @@ class CallbacksRegistry {
add (callback) { add (callback) {
// The callback is already added. // The callback is already added.
var filenameAndLine, id, location, match, ref, regexp, stackString let id = v8Util.getHiddenValue(callback, 'callbackId')
id = v8Util.getHiddenValue(callback, 'callbackId') if (id != null) return id
if (id != null) {
return id id = this.nextId += 1
}
id = ++this.nextId
// Capture the location of the function and put it in the ID string, // Capture the location of the function and put it in the ID string,
// so that release errors can be tracked down easily. // so that release errors can be tracked down easily.
regexp = /at (.*)/gi const regexp = /at (.*)/gi
stackString = (new Error()).stack const stackString = (new Error()).stack
let filenameAndLine
let match
while ((match = regexp.exec(stackString)) !== null) { while ((match = regexp.exec(stackString)) !== null) {
location = match[1] const location = match[1]
if (location.indexOf('(native)') !== -1) { if (location.includes('native')) continue
continue if (location.includes('electron.asar')) continue
}
if (location.indexOf('electron.asar') !== -1) { const ref = /([^/^)]*)\)?$/gi.exec(location)
continue
}
ref = /([^/^)]*)\)?$/gi.exec(location)
filenameAndLine = ref[1] filenameAndLine = ref[1]
break break
} }
@ -40,8 +39,7 @@ class CallbacksRegistry {
} }
get (id) { get (id) {
var ref return this.callbacks[id] || function () {}
return (ref = this.callbacks[id]) != null ? ref : function () {}
} }
apply (id, ...args) { apply (id, ...args) {

View file

@ -1,7 +1,6 @@
// Deprecate a method. // Deprecate a method.
const deprecate = function (oldName, newName, fn) { const deprecate = function (oldName, newName, fn) {
var warned let warned = false
warned = false
return function () { return function () {
if (!(warned || process.noDeprecation)) { if (!(warned || process.noDeprecation)) {
warned = true warned = true
@ -11,80 +10,31 @@ const deprecate = function (oldName, newName, fn) {
} }
} }
// The method is renamed. // The method is aliases and the old method is retained for backwards compat
deprecate.rename = function (object, oldName, newName) { deprecate.alias = function (object, deprecatedName, existingName) {
var newMethod, warned let warned = false
warned = false const newMethod = function () {
newMethod = function () {
if (!(warned || process.noDeprecation)) { if (!(warned || process.noDeprecation)) {
warned = true warned = true
deprecate.warn(oldName, newName) deprecate.warn(deprecatedName, existingName)
} }
return this[newName].apply(this, arguments) return this[existingName].apply(this, arguments)
} }
if (typeof object === 'function') { if (typeof object === 'function') {
object.prototype[oldName] = newMethod object.prototype[deprecatedName] = newMethod
} else { } else {
object[oldName] = newMethod object[deprecatedName] = newMethod
} }
} }
// Forward the method to member. deprecate.warn = (oldName, newName) => {
deprecate.member = function (object, method, member) { return deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`)
var warned
warned = false
object.prototype[method] = function () {
if (!(warned || process.noDeprecation)) {
warned = true
deprecate.warn(method, member + '.' + method)
}
return this[member][method].apply(this[member], arguments)
}
} }
// Deprecate a property. let deprecationHandler = null
deprecate.property = function (object, property, method) {
return Object.defineProperty(object, property, {
get: function () {
var warned
warned = false
if (!(warned || process.noDeprecation)) {
warned = true
deprecate.warn(property + ' property', method + ' method')
}
return this[method]()
}
})
}
// Deprecate an event.
deprecate.event = function (emitter, oldName, newName, fn) {
var warned = false
return emitter.on(newName, function (...args) {
// there is listeners for old API.
if (this.listenerCount(oldName) > 0) {
if (!(warned || process.noDeprecation)) {
warned = true
deprecate.warn("'" + oldName + "' event", "'" + newName + "' event")
}
if (fn != null) {
fn.apply(this, arguments)
} else {
this.emit.apply(this, [oldName].concat(args))
}
}
})
}
// Print deprecation warning.
deprecate.warn = function (oldName, newName) {
return deprecate.log(oldName + ' is deprecated. Use ' + newName + ' instead.')
}
var deprecationHandler = null
// Print deprecation message. // Print deprecation message.
deprecate.log = function (message) { deprecate.log = (message) => {
if (typeof deprecationHandler === 'function') { if (typeof deprecationHandler === 'function') {
deprecationHandler(message) deprecationHandler(message)
} else if (process.throwDeprecation) { } else if (process.throwDeprecation) {
@ -92,16 +42,61 @@ deprecate.log = function (message) {
} else if (process.traceDeprecation) { } else if (process.traceDeprecation) {
return console.trace(message) return console.trace(message)
} else { } else {
return console.warn('(electron) ' + message) return console.warn(`(electron) ${message}`)
} }
} }
deprecate.setHandler = function (handler) { deprecate.setHandler = (handler) => {
deprecationHandler = handler deprecationHandler = handler
} }
deprecate.getHandler = function () { deprecate.getHandler = () => deprecationHandler
return deprecationHandler
} // None of the below methods are used, and so will be commented
// out until such time that they are needed to be used and tested.
// // Forward the method to member.
// deprecate.member = (object, method, member) => {
// let warned = false
// object.prototype[method] = function () {
// if (!(warned || process.noDeprecation)) {
// warned = true
// deprecate.warn(method, `${member}.${method}`)
// }
// return this[member][method].apply(this[member], arguments)
// }
// }
//
// // Deprecate a property.
// deprecate.property = (object, property, method) => {
// return Object.defineProperty(object, property, {
// get: function () {
// let warned = false
// if (!(warned || process.noDeprecation)) {
// warned = true
// deprecate.warn(`${property} property`, `${method} method`)
// }
// return this[method]()
// }
// })
// }
//
// // Deprecate an event.
// deprecate.event = (emitter, oldName, newName, fn) => {
// let warned = false
// return emitter.on(newName, function (...args) {
// if (this.listenerCount(oldName) > 0) {
// if (!(warned || process.noDeprecation)) {
// warned = true
// deprecate.warn(`'${oldName}' event`, `'${newName}' event`)
// }
// if (fn != null) {
// fn.apply(this, arguments)
// } else {
// this.emit.apply(this, [oldName].concat(args))
// }
// }
// })
// }
module.exports = deprecate module.exports = deprecate

View file

@ -3,7 +3,6 @@
const {Buffer} = require('buffer') const {Buffer} = require('buffer')
const childProcess = require('child_process') const childProcess = require('child_process')
const path = require('path') const path = require('path')
const util = require('util')
const hasProp = {}.hasOwnProperty const hasProp = {}.hasOwnProperty
@ -460,11 +459,15 @@
options = { options = {
encoding: null encoding: null
} }
} else if (util.isString(options)) { } else if (typeof options === 'string') {
options = { options = {
encoding: options encoding: options
} }
} else if (!util.isObject(options)) { } else if (options === null || options === undefined) {
options = {
encoding: null
}
} else if (typeof options !== 'object') {
throw new TypeError('Bad arguments') throw new TypeError('Bad arguments')
} }
const {encoding} = options const {encoding} = options
@ -527,11 +530,11 @@
options = { options = {
encoding: null encoding: null
} }
} else if (util.isString(options)) { } else if (typeof options === 'string') {
options = { options = {
encoding: options encoding: options
} }
} else if (!util.isObject(options)) { } else if (typeof options !== 'object') {
throw new TypeError('Bad arguments') throw new TypeError('Bad arguments')
} }
const {encoding} = options const {encoding} = options

View file

@ -45,6 +45,17 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev
event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, null, resolvedResult) event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, null, resolvedResult)
}) })
.catch((resolvedError) => { .catch((resolvedError) => {
if (resolvedError instanceof Error) {
// Errors get lost, because: JSON.stringify(new Error('Message')) === {}
// Take the serializable properties and construct a generic object
resolvedError = {
message: resolvedError.message,
stack: resolvedError.stack,
name: resolvedError.name,
__ELECTRON_SERIALIZED_ERROR__: true
}
}
event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, resolvedError) event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, resolvedError)
}) })
} }
@ -135,7 +146,7 @@ if (nodeIntegration === 'true') {
let warning = 'This renderer process has Node.js integration enabled ' let warning = 'This renderer process has Node.js integration enabled '
warning += 'and attempted to load remote content. This exposes users of this app to severe ' warning += 'and attempted to load remote content. This exposes users of this app to severe '
warning += 'security risks.\n' warning += 'security risks.\n'
warning += 'For more information and help, consult https://electron.atom.io/docs/tutorial/security/' warning += 'For more information and help, consult https://electronjs.org/docs/tutorial/security'
console.warn('%cElectron Security Warning', 'font-weight: bold;', warning) console.warn('%cElectron Security Warning', 'font-weight: bold;', warning)
} }

View file

@ -32,5 +32,15 @@ Object.defineProperties(exports, {
get: function () { get: function () {
return require('../../../common/api/is-promise') return require('../../../common/api/is-promise')
} }
},
desktopCapturer: {
get: function () {
return require('../../../renderer/api/desktop-capturer')
}
},
nativeImage: {
get: function () {
return require('../../../common/api/native-image')
}
} }
}) })

View file

@ -11,8 +11,8 @@
"dotenv-safe": "^4.0.4", "dotenv-safe": "^4.0.4",
"dugite": "^1.45.0", "dugite": "^1.45.0",
"electabul": "~0.0.4", "electabul": "~0.0.4",
"electron-docs-linter": "^2.3.3", "electron-docs-linter": "^2.3.4",
"electron-typescript-definitions": "^1.2.10", "electron-typescript-definitions": "^1.2.11",
"github": "^9.2.0", "github": "^9.2.0",
"husky": "^0.14.3", "husky": "^0.14.3",
"minimist": "^1.2.0", "minimist": "^1.2.0",

View file

@ -85,7 +85,7 @@ def main():
with scoped_cwd(SOURCE_ROOT): with scoped_cwd(SOURCE_ROOT):
update_electron_gyp(version, suffix) update_electron_gyp(version, suffix)
update_win_rc(version, versions) update_win_rc(version, versions)
update_version_h(versions) update_version_h(versions, suffix)
update_info_plist(version) update_info_plist(version)
update_package_json(version, suffix) update_package_json(version, suffix)
tag_version(version, suffix) tag_version(version, suffix)
@ -138,7 +138,7 @@ def update_win_rc(version, versions):
f.write(''.join(lines)) f.write(''.join(lines))
def update_version_h(versions): def update_version_h(versions, suffix):
version_h = os.path.join('atom', 'common', 'atom_version.h') version_h = os.path.join('atom', 'common', 'atom_version.h')
with open(version_h, 'r') as f: with open(version_h, 'r') as f:
lines = f.readlines() lines = f.readlines()
@ -150,6 +150,11 @@ def update_version_h(versions):
lines[i + 1] = '#define ATOM_MINOR_VERSION {0}\n'.format(versions[1]) lines[i + 1] = '#define ATOM_MINOR_VERSION {0}\n'.format(versions[1])
lines[i + 2] = '#define ATOM_PATCH_VERSION {0}\n'.format(versions[2]) lines[i + 2] = '#define ATOM_PATCH_VERSION {0}\n'.format(versions[2])
if (suffix):
lines[i + 3] = '#define ATOM_PRE_RELEASE_VERSION {0}\n'.format(suffix)
else:
lines[i + 3] = '// #define ATOM_PRE_RELEASE_VERSION\n'
with open(version_h, 'w') as f: with open(version_h, 'w') as f:
f.write(''.join(lines)) f.write(''.join(lines))
return return

View file

@ -1,56 +1,210 @@
const args = require('minimist')(process.argv.slice(2))
const assert = require('assert') const assert = require('assert')
const request = require('request') const request = require('request')
const buildAppVeyorURL = 'https://windows-ci.electronjs.org/api/builds'
const jenkinsServer = 'https://mac-ci.electronjs.org'
const ciJobs = [ const circleCIJobs = [
'electron-linux-arm64', 'electron-linux-arm64',
'electron-linux-ia32', 'electron-linux-ia32',
'electron-linux-x64', 'electron-linux-x64',
'electron-linux-arm' 'electron-linux-arm'
] ]
const CIcall = (buildUrl, targetBranch, job) => { const jenkinsJobs = [
console.log(`Triggering CircleCI to run build job: ${job} on branch: ${targetBranch} with release flag.`) 'electron-mas-x64-release',
'electron-osx-x64-release'
]
request({ async function makeRequest (requestOptions, parseResponse) {
return new Promise((resolve, reject) => {
request(requestOptions, (err, res, body) => {
if (!err && res.statusCode >= 200 && res.statusCode < 300) {
if (parseResponse) {
const build = JSON.parse(body)
resolve(build)
} else {
resolve(body)
}
} else {
if (parseResponse) {
console.log('Error: ', `(status ${res.statusCode})`, err || JSON.parse(res.body), requestOptions)
} else {
console.log('Error: ', `(status ${res.statusCode})`, err || res.body, requestOptions)
}
reject()
}
})
})
}
async function circleCIcall (buildUrl, targetBranch, job, ghRelease) {
assert(process.env.CIRCLE_TOKEN, 'CIRCLE_TOKEN not found in environment')
console.log(`Triggering CircleCI to run build job: ${job} on branch: ${targetBranch} with release flag.`)
let buildRequest = {
'build_parameters': {
'CIRCLE_JOB': job
}
}
if (ghRelease) {
buildRequest.build_parameters.ELECTRON_RELEASE = 1
} else {
buildRequest.build_parameters.RUN_RELEASE_BUILD = 'true'
}
let circleResponse = await makeRequest({
method: 'POST', method: 'POST',
url: buildUrl, url: buildUrl,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify(buildRequest)
'build_parameters': { }, true).catch(err => {
'RUN_RELEASE_BUILD': 'true', console.log('Error calling CircleCI:', err)
'CIRCLE_JOB': job
}
})
}, (err, res, body) => {
if (!err && res.statusCode >= 200 && res.statusCode < 300) {
const build = JSON.parse(body)
console.log(`Check ${build.build_url} for status. (${job})`)
} else {
console.log('Error: ', `(status ${res.statusCode})`, err || JSON.parse(res.body), job)
}
}) })
console.log(`Check ${circleResponse.build_url} for status. (${job})`)
} }
if (args._.length < 1) { async function buildAppVeyor (targetBranch, ghRelease) {
console.log(`Trigger Circle CI to build release builds of electron. console.log(`Triggering AppVeyor to run build on branch: ${targetBranch} with release flag.`)
Usage: ci-release-build.js [--job=CI_JOB_NAME] TARGET_BRANCH assert(process.env.APPVEYOR_TOKEN, 'APPVEYOR_TOKEN not found in environment')
`) let environmentVariables = {}
process.exit(0)
if (ghRelease) {
environmentVariables.ELECTRON_RELEASE = 1
} else {
environmentVariables.RUN_RELEASE_BUILD = 'true'
}
const requestOpts = {
url: buildAppVeyorURL,
auth: {
bearer: process.env.APPVEYOR_TOKEN
},
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
accountName: 'AppVeyor',
projectSlug: 'electron',
branch: targetBranch,
environmentVariables
}),
method: 'POST'
}
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}`
console.log(`AppVeyor release build request successful. Check build status at ${buildUrl}`)
} }
assert(process.env.CIRCLE_TOKEN, 'CIRCLE_TOKEN not found in environment') function buildCircleCI (targetBranch, ghRelease, job) {
const circleBuildUrl = `https://circleci.com/api/v1.1/project/github/electron/electron/tree/${targetBranch}?circle-token=${process.env.CIRCLE_TOKEN}`
const targetBranch = args._[0] if (job) {
const job = args['job'] assert(circleCIJobs.includes(job), `Unknown CI job name: ${job}.`)
const circleBuildUrl = `https://circleci.com/api/v1.1/project/github/electron/electron/tree/${targetBranch}?circle-token=${process.env.CIRCLE_TOKEN}` circleCIcall(circleBuildUrl, targetBranch, job, ghRelease)
} else {
if (job) { circleCIJobs.forEach((job) => circleCIcall(circleBuildUrl, targetBranch, job, ghRelease))
assert(ciJobs.includes(job), `Unknown CI job name: ${job}.`) }
CIcall(circleBuildUrl, targetBranch, job) }
} else {
ciJobs.forEach((job) => CIcall(circleBuildUrl, targetBranch, job)) async function buildJenkins (targetBranch, ghRelease, job) {
assert(process.env.JENKINS_AUTH_TOKEN, 'JENKINS_AUTH_TOKEN not found in environment')
assert(process.env.JENKINS_BUILD_TOKEN, 'JENKINS_BUILD_TOKEN not found in environment')
let jenkinsCrumb = await getJenkinsCrumb()
if (job) {
assert(jenkinsJobs.includes(job), `Unknown CI job name: ${job}.`)
callJenkinsBuild(job, jenkinsCrumb, targetBranch, ghRelease)
} else {
jenkinsJobs.forEach((job) => {
callJenkinsBuild(job, jenkinsCrumb, targetBranch, ghRelease)
})
}
}
async function callJenkins (path, requestParameters, requestHeaders) {
let requestOptions = {
url: `${jenkinsServer}/${path}`,
auth: {
user: 'build',
pass: process.env.JENKINS_AUTH_TOKEN
},
qs: requestParameters
}
if (requestHeaders) {
requestOptions.headers = requestHeaders
}
let jenkinsResponse = await makeRequest(requestOptions).catch(err => {
console.log(`Error calling Jenkins:`, err)
})
return jenkinsResponse
}
async function callJenkinsBuild (job, jenkinsCrumb, targetBranch, ghRelease) {
console.log(`Triggering Jenkins to run build job: ${job} on branch: ${targetBranch} with release flag.`)
let jenkinsParams = {
token: process.env.JENKINS_BUILD_TOKEN,
BRANCH: targetBranch
}
if (!ghRelease) {
jenkinsParams.RUN_RELEASE_BUILD = 1
}
await callJenkins(`job/${job}/buildWithParameters`, jenkinsParams, jenkinsCrumb)
.catch(err => {
console.log(`Error calling Jenkins build`, err)
})
let buildUrl = `${jenkinsServer}/job/${job}/lastBuild/`
console.log(`Jenkins build request successful. Check build status at ${buildUrl}.`)
}
async function getJenkinsCrumb () {
let crumbResponse = await callJenkins('crumbIssuer/api/xml', {
xpath: 'concat(//crumbRequestField,":",//crumb)'
}).catch(err => {
console.log(`Error getting jenkins crumb:`, err)
})
let crumbDetails = crumbResponse.split(':')
let crumbHeader = {}
crumbHeader[crumbDetails[0]] = crumbDetails[1]
return crumbHeader
}
function runRelease (targetBranch, options) {
if (options.ci) {
switch (options.ci) {
case 'CircleCI': {
buildCircleCI(targetBranch, options.ghRelease, options.job)
break
}
case 'AppVeyor': {
buildAppVeyor(targetBranch, options.ghRelease)
break
}
case 'Jenkins': {
buildJenkins(targetBranch, options.ghRelease, options.job)
break
}
}
} else {
buildCircleCI(targetBranch, options.ghRelease, options.job)
buildAppVeyor(targetBranch, options.ghRelease)
buildJenkins(targetBranch, options.ghRelease, options.job)
}
}
module.exports = runRelease
if (require.main === module) {
const args = require('minimist')(process.argv.slice(2))
const targetBranch = args._[0]
if (args._.length < 1) {
console.log(`Trigger CI to build release builds of electron.
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--ci=CircleCI|AppVeyor|Jenkins] [--ghRelease] TARGET_BRANCH
`)
process.exit(0)
}
runRelease(targetBranch, args)
} }

View file

@ -11,7 +11,8 @@ import stat
if sys.platform == "win32": if sys.platform == "win32":
import _winreg import _winreg
from lib.config import BASE_URL, PLATFORM, get_target_arch, get_zip_name from lib.config import BASE_URL, PLATFORM, enable_verbose_mode, \
get_target_arch, get_zip_name
from lib.util import scoped_cwd, rm_rf, get_electron_version, make_zip, \ from lib.util import scoped_cwd, rm_rf, get_electron_version, make_zip, \
execute, electron_gyp execute, electron_gyp
@ -79,6 +80,11 @@ TARGET_DIRECTORIES = {
def main(): def main():
args = parse_args()
if args.verbose:
enable_verbose_mode()
rm_rf(DIST_DIR) rm_rf(DIST_DIR)
os.makedirs(DIST_DIR) os.makedirs(DIST_DIR)
@ -92,8 +98,6 @@ def main():
copy_vcruntime_binaries() copy_vcruntime_binaries()
copy_ucrt_binaries() copy_ucrt_binaries()
args = parse_args()
if PLATFORM != 'win32' and not args.no_api_docs: if PLATFORM != 'win32' and not args.no_api_docs:
create_api_json_schema() create_api_json_schema()
create_typescript_definitions() create_typescript_definitions()
@ -307,6 +311,9 @@ def parse_args():
parser.add_argument('--no_api_docs', parser.add_argument('--no_api_docs',
action='store_true', action='store_true',
help='Skip generating the Electron API Documentation!') help='Skip generating the Electron API Documentation!')
parser.add_argument('-v', '--verbose',
action='store_true',
help='Prints the output of the subprocesses')
return parser.parse_args() return parser.parse_args()

View file

@ -3,6 +3,7 @@
require('colors') require('colors')
const args = require('minimist')(process.argv.slice(2)) const args = require('minimist')(process.argv.slice(2))
const assert = require('assert') const assert = require('assert')
const ciReleaseBuild = require('./ci-release-build')
const { execSync } = require('child_process') const { execSync } = require('child_process')
const fail = '\u2717'.red const fail = '\u2717'.red
const { GitProcess, GitError } = require('dugite') const { GitProcess, GitError } = require('dugite')
@ -158,6 +159,12 @@ async function pushRelease () {
} }
} }
async function runReleaseBuilds () {
await ciReleaseBuild('release', {
ghRelease: true
})
}
async function prepareRelease (isBeta, notesOnly) { async function prepareRelease (isBeta, notesOnly) {
let currentBranch = await getCurrentBranch(gitDir) let currentBranch = await getCurrentBranch(gitDir)
if (notesOnly) { if (notesOnly) {
@ -167,6 +174,7 @@ async function prepareRelease (isBeta, notesOnly) {
await createReleaseBranch() await createReleaseBranch()
await createRelease(currentBranch, isBeta) await createRelease(currentBranch, isBeta)
await pushRelease() await pushRelease()
await runReleaseBuilds()
} }
} }

View file

@ -2514,6 +2514,15 @@ describe('BrowserWindow module', () => {
const code = `(() => "${expected}")()` const code = `(() => "${expected}")()`
const asyncCode = `(() => new Promise(r => setTimeout(() => r("${expected}"), 500)))()` const asyncCode = `(() => new Promise(r => setTimeout(() => r("${expected}"), 500)))()`
const badAsyncCode = `(() => new Promise((r, e) => setTimeout(() => e("${expectedErrorMsg}"), 500)))()` const badAsyncCode = `(() => new Promise((r, e) => setTimeout(() => e("${expectedErrorMsg}"), 500)))()`
const errorTypes = new Set([
Error,
ReferenceError,
EvalError,
RangeError,
SyntaxError,
TypeError,
URIError
])
it('doesnt throw when no calback is provided', () => { it('doesnt throw when no calback is provided', () => {
const result = ipcRenderer.sendSync('executeJavaScript', code, false) const result = ipcRenderer.sendSync('executeJavaScript', code, false)
@ -2561,6 +2570,17 @@ describe('BrowserWindow module', () => {
done() done()
}) })
}) })
it('rejects the returned promise with an error if an Error.prototype is thrown', async () => {
for (const error in errorTypes) {
await new Promise((resolve) => {
ipcRenderer.send('executeJavaScript', `Promise.reject(new ${error.name}("Wamp-wamp")`, true)
ipcRenderer.once('executeJavaScript-promise-error-name', (event, name) => {
assert.equal(name, error.name)
resolve()
})
})
}
})
it('works after page load and during subframe load', (done) => { it('works after page load and during subframe load', (done) => {
w.webContents.once('did-finish-load', () => { w.webContents.once('did-finish-load', () => {
// initiate a sub-frame load, then try and execute script during it // initiate a sub-frame load, then try and execute script during it

View file

@ -0,0 +1,48 @@
const {assert} = require('chai')
const {CallbacksRegistry} = require('electron')
describe('CallbacksRegistry module', () => {
let registry = null
beforeEach(() => {
registry = new CallbacksRegistry()
})
it('adds a callback to the registry', () => {
const cb = () => [1, 2, 3, 4, 5]
const key = registry.add(cb)
assert.exists(key)
})
it('returns a specified callback if it is in the registry', () => {
const cb = () => [1, 2, 3, 4, 5]
const key = registry.add(cb)
const callback = registry.get(key)
assert.equal(callback.toString(), cb.toString())
})
it('returns an empty function if the cb doesnt exist', () => {
const callback = registry.get(1)
assert.isFunction(callback)
})
it('removes a callback to the registry', () => {
const cb = () => [1, 2, 3, 4, 5]
const key = registry.add(cb)
assert.exists(key)
const beforeCB = registry.get(key)
assert.equal(beforeCB.toString(), cb.toString())
registry.remove(key)
const afterCB = registry.get(key)
assert.isFunction(afterCB)
assert.notEqual(afterCB.toString(), cb.toString())
})
})

View file

@ -45,10 +45,10 @@ describe('clipboard module', () => {
it('returns title and url', () => { it('returns title and url', () => {
if (process.platform === 'linux') return if (process.platform === 'linux') return
clipboard.writeBookmark('a title', 'https://electron.atom.io') clipboard.writeBookmark('a title', 'https://electronjs.org')
assert.deepEqual(clipboard.readBookmark(), { assert.deepEqual(clipboard.readBookmark(), {
title: 'a title', title: 'a title',
url: 'https://electron.atom.io' url: 'https://electronjs.org'
}) })
clipboard.writeText('no bookmark') clipboard.writeText('no bookmark')

View file

@ -1,5 +1,5 @@
const assert = require('assert') const assert = require('assert')
const {deprecations, deprecate} = require('electron') const {deprecations, deprecate, nativeImage} = require('electron')
describe('deprecations', () => { describe('deprecations', () => {
beforeEach(() => { beforeEach(() => {
@ -18,6 +18,37 @@ describe('deprecations', () => {
assert.deepEqual(messages, ['this is deprecated']) assert.deepEqual(messages, ['this is deprecated'])
}) })
it('returns a deprecation handler after one is set', () => {
const messages = []
deprecations.setHandler((message) => {
messages.push(message)
})
deprecate.log('this is deprecated')
assert(typeof deprecations.getHandler() === 'function')
})
it('returns a deprecation warning', () => {
const messages = []
deprecations.setHandler((message) => {
messages.push(message)
})
deprecate.warn('old', 'new')
assert.deepEqual(messages, [`'old' is deprecated. Use 'new' instead.`])
})
it('renames a method', () => {
assert.equal(typeof nativeImage.createFromDataUrl, 'undefined')
assert.equal(typeof nativeImage.createFromDataURL, 'function')
deprecate.alias(nativeImage, 'createFromDataUrl', 'createFromDataURL')
assert.equal(typeof nativeImage.createFromDataUrl, 'function')
})
it('throws an exception if no deprecation handler is specified', () => { it('throws an exception if no deprecation handler is specified', () => {
assert.throws(() => { assert.throws(() => {
deprecate.log('this is deprecated') deprecate.log('this is deprecated')

View file

@ -1,6 +1,6 @@
'use strict' 'use strict'
const assert = require('assert') const {expect} = require('chai')
const {nativeImage} = require('electron') const {nativeImage} = require('electron')
const path = require('path') const path = require('path')
@ -104,71 +104,71 @@ describe('nativeImage module', () => {
describe('createEmpty()', () => { describe('createEmpty()', () => {
it('returns an empty image', () => { it('returns an empty image', () => {
const empty = nativeImage.createEmpty() const empty = nativeImage.createEmpty()
assert.equal(empty.isEmpty(), true) expect(empty.isEmpty())
assert.equal(empty.getAspectRatio(), 1) expect(empty.getAspectRatio()).to.equal(1)
assert.equal(empty.toDataURL(), 'data:image/png;base64,') expect(empty.toDataURL()).to.equal('data:image/png;base64,')
assert.equal(empty.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,') expect(empty.toDataURL({scaleFactor: 2.0})).to.equal('data:image/png;base64,')
assert.deepEqual(empty.getSize(), {width: 0, height: 0}) expect(empty.getSize()).to.deep.equal({width: 0, height: 0})
assert.deepEqual(empty.getBitmap(), []) expect(empty.getBitmap()).to.be.empty
assert.deepEqual(empty.getBitmap({scaleFactor: 2.0}), []) expect(empty.getBitmap({scaleFactor: 2.0})).to.be.empty
assert.deepEqual(empty.toBitmap(), []) expect(empty.toBitmap()).to.be.empty
assert.deepEqual(empty.toBitmap({scaleFactor: 2.0}), []) expect(empty.toBitmap({scaleFactor: 2.0})).to.be.empty
assert.deepEqual(empty.toJPEG(100), []) expect(empty.toJPEG(100)).to.be.empty
assert.deepEqual(empty.toPNG(), []) expect(empty.toPNG()).to.be.empty
assert.deepEqual(empty.toPNG({scaleFactor: 2.0}), []) expect(empty.toPNG({scaleFactor: 2.0})).to.be.empty
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
assert.deepEqual(empty.getNativeHandle(), []) expect(empty.getNativeHandle()).to.be.empty
} }
}) })
}) })
describe('createFromBuffer(buffer, scaleFactor)', () => { describe('createFromBuffer(buffer, scaleFactor)', () => {
it('returns an empty image when the buffer is empty', () => { it('returns an empty image when the buffer is empty', () => {
assert(nativeImage.createFromBuffer(Buffer.from([])).isEmpty()) expect(nativeImage.createFromBuffer(Buffer.from([])).isEmpty())
}) })
it('returns an image created from the given buffer', () => { it('returns an image created from the given buffer', () => {
const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
const imageB = nativeImage.createFromBuffer(imageA.toPNG()) const imageB = nativeImage.createFromBuffer(imageA.toPNG())
assert.deepEqual(imageB.getSize(), {width: 538, height: 190}) expect(imageB.getSize()).to.deep.equal({width: 538, height: 190})
assert(imageA.toBitmap().equals(imageB.toBitmap())) expect(imageA.toBitmap().equals(imageB.toBitmap())).to.be.true
const imageC = nativeImage.createFromBuffer(imageA.toJPEG(100)) const imageC = nativeImage.createFromBuffer(imageA.toJPEG(100))
assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) expect(imageC.getSize()).to.deep.equal({width: 538, height: 190})
const imageD = nativeImage.createFromBuffer(imageA.toBitmap(), const imageD = nativeImage.createFromBuffer(imageA.toBitmap(),
{width: 538, height: 190}) {width: 538, height: 190})
assert.deepEqual(imageD.getSize(), {width: 538, height: 190}) expect(imageD.getSize()).to.deep.equal({width: 538, height: 190})
const imageE = nativeImage.createFromBuffer(imageA.toBitmap(), const imageE = nativeImage.createFromBuffer(imageA.toBitmap(),
{width: 100, height: 200}) {width: 100, height: 200})
assert.deepEqual(imageE.getSize(), {width: 100, height: 200}) expect(imageE.getSize()).to.deep.equal({width: 100, height: 200})
const imageF = nativeImage.createFromBuffer(imageA.toBitmap()) const imageF = nativeImage.createFromBuffer(imageA.toBitmap())
assert(imageF.isEmpty()) expect(imageF.isEmpty())
const imageG = nativeImage.createFromBuffer(imageA.toPNG(), const imageG = nativeImage.createFromBuffer(imageA.toPNG(),
{width: 100, height: 200}) {width: 100, height: 200})
assert.deepEqual(imageG.getSize(), {width: 538, height: 190}) expect(imageG.getSize()).to.deep.equal({width: 538, height: 190})
const imageH = nativeImage.createFromBuffer(imageA.toJPEG(100), const imageH = nativeImage.createFromBuffer(imageA.toJPEG(100),
{width: 100, height: 200}) {width: 100, height: 200})
assert.deepEqual(imageH.getSize(), {width: 538, height: 190}) expect(imageH.getSize()).to.deep.equal({width: 538, height: 190})
const imageI = nativeImage.createFromBuffer(imageA.toBitmap(), const imageI = nativeImage.createFromBuffer(imageA.toBitmap(),
{width: 538, height: 190, scaleFactor: 2.0}) {width: 538, height: 190, scaleFactor: 2.0})
assert.deepEqual(imageI.getSize(), {width: 269, height: 95}) expect(imageI.getSize()).to.deep.equal({width: 269, height: 95})
const imageJ = nativeImage.createFromBuffer(imageA.toPNG(), 2.0) const imageJ = nativeImage.createFromBuffer(imageA.toPNG(), 2.0)
assert.deepEqual(imageJ.getSize(), {width: 269, height: 95}) expect(imageJ.getSize()).to.deep.equal({width: 269, height: 95})
}) })
}) })
describe('createFromDataURL(dataURL)', () => { describe('createFromDataURL(dataURL)', () => {
it('returns an empty image from the empty string', () => { it('returns an empty image from the empty string', () => {
assert(nativeImage.createFromDataURL('').isEmpty()) expect(nativeImage.createFromDataURL('').isEmpty())
}) })
it('returns an image created from the given string', () => { it('returns an image created from the given string', () => {
@ -177,9 +177,10 @@ describe('nativeImage module', () => {
const imageFromPath = nativeImage.createFromPath(imageData.path) const imageFromPath = nativeImage.createFromPath(imageData.path)
const imageFromDataUrl = nativeImage.createFromDataURL(imageData.dataUrl) const imageFromDataUrl = nativeImage.createFromDataURL(imageData.dataUrl)
assert(!imageFromDataUrl.isEmpty()) expect(imageFromDataUrl.isEmpty())
assert.deepEqual(imageFromDataUrl.getSize(), imageFromPath.getSize()) expect(imageFromDataUrl.getSize()).to.deep.equal(imageFromPath.getSize())
assert(imageFromPath.toBitmap().equals(imageFromDataUrl.toBitmap())) expect(imageFromDataUrl.toBitmap()).to.satisfy(
bitmap => imageFromPath.toBitmap().equals(bitmap))
} }
}) })
}) })
@ -190,8 +191,8 @@ describe('nativeImage module', () => {
for (const imageData of imagesData) { for (const imageData of imagesData) {
const imageFromPath = nativeImage.createFromPath(imageData.path) const imageFromPath = nativeImage.createFromPath(imageData.path)
assert.equal(imageFromPath.toDataURL(), imageData.dataUrl) expect(imageFromPath.toDataURL()).to.equal(imageData.dataUrl)
assert.equal(imageFromPath.toDataURL({scaleFactor: 2.0}), imageData.dataUrl) expect(imageFromPath.toDataURL({scaleFactor: 2.0})).to.equal(imageData.dataUrl)
} }
}) })
@ -204,14 +205,14 @@ describe('nativeImage module', () => {
height: image.getSize().height, height: image.getSize().height,
scaleFactor: 2.0 scaleFactor: 2.0
}) })
assert.deepEqual(imageOne.getSize(), expect(imageOne.getSize()).to.deep.equal(
{width: imageData.width / 2, height: imageData.height / 2}) {width: imageData.width / 2, height: imageData.height / 2})
const imageTwo = nativeImage.createFromDataURL(imageOne.toDataURL()) const imageTwo = nativeImage.createFromDataURL(imageOne.toDataURL())
assert.deepEqual(imageTwo.getSize(), expect(imageTwo.getSize()).to.deep.equal(
{width: imageData.width, height: imageData.height}) {width: imageData.width, height: imageData.height})
assert(imageOne.toBitmap().equals(imageTwo.toBitmap())) expect(imageOne.toBitmap().equals(imageTwo.toBitmap())).to.be.true
}) })
it('supports a scale factor', () => { it('supports a scale factor', () => {
@ -221,11 +222,11 @@ describe('nativeImage module', () => {
const imageFromDataUrlOne = nativeImage.createFromDataURL( const imageFromDataUrlOne = nativeImage.createFromDataURL(
image.toDataURL({scaleFactor: 1.0})) image.toDataURL({scaleFactor: 1.0}))
assert.deepEqual(imageFromDataUrlOne.getSize(), expectedSize) expect(imageFromDataUrlOne.getSize()).to.deep.equal(expectedSize)
const imageFromDataUrlTwo = nativeImage.createFromDataURL( const imageFromDataUrlTwo = nativeImage.createFromDataURL(
image.toDataURL({scaleFactor: 2.0})) image.toDataURL({scaleFactor: 2.0}))
assert.deepEqual(imageFromDataUrlTwo.getSize(), expectedSize) expect(imageFromDataUrlTwo.getSize()).to.deep.equal(expectedSize)
}) })
}) })
@ -239,14 +240,14 @@ describe('nativeImage module', () => {
height: imageA.getSize().height, height: imageA.getSize().height,
scaleFactor: 2.0 scaleFactor: 2.0
}) })
assert.deepEqual(imageB.getSize(), expect(imageB.getSize()).to.deep.equal(
{width: imageData.width / 2, height: imageData.height / 2}) {width: imageData.width / 2, height: imageData.height / 2})
const imageC = nativeImage.createFromBuffer(imageB.toPNG()) const imageC = nativeImage.createFromBuffer(imageB.toPNG())
assert.deepEqual(imageC.getSize(), expect(imageC.getSize()).to.deep.equal(
{width: imageData.width, height: imageData.height}) {width: imageData.width, height: imageData.height})
assert(imageB.toBitmap().equals(imageC.toBitmap())) expect(imageB.toBitmap().equals(imageC.toBitmap())).to.be.true
}) })
it('supports a scale factor', () => { it('supports a scale factor', () => {
@ -255,46 +256,44 @@ describe('nativeImage module', () => {
const imageFromBufferOne = nativeImage.createFromBuffer( const imageFromBufferOne = nativeImage.createFromBuffer(
image.toPNG({scaleFactor: 1.0})) image.toPNG({scaleFactor: 1.0}))
assert.deepEqual( expect(imageFromBufferOne.getSize()).to.deep.equal(
imageFromBufferOne.getSize(),
{width: imageData.width, height: imageData.height}) {width: imageData.width, height: imageData.height})
const imageFromBufferTwo = nativeImage.createFromBuffer( const imageFromBufferTwo = nativeImage.createFromBuffer(
image.toPNG({scaleFactor: 2.0}), {scaleFactor: 2.0}) image.toPNG({scaleFactor: 2.0}), {scaleFactor: 2.0})
assert.deepEqual( expect(imageFromBufferTwo.getSize()).to.deep.equal(
imageFromBufferTwo.getSize(),
{width: imageData.width / 2, height: imageData.height / 2}) {width: imageData.width / 2, height: imageData.height / 2})
}) })
}) })
describe('createFromPath(path)', () => { describe('createFromPath(path)', () => {
it('returns an empty image for invalid paths', () => { it('returns an empty image for invalid paths', () => {
assert(nativeImage.createFromPath('').isEmpty()) expect(nativeImage.createFromPath('').isEmpty())
assert(nativeImage.createFromPath('does-not-exist.png').isEmpty()) expect(nativeImage.createFromPath('does-not-exist.png').isEmpty())
assert(nativeImage.createFromPath('does-not-exist.ico').isEmpty()) expect(nativeImage.createFromPath('does-not-exist.ico').isEmpty())
assert(nativeImage.createFromPath(__dirname).isEmpty()) expect(nativeImage.createFromPath(__dirname).isEmpty())
assert(nativeImage.createFromPath(__filename).isEmpty()) expect(nativeImage.createFromPath(__filename).isEmpty())
}) })
it('loads images from paths relative to the current working directory', () => { it('loads images from paths relative to the current working directory', () => {
const imagePath = `.${path.sep}${path.join('spec', 'fixtures', 'assets', 'logo.png')}` const imagePath = `.${path.sep}${path.join('spec', 'fixtures', 'assets', 'logo.png')}`
const image = nativeImage.createFromPath(imagePath) const image = nativeImage.createFromPath(imagePath)
assert(!image.isEmpty()) expect(image.isEmpty()).to.be.false
assert.deepEqual(image.getSize(), {width: 538, height: 190}) expect(image.getSize()).to.deep.equal({width: 538, height: 190})
}) })
it('loads images from paths with `.` segments', () => { it('loads images from paths with `.` segments', () => {
const imagePath = `${path.join(__dirname, 'fixtures')}${path.sep}.${path.sep}${path.join('assets', 'logo.png')}` const imagePath = `${path.join(__dirname, 'fixtures')}${path.sep}.${path.sep}${path.join('assets', 'logo.png')}`
const image = nativeImage.createFromPath(imagePath) const image = nativeImage.createFromPath(imagePath)
assert(!image.isEmpty()) expect(image.isEmpty()).to.be.false
assert.deepEqual(image.getSize(), {width: 538, height: 190}) expect(image.getSize()).to.deep.equal({width: 538, height: 190})
}) })
it('loads images from paths with `..` segments', () => { it('loads images from paths with `..` segments', () => {
const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}` const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}`
const image = nativeImage.createFromPath(imagePath) const image = nativeImage.createFromPath(imagePath)
assert(!image.isEmpty()) expect(image.isEmpty()).to.be.false
assert.deepEqual(image.getSize(), {width: 538, height: 190}) expect(image.getSize()).to.deep.equal({width: 538, height: 190})
}) })
it('Gets an NSImage pointer on macOS', () => { it('Gets an NSImage pointer on macOS', () => {
@ -304,10 +303,11 @@ describe('nativeImage module', () => {
const image = nativeImage.createFromPath(imagePath) const image = nativeImage.createFromPath(imagePath)
const nsimage = image.getNativeHandle() const nsimage = image.getNativeHandle()
assert.equal(nsimage.length, 8) expect(nsimage).to.have.lengthOf(8)
// If all bytes are null, that's Bad // If all bytes are null, that's Bad
assert.equal(nsimage.reduce((acc, x) => acc || (x !== 0), false), true) const allBytesAreNotNull = nsimage.reduce((acc, x) => acc || (x !== 0), false)
expect(allBytesAreNotNull)
}) })
it('loads images from .ico files on Windows', () => { it('loads images from .ico files on Windows', () => {
@ -315,56 +315,61 @@ describe('nativeImage module', () => {
const imagePath = path.join(__dirname, 'fixtures', 'assets', 'icon.ico') const imagePath = path.join(__dirname, 'fixtures', 'assets', 'icon.ico')
const image = nativeImage.createFromPath(imagePath) const image = nativeImage.createFromPath(imagePath)
assert(!image.isEmpty()) expect(image.isEmpty()).to.be.false
assert.deepEqual(image.getSize(), {width: 256, height: 256}) expect(image.getSize()).to.deep.equal({width: 256, height: 256})
}) })
}) })
describe('createFromNamedImage(name)', () => { describe('createFromNamedImage(name)', () => {
it('returns empty for invalid options', () => { it('returns empty for invalid options', () => {
const image = nativeImage.createFromNamedImage('totally_not_real') const image = nativeImage.createFromNamedImage('totally_not_real')
assert(image.isEmpty()) expect(image.isEmpty())
}) })
it('returns empty on non-darwin platforms', () => { it('returns empty on non-darwin platforms', () => {
if (process.platform === 'darwin') return if (process.platform === 'darwin') return
const image = nativeImage.createFromNamedImage('NSActionTemplate') const image = nativeImage.createFromNamedImage('NSActionTemplate')
assert(image.isEmpty()) expect(image.isEmpty())
}) })
it('returns a valid image on darwin', () => { it('returns a valid image on darwin', () => {
if (process.platform !== 'darwin') return if (process.platform !== 'darwin') return
const image = nativeImage.createFromNamedImage('NSActionTemplate') const image = nativeImage.createFromNamedImage('NSActionTemplate')
assert(!image.isEmpty()) expect(image.isEmpty()).to.be.false
}) })
it('returns allows an HSL shift for a valid image on darwin', () => { it('returns allows an HSL shift for a valid image on darwin', () => {
if (process.platform !== 'darwin') return if (process.platform !== 'darwin') return
const image = nativeImage.createFromNamedImage('NSActionTemplate', [0.5, 0.2, 0.8]) const image = nativeImage.createFromNamedImage('NSActionTemplate', [0.5, 0.2, 0.8])
assert(!image.isEmpty()) expect(image.isEmpty()).to.be.false
}) })
}) })
describe('resize(options)', () => { describe('resize(options)', () => {
it('returns a resized image', () => { it('returns a resized image', () => {
const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
assert.deepEqual(image.resize({}).getSize(), {width: 538, height: 190}) for (const [resizeTo, expectedSize] of new Map([
assert.deepEqual(image.resize({width: 269}).getSize(), {width: 269, height: 95}) [{}, {width: 538, height: 190}],
assert.deepEqual(image.resize({width: 600}).getSize(), {width: 600, height: 212}) [{width: 269}, {width: 269, height: 95}],
assert.deepEqual(image.resize({height: 95}).getSize(), {width: 269, height: 95}) [{width: 600}, {width: 600, height: 212}],
assert.deepEqual(image.resize({height: 200}).getSize(), {width: 566, height: 200}) [{height: 95}, {width: 269, height: 95}],
assert.deepEqual(image.resize({width: 80, height: 65}).getSize(), {width: 80, height: 65}) [{height: 200}, {width: 566, height: 200}],
assert.deepEqual(image.resize({width: 600, height: 200}).getSize(), {width: 600, height: 200}) [{width: 80, height: 65}, {width: 80, height: 65}],
assert.deepEqual(image.resize({width: 0, height: 0}).getSize(), {width: 0, height: 0}) [{width: 600, height: 200}, {width: 600, height: 200}],
assert.deepEqual(image.resize({width: -1, height: -1}).getSize(), {width: 0, height: 0}) [{width: 0, height: 0}, {width: 0, height: 0}],
[{width: -1, height: -1}, {width: 0, height: 0}]
])) {
const actualSize = image.resize(resizeTo).getSize()
expect(actualSize).to.deep.equal(expectedSize)
}
}) })
it('returns an empty image when called on an empty image', () => { it('returns an empty image when called on an empty image', () => {
assert(nativeImage.createEmpty().resize({width: 1, height: 1}).isEmpty()) expect(nativeImage.createEmpty().resize({width: 1, height: 1}).isEmpty())
assert(nativeImage.createEmpty().resize({width: 0, height: 0}).isEmpty()) expect(nativeImage.createEmpty().resize({width: 0, height: 0}).isEmpty())
}) })
it('supports a quality option', () => { it('supports a quality option', () => {
@ -372,38 +377,39 @@ describe('nativeImage module', () => {
const good = image.resize({width: 100, height: 100, quality: 'good'}) const good = image.resize({width: 100, height: 100, quality: 'good'})
const better = image.resize({width: 100, height: 100, quality: 'better'}) const better = image.resize({width: 100, height: 100, quality: 'better'})
const best = image.resize({width: 100, height: 100, quality: 'best'}) const best = image.resize({width: 100, height: 100, quality: 'best'})
assert(good.toPNG().length <= better.toPNG().length)
assert(better.toPNG().length < best.toPNG().length) expect(good.toPNG()).to.have.lengthOf.at.most(better.toPNG().length)
expect(better.toPNG()).to.have.lengthOf.below(best.toPNG().length)
}) })
}) })
describe('crop(bounds)', () => { describe('crop(bounds)', () => {
it('returns an empty image when called on an empty image', () => { it('returns an empty image when called on an empty image', () => {
assert(nativeImage.createEmpty().crop({width: 1, height: 2, x: 0, y: 0}).isEmpty()) expect(nativeImage.createEmpty().crop({width: 1, height: 2, x: 0, y: 0}).isEmpty())
assert(nativeImage.createEmpty().crop({width: 0, height: 0, x: 0, y: 0}).isEmpty()) expect(nativeImage.createEmpty().crop({width: 0, height: 0, x: 0, y: 0}).isEmpty())
}) })
it('returns an empty image when the bounds are invalid', () => { it('returns an empty image when the bounds are invalid', () => {
const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
assert(image.crop({width: 0, height: 0, x: 0, y: 0}).isEmpty()) expect(image.crop({width: 0, height: 0, x: 0, y: 0}).isEmpty())
assert(image.crop({width: -1, height: 10, x: 0, y: 0}).isEmpty()) expect(image.crop({width: -1, height: 10, x: 0, y: 0}).isEmpty())
assert(image.crop({width: 10, height: -35, x: 0, y: 0}).isEmpty()) expect(image.crop({width: 10, height: -35, x: 0, y: 0}).isEmpty())
assert(image.crop({width: 100, height: 100, x: 1000, y: 1000}).isEmpty()) expect(image.crop({width: 100, height: 100, x: 1000, y: 1000}).isEmpty())
}) })
it('returns a cropped image', () => { it('returns a cropped image', () => {
const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png'))
const cropA = image.crop({width: 25, height: 64, x: 0, y: 0}) const cropA = image.crop({width: 25, height: 64, x: 0, y: 0})
const cropB = image.crop({width: 25, height: 64, x: 30, y: 40}) const cropB = image.crop({width: 25, height: 64, x: 30, y: 40})
assert.deepEqual(cropA.getSize(), {width: 25, height: 64}) expect(cropA.getSize()).to.deep.equal({width: 25, height: 64})
assert.deepEqual(cropB.getSize(), {width: 25, height: 64}) expect(cropB.getSize()).to.deep.equal({width: 25, height: 64})
assert(!cropA.toPNG().equals(cropB.toPNG())) expect(cropA.toPNG().equals(cropB.toPNG())).to.be.false
}) })
}) })
describe('getAspectRatio()', () => { describe('getAspectRatio()', () => {
it('returns an aspect ratio of an empty image', () => { it('returns an aspect ratio of an empty image', () => {
assert.equal(nativeImage.createEmpty().getAspectRatio(), 1.0) expect(nativeImage.createEmpty().getAspectRatio()).to.equal(1.0)
}) })
it('returns an aspect ratio of an image', () => { it('returns an aspect ratio of an image', () => {
@ -412,7 +418,7 @@ describe('nativeImage module', () => {
const expectedAspectRatio = 2.8315789699554443 const expectedAspectRatio = 2.8315789699554443
const image = nativeImage.createFromPath(imageData.path) const image = nativeImage.createFromPath(imageData.path)
assert.equal(image.getAspectRatio(), expectedAspectRatio) expect(image.getAspectRatio()).to.equal(expectedAspectRatio)
}) })
}) })
@ -443,13 +449,13 @@ describe('nativeImage module', () => {
buffer: 'invalid' buffer: 'invalid'
}) })
assert.equal(image.isEmpty(), false) expect(image.isEmpty()).to.be.false
assert.deepEqual(image.getSize(), {width: 1, height: 1}) expect(image.getSize()).to.deep.equal({width: 1, height: 1})
assert.equal(image.toDataURL({scaleFactor: 1.0}), imageDataOne.dataUrl) expect(image.toDataURL({scaleFactor: 1.0})).to.equal(imageDataOne.dataUrl)
assert.equal(image.toDataURL({scaleFactor: 2.0}), imageDataTwo.dataUrl) expect(image.toDataURL({scaleFactor: 2.0})).to.equal(imageDataTwo.dataUrl)
assert.equal(image.toDataURL({scaleFactor: 3.0}), imageDataThree.dataUrl) expect(image.toDataURL({scaleFactor: 3.0})).to.equal(imageDataThree.dataUrl)
assert.equal(image.toDataURL({scaleFactor: 4.0}), imageDataThree.dataUrl) expect(image.toDataURL({scaleFactor: 4.0})).to.equal(imageDataThree.dataUrl)
}) })
it('supports adding a data URL representation for a scale factor', () => { it('supports adding a data URL representation for a scale factor', () => {
@ -478,13 +484,13 @@ describe('nativeImage module', () => {
dataURL: 'invalid' dataURL: 'invalid'
}) })
assert.equal(image.isEmpty(), false) expect(image.isEmpty()).to.be.false
assert.deepEqual(image.getSize(), {width: 1, height: 1}) expect(image.getSize()).to.deep.equal({width: 1, height: 1})
assert.equal(image.toDataURL({scaleFactor: 1.0}), imageDataOne.dataUrl) expect(image.toDataURL({scaleFactor: 1.0})).to.equal(imageDataOne.dataUrl)
assert.equal(image.toDataURL({scaleFactor: 2.0}), imageDataTwo.dataUrl) expect(image.toDataURL({scaleFactor: 2.0})).to.equal(imageDataTwo.dataUrl)
assert.equal(image.toDataURL({scaleFactor: 3.0}), imageDataThree.dataUrl) expect(image.toDataURL({scaleFactor: 3.0})).to.equal(imageDataThree.dataUrl)
assert.equal(image.toDataURL({scaleFactor: 4.0}), imageDataThree.dataUrl) expect(image.toDataURL({scaleFactor: 4.0})).to.equal(imageDataThree.dataUrl)
}) })
it('supports adding a representation to an existing image', () => { it('supports adding a representation to an existing image', () => {
@ -503,8 +509,8 @@ describe('nativeImage module', () => {
dataURL: imageDataThree.dataUrl dataURL: imageDataThree.dataUrl
}) })
assert.equal(image.toDataURL({scaleFactor: 1.0}), imageDataOne.dataUrl) expect(image.toDataURL({scaleFactor: 1.0})).to.equal(imageDataOne.dataUrl)
assert.equal(image.toDataURL({scaleFactor: 2.0}), imageDataTwo.dataUrl) expect(image.toDataURL({scaleFactor: 2.0})).to.equal(imageDataTwo.dataUrl)
}) })
}) })
}) })

View file

@ -5,6 +5,10 @@ const qs = require('querystring')
const {closeWindow} = require('./window-helpers') const {closeWindow} = require('./window-helpers')
const {remote} = require('electron') const {remote} = require('electron')
const {BrowserWindow, ipcMain, protocol, session, webContents} = remote const {BrowserWindow, ipcMain, protocol, session, webContents} = remote
// The RPC API doesn't seem to support calling methods on remote objects very
// well. In order to test stream protocol, we must work around this limitation
// and use Stream instances created in the browser process.
const stream = remote.require('stream')
describe('protocol module', () => { describe('protocol module', () => {
const protocolName = 'sp' const protocolName = 'sp'
@ -14,6 +18,33 @@ describe('protocol module', () => {
type: 'string' type: 'string'
} }
function delay (ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
function getStream (chunkSize = text.length, data = text) {
const body = stream.PassThrough()
async function sendChunks () {
let buf = new Buffer(data)
for (;;) {
body.push(buf.slice(0, chunkSize))
buf = buf.slice(chunkSize)
if (!buf.length) {
break
}
// emulate network delay
await delay(50)
}
body.push(null)
}
sendChunks()
return body
}
afterEach((done) => { afterEach((done) => {
protocol.unregisterProtocol(protocolName, () => { protocol.unregisterProtocol(protocolName, () => {
protocol.uninterceptProtocol('http', () => done()) protocol.uninterceptProtocol('http', () => done())
@ -443,6 +474,120 @@ describe('protocol module', () => {
}) })
}) })
describe('protocol.registerStreamProtocol', () => {
it('sends Stream as response', (done) => {
const handler = (request, callback) => callback(getStream())
protocol.registerStreamProtocol(protocolName, handler, (error) => {
if (error) return done(error)
$.ajax({
url: protocolName + '://fake-host',
cache: false,
success: (data) => {
assert.equal(data, text)
done()
},
error: (xhr, errorType, error) => {
done(error || new Error(`Request failed: ${xhr.status}`))
}
})
})
})
it('sends object as response', (done) => {
const handler = (request, callback) => callback({data: getStream()})
protocol.registerStreamProtocol(protocolName, handler, (error) => {
if (error) return done(error)
$.ajax({
url: protocolName + '://fake-host',
cache: false,
success: (data, _, request) => {
assert.equal(request.status, 200)
assert.equal(data, text)
done()
},
error: (xhr, errorType, error) => {
done(error || new Error(`Request failed: ${xhr.status}`))
}
})
})
})
it('sends custom response headers', (done) => {
const handler = (request, callback) => callback({
data: getStream(3),
headers: {
'x-electron': ['a', 'b']
}
})
protocol.registerStreamProtocol(protocolName, handler, (error) => {
if (error) return done(error)
$.ajax({
url: protocolName + '://fake-host',
cache: false,
success: (data, _, request) => {
assert.equal(request.status, 200)
assert.equal(request.getResponseHeader('x-electron'), 'a,b')
assert.equal(data, text)
done()
},
error: (xhr, errorType, error) => {
done(error || new Error(`Request failed: ${xhr.status}`))
}
})
})
})
it('sends custom status code', (done) => {
const handler = (request, callback) => callback({
statusCode: 204,
data: null
})
protocol.registerStreamProtocol(protocolName, handler, (error) => {
if (error) return done(error)
$.ajax({
url: protocolName + '://fake-host',
cache: false,
success: (data, _, request) => {
assert.equal(request.status, 204)
assert.equal(data, undefined)
done()
},
error: (xhr, errorType, error) => {
done(error || new Error(`Request failed: ${xhr.status}`))
}
})
})
})
it('receives request headers', (done) => {
const handler = (request, callback) => {
callback({
headers: {
'content-type': 'application/json'
},
data: getStream(5, JSON.stringify(Object.assign({}, request.headers)))
})
}
protocol.registerStreamProtocol(protocolName, handler, (error) => {
if (error) return done(error)
$.ajax({
url: protocolName + '://fake-host',
headers: {
'x-return-headers': 'yes'
},
cache: false,
success: (data) => {
assert.equal(data['x-return-headers'], 'yes')
done()
},
error: (xhr, errorType, error) => {
done(error || new Error(`Request failed: ${xhr.status}`))
}
})
})
})
})
describe('protocol.isProtocolHandled', () => { describe('protocol.isProtocolHandled', () => {
it('returns true for about:', (done) => { it('returns true for about:', (done) => {
protocol.isProtocolHandled('about', (result) => { protocol.isProtocolHandled('about', (result) => {
@ -722,6 +867,81 @@ describe('protocol module', () => {
}) })
}) })
describe('protocol.interceptStreamProtocol', () => {
it('can intercept http protocol', (done) => {
const handler = (request, callback) => callback(getStream())
protocol.interceptStreamProtocol('http', handler, (error) => {
if (error) return done(error)
$.ajax({
url: 'http://fake-host',
cache: false,
success: (data) => {
assert.equal(data, text)
done()
},
error: (xhr, errorType, error) => {
done(error || new Error(`Request failed: ${xhr.status}`))
}
})
})
})
it('can receive post data', (done) => {
const handler = (request, callback) => {
callback(getStream(3, request.uploadData[0].bytes.toString()))
}
protocol.interceptStreamProtocol('http', handler, (error) => {
if (error) return done(error)
$.ajax({
url: 'http://fake-host',
cache: false,
type: 'POST',
data: postData,
success: (data) => {
assert.deepEqual(qs.parse(data), postData)
done()
},
error: (xhr, errorType, error) => {
done(error || new Error(`Request failed: ${xhr.status}`))
}
})
})
})
it('can execute redirects', (done) => {
const handler = (request, callback) => {
if (request.url.indexOf('http://fake-host') === 0) {
setTimeout(() => {
callback({
data: null,
statusCode: 302,
headers: {
Location: 'http://fake-redirect'
}
})
}, 300)
} else {
assert.equal(request.url.indexOf('http://fake-redirect'), 0)
callback(getStream(1, 'redirect'))
}
}
protocol.interceptStreamProtocol('http', handler, (error) => {
if (error) return done(error)
$.ajax({
url: 'http://fake-host',
cache: false,
success: (data) => {
assert.equal(data, 'redirect')
done()
},
error: (xhr, errorType, error) => {
done(error || new Error(`Request failed: ${xhr.status}`))
}
})
})
})
})
describe('protocol.uninterceptProtocol', () => { describe('protocol.uninterceptProtocol', () => {
it('returns error when scheme does not exist', (done) => { it('returns error when scheme does not exist', (done) => {
protocol.uninterceptProtocol('not-exist', (error) => { protocol.uninterceptProtocol('not-exist', (error) => {

View file

@ -955,18 +955,40 @@ describe('chromium feature', () => {
slashes: true slashes: true
}) })
function createBrowserWindow ({plugins}) { function createBrowserWindow ({plugins, preload}) {
w = new BrowserWindow({ w = new BrowserWindow({
show: false, show: false,
webPreferences: { webPreferences: {
preload: path.join(fixtures, 'module', 'preload-pdf-loaded.js'), preload: path.join(fixtures, 'module', preload),
plugins: plugins plugins: plugins
} }
}) })
} }
function testPDFIsLoadedInSubFrame (page, preloadFile, done) {
const pagePath = url.format({
pathname: path.join(fixtures, 'pages', page).replace(/\\/g, '/'),
protocol: 'file',
slashes: true
})
createBrowserWindow({plugins: true, preload: preloadFile})
ipcMain.once('pdf-loaded', (event, state) => {
assert.equal(state, 'success')
done()
})
w.webContents.on('page-title-updated', () => {
const parsedURL = url.parse(w.webContents.getURL(), true)
assert.equal(parsedURL.protocol, 'chrome:')
assert.equal(parsedURL.hostname, 'pdf-viewer')
assert.equal(parsedURL.query.src, pagePath)
assert.equal(w.webContents.getTitle(), 'cat.pdf')
})
w.webContents.loadURL(pagePath)
}
it('opens when loading a pdf resource as top level navigation', (done) => { it('opens when loading a pdf resource as top level navigation', (done) => {
createBrowserWindow({plugins: true}) createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'})
ipcMain.once('pdf-loaded', (event, state) => { ipcMain.once('pdf-loaded', (event, state) => {
assert.equal(state, 'success') assert.equal(state, 'success')
done() done()
@ -982,7 +1004,7 @@ describe('chromium feature', () => {
}) })
it('opens a pdf link given params, the query string should be escaped', (done) => { it('opens a pdf link given params, the query string should be escaped', (done) => {
createBrowserWindow({plugins: true}) createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'})
ipcMain.once('pdf-loaded', (event, state) => { ipcMain.once('pdf-loaded', (event, state) => {
assert.equal(state, 'success') assert.equal(state, 'success')
done() done()
@ -1000,7 +1022,7 @@ describe('chromium feature', () => {
}) })
it('should download a pdf when plugins are disabled', (done) => { it('should download a pdf when plugins are disabled', (done) => {
createBrowserWindow({plugins: false}) createBrowserWindow({plugins: false, preload: 'preload-pdf-loaded.js'})
ipcRenderer.sendSync('set-download-option', false, false) ipcRenderer.sendSync('set-download-option', false, false)
ipcRenderer.once('download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) => { ipcRenderer.once('download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) => {
assert.equal(state, 'completed') assert.equal(state, 'completed')
@ -1013,7 +1035,7 @@ describe('chromium feature', () => {
}) })
it('should not open when pdf is requested as sub resource', (done) => { it('should not open when pdf is requested as sub resource', (done) => {
createBrowserWindow({plugins: true}) createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'})
webFrame.registerURLSchemeAsPrivileged('file', { webFrame.registerURLSchemeAsPrivileged('file', {
secure: false, secure: false,
bypassCSP: false, bypassCSP: false,
@ -1026,6 +1048,14 @@ describe('chromium feature', () => {
done() done()
}).catch((e) => done(e)) }).catch((e) => done(e))
}) })
it('opens when loading a pdf resource in a iframe', (done) => {
testPDFIsLoadedInSubFrame('pdf-in-iframe.html', 'preload-pdf-loaded-in-subframe.js', done)
})
it('opens when loading a pdf resource in a nested iframe', (done) => {
testPDFIsLoadedInSubFrame('pdf-in-nested-iframe.html', 'preload-pdf-loaded-in-nested-subframe.js', done)
})
}) })
describe('window.alert(message, title)', () => { describe('window.alert(message, title)', () => {

View file

@ -0,0 +1,15 @@
const {ipcRenderer} = require('electron')
document.addEventListener('DOMContentLoaded', (event) => {
var outerFrame = document.getElementById('outer-frame')
if (outerFrame) {
outerFrame.onload = function () {
var pdframe = outerFrame.contentWindow.document.getElementById('pdf-frame')
if (pdframe) {
pdframe.contentWindow.addEventListener('pdf-loaded', (event) => {
ipcRenderer.send('pdf-loaded', event.detail)
})
}
}
}
})

View file

@ -0,0 +1,10 @@
const {ipcRenderer} = require('electron')
document.addEventListener('DOMContentLoaded', (event) => {
var subframe = document.getElementById('pdf-frame')
if (subframe) {
subframe.contentWindow.addEventListener('pdf-loaded', (event) => {
ipcRenderer.send('pdf-loaded', event.detail)
})
}
})

View file

@ -0,0 +1,6 @@
<html>
<body>
<iframe id="pdf-frame" src="../assets/cat.pdf"/>
</script>
</body>
</html>

View file

@ -0,0 +1,5 @@
<html>
<body>
<iframe id="outer-frame" src="./pdf-in-iframe.html"/>
</body>
</html>

View file

@ -336,7 +336,7 @@ describe('node feature', () => {
}) })
it('includes the electron version in process.versions', () => { it('includes the electron version in process.versions', () => {
assert(/^\d+\.\d+\.\d+$/.test(process.versions.electron)) assert(/^\d+\.\d+\.\d+(\S*)?$/.test(process.versions.electron))
}) })
it('includes the chrome version in process.versions', () => { it('includes the chrome version in process.versions', () => {

1035
spec/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,7 @@
"version": "0.1.0", "version": "0.1.0",
"devDependencies": { "devDependencies": {
"basic-auth": "^1.0.4", "basic-auth": "^1.0.4",
"chai": "^4.1.2",
"coffee-script": "^1.12.3", "coffee-script": "^1.12.3",
"graceful-fs": "^4.1.9", "graceful-fs": "^4.1.9",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",

View file

@ -217,6 +217,10 @@ app.on('ready', function () {
window.webContents.send('executeJavaScript-promise-response', result) window.webContents.send('executeJavaScript-promise-response', result)
}).catch((error) => { }).catch((error) => {
window.webContents.send('executeJavaScript-promise-error', error) window.webContents.send('executeJavaScript-promise-error', error)
if (error && error.name) {
window.webContents.send('executeJavaScript-promise-error-name', error.name)
}
}) })
if (!hasCallback) { if (!hasCallback) {

@ -1 +1 @@
Subproject commit 0784acb6519ea45c0cef26d8599fa8c7f6b9c7ca Subproject commit b0c0a9e10bfac39d6da64a9e66e3509731d6fa69