diff --git a/README.md b/README.md index b8f55c19bd21..213580a5e2a7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![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/) -: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) +: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) 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 @@ -75,7 +75,7 @@ forums - [`electron-br`](https://electron-br.slack.com) *(Brazilian Portuguese)* - [`electron-kr`](http://www.meetup.com/electron-kr/) *(Korean)* - [`electron-jp`](https://electron-jp.slack.com) *(Japanese)* -- [`electron-tr`](http://www.meetup.com/Electron-JS-Istanbul/) *(Turkish)* +- [`electron-tr`](https://electron-tr.slack.com) *(Turkish)* - [`electron-id`](https://electron-id.slack.com) *(Indonesia)* Check out [awesome-electron](https://github.com/sindresorhus/awesome-electron) diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 954d239b5034..8e2a6c573097 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -44,7 +44,7 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, std::vector flash_version_numbers = base::SplitString( version, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - if (flash_version_numbers.size() < 1) + if (flash_version_numbers.empty()) flash_version_numbers.push_back("11"); // |SplitString()| puts in an empty string given an empty string. :( else if (flash_version_numbers[0].empty()) diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index 56a11daf69bd..115041d0082d 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -427,7 +427,7 @@ void OnClientCertificateSelected( auto certs = net::X509Certificate::CreateCertificateListFromBytes( data.c_str(), data.length(), net::X509Certificate::FORMAT_AUTO); - if (certs.size() > 0) + if (!certs.empty()) delegate->ContinueWithCertificate(certs[0].get()); } @@ -520,7 +520,7 @@ void App::OnQuit() { int exitCode = AtomBrowserMainParts::Get()->GetExitCode(); Emit("quit", exitCode); - if (process_singleton_.get()) { + if (process_singleton_) { process_singleton_->Cleanup(); process_singleton_.reset(); } @@ -655,6 +655,14 @@ void App::OnGpuProcessCrashed(base::TerminationStatus status) { status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED); } +base::FilePath App::GetAppPath() const { + return app_path_; +} + +void App::SetAppPath(const base::FilePath& app_path) { + app_path_ = app_path; +} + base::FilePath App::GetPath(mate::Arguments* args, const std::string& name) { bool succeed = false; base::FilePath path; @@ -695,7 +703,7 @@ std::string App::GetLocale() { bool App::MakeSingleInstance( const ProcessSingleton::NotificationCallback& callback) { - if (process_singleton_.get()) + if (process_singleton_) return false; base::FilePath user_dir; @@ -716,7 +724,7 @@ bool App::MakeSingleInstance( } void App::ReleaseSingleInstance() { - if (process_singleton_.get()) { + if (process_singleton_) { process_singleton_->Cleanup(); process_singleton_.reset(); } @@ -959,6 +967,8 @@ void App::BuildPrototype( .SetMethod("isUnityRunning", base::Bind(&Browser::IsUnityRunning, browser)) #endif + .SetMethod("setAppPath", &App::SetAppPath) + .SetMethod("getAppPath", &App::GetAppPath) .SetMethod("setPath", &App::SetPath) .SetMethod("getPath", &App::GetPath) .SetMethod("setDesktopName", &App::SetDesktopName) diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 8b276f334d5c..a87b88bc4642 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -70,6 +70,8 @@ class App : public AtomBrowserClient::Delegate, std::unique_ptr model); #endif + base::FilePath GetAppPath() const; + protected: explicit App(v8::Isolate* isolate); ~App() override; @@ -115,6 +117,8 @@ class App : public AtomBrowserClient::Delegate, void OnGpuProcessCrashed(base::TerminationStatus status) override; private: + void SetAppPath(const base::FilePath& app_path); + // Get/Set the pre-defined path in PathService. base::FilePath GetPath(mate::Arguments* args, const std::string& name); void SetPath(mate::Arguments* args, @@ -154,6 +158,8 @@ class App : public AtomBrowserClient::Delegate, // Tracks tasks requesting file icons. base::CancelableTaskTracker cancelable_task_tracker_; + base::FilePath app_path_; + DISALLOW_COPY_AND_ASSIGN(App); }; diff --git a/atom/browser/api/atom_api_auto_updater.cc b/atom/browser/api/atom_api_auto_updater.cc index 67079abf2864..c23e488f64fd 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -7,6 +7,7 @@ #include "atom/browser/browser.h" #include "atom/browser/native_window.h" #include "atom/browser/window_list.h" +#include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/node_includes.h" #include "base/time/time.h" @@ -47,7 +48,9 @@ void AutoUpdater::OnError(const std::string& message) { v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); auto error = v8::Exception::Error(mate::StringToV8(isolate(), message)); - EmitCustomEvent( + mate::EmitEvent( + isolate(), + GetWrapper(), "error", error->ToObject(isolate()->GetCurrentContext()).ToLocalChecked(), // Message is also emitted to keep compatibility with old code. @@ -87,16 +90,14 @@ void AutoUpdater::SetFeedURL(const std::string& url, mate::Arguments* args) { void AutoUpdater::QuitAndInstall() { // If we don't have any window then quitAndInstall immediately. - WindowList* window_list = WindowList::GetInstance(); - if (window_list->size() == 0) { + if (WindowList::IsEmpty()) { auto_updater::AutoUpdater::QuitAndInstall(); return; } // Otherwise do the restart after all windows have been closed. - window_list->AddObserver(this); - for (NativeWindow* window : *window_list) - window->Close(); + WindowList::AddObserver(this); + WindowList::CloseAllWindows(); } // static diff --git a/atom/browser/api/atom_api_browser_view.cc b/atom/browser/api/atom_api_browser_view.cc new file mode 100644 index 000000000000..d37d2df41c5b --- /dev/null +++ b/atom/browser/api/atom_api_browser_view.cc @@ -0,0 +1,162 @@ +// 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 "atom/browser/api/atom_api_browser_view.h" + +#include "atom/browser/api/atom_api_web_contents.h" +#include "atom/browser/browser.h" +#include "atom/browser/native_browser_view.h" +#include "atom/common/color_util.h" +#include "atom/common/native_mate_converters/gfx_converter.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/node_includes.h" +#include "atom/common/options_switches.h" +#include "native_mate/constructor.h" +#include "native_mate/dictionary.h" +#include "ui/gfx/geometry/rect.h" + +namespace mate { + +template <> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + atom::AutoResizeFlags* auto_resize_flags) { + mate::Dictionary params; + if (!ConvertFromV8(isolate, val, ¶ms)) { + return false; + } + + uint8_t flags = 0; + bool width = false; + if (params.Get("width", &width) && width) { + flags |= atom::kAutoResizeWidth; + } + bool height = false; + if (params.Get("height", &height) && height) { + flags |= atom::kAutoResizeHeight; + } + + *auto_resize_flags = static_cast(flags); + return true; + } +}; + +} // namespace mate + +namespace atom { + +namespace api { + +BrowserView::BrowserView(v8::Isolate* isolate, + v8::Local wrapper, + const mate::Dictionary& options) + : api_web_contents_(nullptr) { + Init(isolate, wrapper, options); +} + +void BrowserView::Init(v8::Isolate* isolate, + v8::Local wrapper, + const mate::Dictionary& options) { + mate::Dictionary web_preferences = mate::Dictionary::CreateEmpty(isolate); + options.Get(options::kWebPreferences, &web_preferences); + web_preferences.Set("isBrowserView", true); + mate::Handle web_contents = + WebContents::Create(isolate, web_preferences); + + web_contents_.Reset(isolate, web_contents.ToV8()); + api_web_contents_ = web_contents.get(); + + view_.reset(NativeBrowserView::Create( + api_web_contents_->managed_web_contents()->GetView())); + + InitWith(isolate, wrapper); +} + +BrowserView::~BrowserView() { + api_web_contents_->DestroyWebContents(true /* async */); +} + +// static +mate::WrappableBase* BrowserView::New(mate::Arguments* args) { + if (!Browser::Get()->is_ready()) { + args->ThrowError("Cannot create BrowserView before app is ready"); + return nullptr; + } + + if (args->Length() > 1) { + args->ThrowError("Too many arguments"); + return nullptr; + } + + mate::Dictionary options; + if (!(args->Length() == 1 && args->GetNext(&options))) { + options = mate::Dictionary::CreateEmpty(args->isolate()); + } + + return new BrowserView(args->isolate(), args->GetThis(), options); +} + +int32_t BrowserView::ID() const { + return weak_map_id(); +} + +void BrowserView::SetAutoResize(AutoResizeFlags flags) { + view_->SetAutoResizeFlags(flags); +} + +void BrowserView::SetBounds(const gfx::Rect& bounds) { + view_->SetBounds(bounds); +} + +void BrowserView::SetBackgroundColor(const std::string& color_name) { + view_->SetBackgroundColor(ParseHexColor(color_name)); +} + +v8::Local BrowserView::WebContents() { + if (web_contents_.IsEmpty()) { + return v8::Null(isolate()); + } + + return v8::Local::New(isolate(), web_contents_); +} + +// static +void BrowserView::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "BrowserView")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .MakeDestroyable() + .SetMethod("setAutoResize", &BrowserView::SetAutoResize) + .SetMethod("setBounds", &BrowserView::SetBounds) + .SetMethod("setBackgroundColor", &BrowserView::SetBackgroundColor) + .SetProperty("webContents", &BrowserView::WebContents) + .SetProperty("id", &BrowserView::ID); +} + +} // namespace api + +} // namespace atom + +namespace { + +using atom::api::BrowserView; + +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + BrowserView::SetConstructor(isolate, base::Bind(&BrowserView::New)); + + mate::Dictionary browser_view( + isolate, BrowserView::GetConstructor(isolate)->GetFunction()); + + mate::Dictionary dict(isolate, exports); + dict.Set("BrowserView", browser_view); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_browser_view, Initialize) diff --git a/atom/browser/api/atom_api_browser_view.h b/atom/browser/api/atom_api_browser_view.h new file mode 100644 index 000000000000..7531cfcc4a1b --- /dev/null +++ b/atom/browser/api/atom_api_browser_view.h @@ -0,0 +1,72 @@ +// 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_ATOM_API_BROWSER_VIEW_H_ +#define ATOM_BROWSER_API_ATOM_API_BROWSER_VIEW_H_ + +#include +#include + +#include "atom/browser/api/trackable_object.h" +#include "atom/browser/native_browser_view.h" +#include "native_mate/handle.h" + +namespace gfx { +class Rect; +} + +namespace mate { +class Arguments; +class Dictionary; +} // namespace mate + +namespace atom { + +class NativeBrowserView; + +namespace api { + +class WebContents; + +class BrowserView : public mate::TrackableObject { + public: + static mate::WrappableBase* New(mate::Arguments* args); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + NativeBrowserView* view() const { return view_.get(); } + + int32_t ID() const; + + protected: + BrowserView(v8::Isolate* isolate, + v8::Local wrapper, + const mate::Dictionary& options); + ~BrowserView() override; + + private: + void Init(v8::Isolate* isolate, + v8::Local wrapper, + const mate::Dictionary& options); + + void SetAutoResize(AutoResizeFlags flags); + void SetBounds(const gfx::Rect& bounds); + void SetBackgroundColor(const std::string& color_name); + + v8::Local WebContents(); + + v8::Global web_contents_; + class WebContents* api_web_contents_; + + std::unique_ptr view_; + + DISALLOW_COPY_AND_ASSIGN(BrowserView); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_BROWSER_VIEW_H_ diff --git a/atom/browser/api/atom_api_cookies.cc b/atom/browser/api/atom_api_cookies.cc index ddb6910b509a..2ce9e12f6c5b 100644 --- a/atom/browser/api/atom_api_cookies.cc +++ b/atom/browser/api/atom_api_cookies.cc @@ -179,6 +179,13 @@ void OnSetCookie(const Cookies::SetCallback& callback, bool success) { base::Bind(callback, success ? Cookies::SUCCESS : Cookies::FAILED)); } +// Flushes cookie store in IO thread. +void FlushCookieStoreOnIOThread( + scoped_refptr getter, + const base::Closure& callback) { + GetCookieStore(getter)->FlushStore(base::Bind(RunCallbackInUI, callback)); +} + // Sets cookie with |details| in IO thread. void SetCookieOnIO(scoped_refptr getter, std::unique_ptr details, @@ -265,6 +272,13 @@ void Cookies::Set(const base::DictionaryValue& details, base::Bind(SetCookieOnIO, getter, Passed(&copied), callback)); } +void Cookies::FlushStore(const base::Closure& callback) { + auto getter = make_scoped_refptr(request_context_getter_); + content::BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(FlushCookieStoreOnIOThread, getter, callback)); +} + void Cookies::OnCookieChanged(const net::CanonicalCookie& cookie, bool removed, net::CookieStore::ChangeCause cause) { @@ -286,7 +300,8 @@ void Cookies::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) .SetMethod("get", &Cookies::Get) .SetMethod("remove", &Cookies::Remove) - .SetMethod("set", &Cookies::Set); + .SetMethod("set", &Cookies::Set) + .SetMethod("flushStore", &Cookies::FlushStore); } } // namespace api diff --git a/atom/browser/api/atom_api_cookies.h b/atom/browser/api/atom_api_cookies.h index 3a7a98fbafd9..d20dab8394c6 100644 --- a/atom/browser/api/atom_api_cookies.h +++ b/atom/browser/api/atom_api_cookies.h @@ -53,6 +53,7 @@ class Cookies : public mate::TrackableObject, void Remove(const GURL& url, const std::string& name, const base::Closure& callback); void Set(const base::DictionaryValue& details, const SetCallback& callback); + void FlushStore(const base::Closure& callback); // AtomCookieDelegate::Observer: void OnCookieChanged(const net::CanonicalCookie& cookie, diff --git a/atom/browser/api/atom_api_debugger.cc b/atom/browser/api/atom_api_debugger.cc index 90999a4f602a..075823185259 100644 --- a/atom/browser/api/atom_api_debugger.cc +++ b/atom/browser/api/atom_api_debugger.cc @@ -9,7 +9,6 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/value_converter.h" -#include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/web_contents.h" @@ -45,12 +44,23 @@ void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host, const std::string& message) { DCHECK(agent_host == agent_host_.get()); - std::unique_ptr parsed_message(base::JSONReader::Read(message)); - if (!parsed_message->IsType(base::Value::TYPE_DICTIONARY)) - return; + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + + v8::Local local_message = + v8::String::NewFromUtf8(isolate(), message.data()); + v8::MaybeLocal parsed_message = v8::JSON::Parse( + isolate()->GetCurrentContext(), local_message); + if (parsed_message.IsEmpty()) { + return; + } + + std::unique_ptr dict(new base::DictionaryValue()); + if (!mate::ConvertFromV8(isolate(), parsed_message.ToLocalChecked(), + dict.get())) { + return; + } - base::DictionaryValue* dict = - static_cast(parsed_message.get()); int id; if (!dict->GetInteger("id", &id)) { std::string method; diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 326834472d24..cb9f17f704fb 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -8,11 +8,13 @@ #include "atom/browser/api/atom_api_window.h" #include "atom/browser/native_window.h" +#include "atom/browser/ui/certificate_trust.h" #include "atom/browser/ui/file_dialog.h" #include "atom/browser/ui/message_box.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/image_converter.h" +#include "atom/common/native_mate_converters/net_converter.h" #include "native_mate/dictionary.h" #include "atom/common/node_includes.h" @@ -78,13 +80,14 @@ void ShowMessageBox(int type, if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons, - default_id, cancel_id, options, title, message, detail, - checkbox_label, checkbox_checked, icon, callback); + atom::ShowMessageBox(window, static_cast(type), + buttons, default_id, cancel_id, options, title, + message, detail, checkbox_label, checkbox_checked, + icon, callback); } else { - int chosen = atom::ShowMessageBox(window, (atom::MessageBoxType)type, - buttons, default_id, cancel_id, - options, title, message, detail, icon); + int chosen = atom::ShowMessageBox( + window, static_cast(type), buttons, default_id, + cancel_id, options, title, message, detail, icon); args->Return(chosen); } } @@ -126,6 +129,10 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("showErrorBox", &atom::ShowErrorBox); dict.SetMethod("showOpenDialog", &ShowOpenDialog); dict.SetMethod("showSaveDialog", &ShowSaveDialog); +#if defined(OS_MACOSX) + dict.SetMethod("showCertificateTrustDialog", + &certificate_trust::ShowCertificateTrust); +#endif } } // namespace diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 0c850888e8e7..83d103a631f7 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -233,7 +233,7 @@ class ResolveProxyHelper { public: ResolveProxyHelper(AtomBrowserContext* browser_context, const GURL& url, - Session::ResolveProxyCallback callback) + const Session::ResolveProxyCallback& callback) : callback_(callback), original_thread_(base::ThreadTaskRunnerHandle::Get()) { scoped_refptr context_getter = diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 967ae50a7e1f..d3607e7283cc 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -8,6 +8,7 @@ #include "atom/browser/net/atom_url_request.h" #include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/node_includes.h" @@ -145,6 +146,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { dict.Get("method", &method); std::string url; dict.Get("url", &url); + std::string redirect_policy; + dict.Get("redirect", &redirect_policy); std::string partition; mate::Handle session; if (dict.Get("session", &session)) { @@ -156,8 +159,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { } auto browser_context = session->browser_context(); auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); - auto atom_url_request = - AtomURLRequest::Create(browser_context, method, url, api_url_request); + auto atom_url_request = AtomURLRequest::Create( + browser_context, method, url, redirect_policy, api_url_request); api_url_request->atom_request_ = atom_url_request; @@ -176,6 +179,7 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) .SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload) + .SetMethod("followRedirect", &URLRequest::FollowRedirect) .SetMethod("_setLoadFlags", &URLRequest::SetLoadFlags) .SetProperty("notStarted", &URLRequest::NotStarted) .SetProperty("finished", &URLRequest::Finished) @@ -246,6 +250,17 @@ void URLRequest::Cancel() { Close(); } +void URLRequest::FollowRedirect() { + if (request_state_.Canceled() || request_state_.Closed()) { + return; + } + + DCHECK(atom_request_); + if (atom_request_) { + atom_request_->FollowRedirect(); + } +} + bool URLRequest::SetExtraHeader(const std::string& name, const std::string& value) { // Request state must be in the initial non started state. @@ -305,6 +320,24 @@ void URLRequest::SetLoadFlags(int flags) { } } +void URLRequest::OnReceivedRedirect( + int status_code, + const std::string& method, + const GURL& url, + scoped_refptr response_headers) { + if (request_state_.Canceled() || request_state_.Closed()) { + return; + } + + DCHECK(atom_request_); + if (!atom_request_) { + return; + } + + EmitRequestEvent(false, "redirect", status_code, method, url, + response_headers.get()); +} + void URLRequest::OnAuthenticationRequired( scoped_refptr auth_info) { if (request_state_.Canceled() || request_state_.Closed()) { diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index c92ac01961cd..372ac98ac657 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -99,6 +99,11 @@ class URLRequest : public mate::EventEmitter { v8::Local prototype); // Methods for reporting events into JavaScript. + void OnReceivedRedirect( + int status_code, + const std::string& method, + const GURL& url, + scoped_refptr response_headers); void OnAuthenticationRequired( scoped_refptr auth_info); void OnResponseStarted( @@ -170,6 +175,7 @@ class URLRequest : public mate::EventEmitter { bool Failed() const; bool Write(scoped_refptr buffer, bool is_last); void Cancel(); + void FollowRedirect(); bool SetExtraHeader(const std::string& name, const std::string& value); void RemoveExtraHeader(const std::string& name); void SetChunkedUpload(bool is_chunked_upload); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 6ba23b3c9afc..a06ec2244dde 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -188,6 +188,7 @@ struct Converter { switch (val) { case Type::BACKGROUND_PAGE: type = "backgroundPage"; break; case Type::BROWSER_WINDOW: type = "window"; break; + case Type::BROWSER_VIEW: type = "browserView"; break; case Type::REMOTE: type = "remote"; break; case Type::WEB_VIEW: type = "webview"; break; case Type::OFF_SCREEN: type = "offscreen"; break; @@ -202,10 +203,12 @@ struct Converter { std::string type; if (!ConvertFromV8(isolate, val, &type)) return false; - if (type == "webview") { - *out = Type::WEB_VIEW; - } else if (type == "backgroundPage") { + if (type == "backgroundPage") { *out = Type::BACKGROUND_PAGE; + } else if (type == "browserView") { + *out = Type::BROWSER_VIEW; + } else if (type == "webview") { + *out = Type::WEB_VIEW; } else if (type == "offscreen") { *out = Type::OFF_SCREEN; } else { @@ -240,7 +243,7 @@ content::ServiceWorkerContext* GetServiceWorkerContext( } // Called when CapturePage is done. -void OnCapturePageDone(base::Callback callback, +void OnCapturePageDone(const base::Callback& callback, const SkBitmap& bitmap, content::ReadbackResponse response) { callback.Run(gfx::Image::CreateFrom1xBitmap(bitmap)); @@ -306,6 +309,8 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) type_ = WEB_VIEW; else if (options.Get("isBackgroundPage", &b) && b) type_ = BACKGROUND_PAGE; + else if (options.Get("isBrowserView", &b) && b) + type_ = BROWSER_VIEW; else if (options.Get("offscreen", &b) && b) type_ = OFF_SCREEN; @@ -411,14 +416,31 @@ WebContents::~WebContents() { if (type_ == WEB_VIEW) guest_delegate_->Destroy(); - // The WebContentsDestroyed will not be called automatically because we - // unsubscribe from webContents before destroying it. So we have to manually - // call it here to make sure "destroyed" event is emitted. RenderViewDeleted(web_contents()->GetRenderViewHost()); - WebContentsDestroyed(); + + if (type_ == WEB_VIEW) { + DestroyWebContents(false /* async */); + } else { + if (type_ == BROWSER_WINDOW && owner_window()) { + owner_window()->CloseContents(nullptr); + } else { + DestroyWebContents(true /* async */); + } + // The WebContentsDestroyed will not be called automatically because we + // destroy the webContents in the next tick. So we have to manually + // call it here to make sure "destroyed" event is emitted. + WebContentsDestroyed(); + } } } +void WebContents::DestroyWebContents(bool async) { + // This event is only for internal use, which is emitted when WebContents is + // being destroyed. + Emit("will-destroy"); + ResetManagedWebContents(async); +} + bool WebContents::DidAddMessageToConsole(content::WebContents* source, int32_t level, const base::string16& message, @@ -468,7 +490,7 @@ void WebContents::AddNewContents(content::WebContents* source, if (Emit("-add-new-contents", api_web_contents, disposition, user_gesture, initial_rect.x(), initial_rect.y(), initial_rect.width(), initial_rect.height())) { - api_web_contents->DestroyWebContents(); + api_web_contents->DestroyWebContents(true /* async */); } } @@ -807,10 +829,8 @@ void WebContents::DidFinishNavigation( void WebContents::TitleWasSet(content::NavigationEntry* entry, bool explicit_set) { - if (entry) - Emit("-page-title-updated", entry->GetTitle(), explicit_set); - else - Emit("-page-title-updated", "", explicit_set); + auto title = entry ? entry->GetTitle() : base::string16(); + Emit("page-title-updated", title, explicit_set); } void WebContents::DidUpdateFaviconURL( @@ -919,10 +939,6 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { // be destroyed on close, and WebContentsDestroyed would be called for it, so // we need to make sure the api::WebContents is also deleted. void WebContents::WebContentsDestroyed() { - // This event is only for internal use, which is emitted when WebContents is - // being destroyed. - Emit("will-destroy"); - // Cleanup relationships with other parts. RemoveFromWeakMap(); diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 2513dc7722a8..2289cdb4a45b 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -53,10 +53,11 @@ class WebContents : public mate::TrackableObject, public: enum Type { BACKGROUND_PAGE, // A DevTools extension background page. - BROWSER_WINDOW, // Used by BrowserWindow. - REMOTE, // Thin wrap around an existing WebContents. - WEB_VIEW, // Used by . - OFF_SCREEN, // Used for offscreen rendering + BROWSER_WINDOW, // Used by BrowserWindow. + BROWSER_VIEW, // Used by BrowserView. + REMOTE, // Thin wrap around an existing WebContents. + WEB_VIEW, // Used by . + OFF_SCREEN, // Used for offscreen rendering }; // For node.js callback function type: function(error, buffer) @@ -76,6 +77,9 @@ class WebContents : public mate::TrackableObject, static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); + // Notifies to destroy any guest web contents before destroying self. + void DestroyWebContents(bool async); + int64_t GetID() const; int GetProcessID() const; Type GetType() const; diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 6748c9c0dd1d..6862915f9cf2 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -5,6 +5,7 @@ #include "atom/browser/api/atom_api_window.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "atom/browser/api/atom_api_browser_view.h" #include "atom/browser/api/atom_api_menu.h" #include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/browser.h" @@ -172,7 +173,7 @@ void Window::WillDestroyNativeObject() { } void Window::OnWindowClosed() { - api_web_contents_->DestroyWebContents(); + api_web_contents_->DestroyWebContents(true /* async */); RemoveFromWeakMap(); window_->RemoveObserver(this); @@ -190,6 +191,10 @@ void Window::OnWindowClosed() { FROM_HERE, GetDestroyClosure()); } +void Window::OnWindowEndSession() { + Emit("session-end"); +} + void Window::OnWindowBlur() { Emit("blur"); } @@ -262,6 +267,14 @@ void Window::OnWindowSwipe(const std::string& direction) { Emit("swipe", direction); } +void Window::OnWindowSheetBegin() { + Emit("sheet-begin"); +} + +void Window::OnWindowSheetEnd() { + Emit("sheet-end"); +} + void Window::OnWindowEnterHtmlFullScreen() { Emit("enter-html-full-screen"); } @@ -816,6 +829,25 @@ std::vector> Window::GetChildWindows() const { return child_windows_.Values(isolate()); } +v8::Local Window::GetBrowserView() const { + if (browser_view_.IsEmpty()) { + return v8::Null(isolate()); + } + + return v8::Local::New(isolate(), browser_view_); +} + +void Window::SetBrowserView(v8::Local value) { + mate::Handle browser_view; + if (value->IsNull()) { + window_->SetBrowserView(nullptr); + browser_view_.Reset(); + } else if (mate::ConvertFromV8(isolate(), value, &browser_view)) { + window_->SetBrowserView(browser_view->view()); + browser_view_.Reset(isolate(), value); + } +} + bool Window::IsModal() const { return window_->is_modal(); } @@ -853,15 +885,20 @@ void Window::RefreshTouchBarItem(const std::string& item_id) { window_->RefreshTouchBarItem(item_id); } +void Window::SetEscapeTouchBarItem(const mate::PersistentDictionary& item) { + window_->SetEscapeTouchBarItem(item); +} + int32_t Window::ID() const { return weak_map_id(); } v8::Local Window::WebContents(v8::Isolate* isolate) { - if (web_contents_.IsEmpty()) + if (web_contents_.IsEmpty()) { return v8::Null(isolate); - else - return v8::Local::New(isolate, web_contents_); + } + + return v8::Local::New(isolate, web_contents_); } void Window::RemoveFromParentChildWindows() { @@ -906,6 +943,8 @@ void Window::BuildPrototype(v8::Isolate* isolate, #endif .SetMethod("getParentWindow", &Window::GetParentWindow) .SetMethod("getChildWindows", &Window::GetChildWindows) + .SetMethod("getBrowserView", &Window::GetBrowserView) + .SetMethod("setBrowserView", &Window::SetBrowserView) .SetMethod("isModal", &Window::IsModal) .SetMethod("getNativeWindowHandle", &Window::GetNativeWindowHandle) .SetMethod("getBounds", &Window::GetBounds) @@ -975,6 +1014,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setVibrancy", &Window::SetVibrancy) .SetMethod("_setTouchBarItems", &Window::SetTouchBar) .SetMethod("_refreshTouchBarItem", &Window::RefreshTouchBarItem) + .SetMethod("_setEscapeTouchBarItem", &Window::SetEscapeTouchBarItem) #if defined(OS_WIN) .SetMethod("hookWindowMessage", &Window::HookWindowMessage) .SetMethod("isWindowMessageHooked", &Window::IsWindowMessageHooked) diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index f30baf79d4df..75f0328ba64f 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -63,6 +63,7 @@ class Window : public mate::TrackableObject, void WillCloseWindow(bool* prevent_default) override; void WillDestroyNativeObject() override; void OnWindowClosed() override; + void OnWindowEndSession() override; void OnWindowBlur() override; void OnWindowFocus() override; void OnWindowShow() override; @@ -79,6 +80,8 @@ class Window : public mate::TrackableObject, void OnWindowScrollTouchEnd() override; void OnWindowScrollTouchEdge() override; void OnWindowSwipe(const std::string& direction) override; + void OnWindowSheetBegin() override; + void OnWindowSheetEnd() override; void OnWindowEnterFullScreen() override; void OnWindowLeaveFullScreen() override; void OnWindowEnterHtmlFullScreen() override; @@ -180,6 +183,8 @@ class Window : public mate::TrackableObject, void SetParentWindow(v8::Local value, mate::Arguments* args); v8::Local GetParentWindow() const; std::vector> GetChildWindows() const; + v8::Local GetBrowserView() const; + void SetBrowserView(v8::Local value); bool IsModal() const; v8::Local GetNativeWindowHandle(); @@ -208,6 +213,7 @@ class Window : public mate::TrackableObject, void SetVibrancy(mate::Arguments* args); void SetTouchBar(const std::vector& items); void RefreshTouchBarItem(const std::string& item_id); + void SetEscapeTouchBarItem(const mate::PersistentDictionary& item); v8::Local WebContents(v8::Isolate* isolate); @@ -219,6 +225,7 @@ class Window : public mate::TrackableObject, MessageCallbackMap messages_callback_map_; #endif + v8::Global browser_view_; v8::Global web_contents_; v8::Global menu_; v8::Global parent_window_; diff --git a/atom/browser/atom_access_token_store.cc b/atom/browser/atom_access_token_store.cc index aef54dfa0ebc..6a5597ca7780 100644 --- a/atom/browser/atom_access_token_store.cc +++ b/atom/browser/atom_access_token_store.cc @@ -7,11 +7,13 @@ #include #include -#include "atom/browser/atom_browser_context.h" #include "atom/common/google_api_key.h" #include "base/environment.h" #include "content/public/browser/browser_thread.h" #include "device/geolocation/geolocation_provider.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_builder.h" +#include "net/url_request/url_request_context_getter.h" using content::BrowserThread; @@ -19,51 +21,40 @@ namespace atom { namespace internal { -// Loads access tokens and other necessary data on the UI thread, and -// calls back to the originator on the originating thread. -class TokenLoadingJob : public base::RefCountedThreadSafe { +class GeoURLRequestContextGetter : public net::URLRequestContextGetter { public: - explicit TokenLoadingJob( - const device::AccessTokenStore::LoadAccessTokensCallback& callback) - : callback_(callback), request_context_getter_(nullptr) {} + net::URLRequestContext* GetURLRequestContext() override { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + if (!url_request_context_.get()) { + net::URLRequestContextBuilder builder; + builder.set_proxy_config_service( + net::ProxyService::CreateSystemProxyConfigService( + BrowserThread::GetTaskRunnerForThread(BrowserThread::IO), + BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE))); + url_request_context_ = builder.Build(); + } + return url_request_context_.get(); + } - void Run(AtomBrowserContext* browser_context) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); - request_context_getter_ = browser_context->GetRequestContext(); - std::unique_ptr env(base::Environment::Create()); - if (!env->GetVar("GOOGLE_API_KEY", &api_key_)) - api_key_ = GOOGLEAPIS_API_KEY; - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(&TokenLoadingJob::RespondOnIOThread, this)); + scoped_refptr GetNetworkTaskRunner() + const override { + return BrowserThread::GetTaskRunnerForThread(BrowserThread::IO); } private: - friend class base::RefCountedThreadSafe; + friend class atom::AtomAccessTokenStore; - ~TokenLoadingJob() {} + GeoURLRequestContextGetter() {} + ~GeoURLRequestContextGetter() override {} - void RespondOnIOThread() { - // Equivalent to access_token_map[kGeolocationProviderURL]. - // Somehow base::string16 is causing compilation errors when used in a pair - // of std::map on Linux, this can work around it. - device::AccessTokenStore::AccessTokenMap access_token_map; - std::pair token_pair; - token_pair.first = GURL(GOOGLEAPIS_ENDPOINT + api_key_); - access_token_map.insert(token_pair); - - callback_.Run(access_token_map, request_context_getter_); - } - - device::AccessTokenStore::LoadAccessTokensCallback callback_; - net::URLRequestContextGetter* request_context_getter_; - std::string api_key_; + std::unique_ptr url_request_context_; + DISALLOW_COPY_AND_ASSIGN(GeoURLRequestContextGetter); }; } // namespace internal -AtomAccessTokenStore::AtomAccessTokenStore() { - browser_context_ = AtomBrowserContext::From("", false); +AtomAccessTokenStore::AtomAccessTokenStore() + : request_context_getter_(new internal::GeoURLRequestContextGetter) { device::GeolocationProvider::GetInstance()->UserDidOptIntoLocationServices(); } @@ -72,16 +63,19 @@ AtomAccessTokenStore::~AtomAccessTokenStore() { void AtomAccessTokenStore::LoadAccessTokens( const LoadAccessTokensCallback& callback) { - scoped_refptr job( - new internal::TokenLoadingJob(callback)); - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&AtomAccessTokenStore::RunTokenLoadingJob, - this, base::RetainedRef(job))); -} + std::unique_ptr env(base::Environment::Create()); + std::string api_key; + if (!env->GetVar("GOOGLE_API_KEY", &api_key)) + api_key = GOOGLEAPIS_API_KEY; + // Equivalent to access_token_map[kGeolocationProviderURL]. + // Somehow base::string16 is causing compilation errors when used in a pair + // of std::map on Linux, this can work around it. + device::AccessTokenStore::AccessTokenMap access_token_map; + std::pair token_pair; + token_pair.first = GURL(GOOGLEAPIS_ENDPOINT + api_key); + access_token_map.insert(token_pair); -void AtomAccessTokenStore::RunTokenLoadingJob( - scoped_refptr job) { - job->Run(browser_context_.get()); + callback.Run(access_token_map, request_context_getter_.get()); } void AtomAccessTokenStore::SaveAccessToken(const GURL& server_url, diff --git a/atom/browser/atom_access_token_store.h b/atom/browser/atom_access_token_store.h index 07884e58d60a..820ceddce4fd 100644 --- a/atom/browser/atom_access_token_store.h +++ b/atom/browser/atom_access_token_store.h @@ -9,10 +9,8 @@ namespace atom { -class AtomBrowserContext; - namespace internal { -class TokenLoadingJob; +class GeoURLRequestContextGetter; } class AtomAccessTokenStore : public device::AccessTokenStore { @@ -27,9 +25,7 @@ class AtomAccessTokenStore : public device::AccessTokenStore { const base::string16& access_token) override; private: - void RunTokenLoadingJob(scoped_refptr job); - - scoped_refptr browser_context_; + scoped_refptr request_context_getter_; DISALLOW_COPY_AND_ASSIGN(AtomAccessTokenStore); }; diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index d0bbf4ad535f..77c3212e2e02 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -172,6 +172,7 @@ std::string AtomBrowserClient::GetApplicationLocale() { } void AtomBrowserClient::OverrideSiteInstanceForNavigation( + content::RenderFrameHost* render_frame_host, content::BrowserContext* browser_context, content::SiteInstance* current_instance, const GURL& url, @@ -234,6 +235,11 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches( } #endif + if (delegate_) { + auto app_path = static_cast(delegate_)->GetAppPath(); + command_line->AppendSwitchPath(switches::kAppPath, app_path); + } + content::WebContents* web_contents = GetWebContentsFromProcessID(process_id); if (!web_contents) return; diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index c2a7d5edd0f4..70573d6eee37 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -54,6 +54,7 @@ class AtomBrowserClient : public brightray::BrowserClient, content::WebPreferences* prefs) override; std::string GetApplicationLocale() override; void OverrideSiteInstanceForNavigation( + content::RenderFrameHost* render_frame_host, content::BrowserContext* browser_context, content::SiteInstance* current_instance, const GURL& dest_url, diff --git a/atom/browser/auto_updater_mac.mm b/atom/browser/auto_updater_mac.mm index a6102f3e73fa..3802fef16299 100644 --- a/atom/browser/auto_updater_mac.mm +++ b/atom/browser/auto_updater_mac.mm @@ -27,7 +27,7 @@ namespace { bool g_update_available = false; std::string update_url_ = ""; -} +} // namespace std::string AutoUpdater::GetFeedURL() { return update_url_; diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 9b7423cd7d9f..b2900a326ff3 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -43,11 +43,10 @@ void Browser::Quit() { if (!is_quiting_) return; - atom::WindowList* window_list = atom::WindowList::GetInstance(); - if (window_list->size() == 0) + if (atom::WindowList::IsEmpty()) NotifyAndShutdown(); - - window_list->CloseAllWindows(); + else + atom::WindowList::CloseAllWindows(); } void Browser::Exit(mate::Arguments* args) { @@ -65,14 +64,12 @@ void Browser::Exit(mate::Arguments* args) { is_exiting_ = true; // Must destroy windows before quitting, otherwise bad things can happen. - atom::WindowList* window_list = atom::WindowList::GetInstance(); - if (window_list->size() == 0) { + if (atom::WindowList::IsEmpty()) { Shutdown(); } else { // Unlike Quit(), we do not ask to close window, but destroy the window // without asking. - for (NativeWindow* window : *window_list) - window->CloseContents(nullptr); // e.g. Destroy() + atom::WindowList::DestroyAllWindows(); } } } diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 28103a99c704..78cac65f7e40 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -102,7 +102,7 @@ class Browser : public WindowListObserver { std::vector args; }; void SetLoginItemSettings(LoginItemSettings settings); - LoginItemSettings GetLoginItemSettings(LoginItemSettings options); + LoginItemSettings GetLoginItemSettings(const LoginItemSettings& options); #if defined(OS_MACOSX) // Hide the application. diff --git a/atom/browser/browser_linux.cc b/atom/browser/browser_linux.cc index 6abfcf5c3481..f569040a2189 100644 --- a/atom/browser/browser_linux.cc +++ b/atom/browser/browser_linux.cc @@ -16,9 +16,7 @@ namespace atom { void Browser::Focus() { // Focus on the first visible window. - WindowList* list = WindowList::GetInstance(); - for (WindowList::iterator iter = list->begin(); iter != list->end(); ++iter) { - NativeWindow* window = *iter; + for (const auto& window : WindowList::GetWindows()) { if (window->IsVisible()) { window->Focus(true); break; @@ -64,7 +62,7 @@ void Browser::SetLoginItemSettings(LoginItemSettings settings) { } Browser::LoginItemSettings Browser::GetLoginItemSettings( - LoginItemSettings options) { + const LoginItemSettings& options) { return LoginItemSettings(); } diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index c318cf850701..38a0a003d968 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -64,8 +64,9 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol, // On macOS, we can't query the default, but the handlers list seems to put // Apple's defaults first, so we'll use the first option that isn't our bundle CFStringRef other = nil; - for (CFIndex i = 0; i < CFArrayGetCount(bundleList); i++) { - other = (CFStringRef)CFArrayGetValueAtIndex(bundleList, i); + for (CFIndex i = 0; i < CFArrayGetCount(bundleList); ++i) { + other = base::mac::CFCast(CFArrayGetValueAtIndex(bundleList, + i)); if (![identifier isEqualToString: (__bridge NSString *)other]) { break; } @@ -152,7 +153,7 @@ bool Browser::ContinueUserActivity(const std::string& type, } Browser::LoginItemSettings Browser::GetLoginItemSettings( - LoginItemSettings options) { + const LoginItemSettings& options) { LoginItemSettings settings; settings.open_at_login = base::mac::CheckLoginItemStatus( &settings.open_as_hidden); @@ -179,7 +180,7 @@ std::string Browser::GetExecutableFileProductName() const { int Browser::DockBounce(BounceType type) { return [[AtomApplication sharedApplication] - requestUserAttention:(NSRequestUserAttentionType)type]; + requestUserAttention:static_cast(type)]; } void Browser::DockCancelBounce(int request_id) { @@ -203,9 +204,8 @@ std::string Browser::DockGetBadgeText() { } void Browser::DockHide() { - WindowList* list = WindowList::GetInstance(); - for (WindowList::iterator it = list->begin(); it != list->end(); ++it) - [(*it)->GetNativeWindow() setCanHide:NO]; + for (const auto& window : WindowList::GetWindows()) + [window->GetNativeWindow() setCanHide:NO]; ProcessSerialNumber psn = { 0, kCurrentProcess }; TransformProcessType(&psn, kProcessTransformToUIElementApplication); diff --git a/atom/browser/browser_win.cc b/atom/browser/browser_win.cc index 85990bbc67e3..ac0f713c8895 100644 --- a/atom/browser/browser_win.cc +++ b/atom/browser/browser_win.cc @@ -61,11 +61,11 @@ bool GetProtocolLaunchPath(mate::Arguments* args, base::string16* exe) { // Read in optional args arg std::vector launch_args; if (args->GetNext(&launch_args) && !launch_args.empty()) - *exe = base::StringPrintf(L"\"%s\" %s \"%%1\"", + *exe = base::StringPrintf(L"\"%ls\" %ls \"%%1\"", exe->c_str(), base::JoinString(launch_args, L" ").c_str()); else - *exe = base::StringPrintf(L"\"%s\" \"%%1\"", exe->c_str()); + *exe = base::StringPrintf(L"\"%ls\" \"%%1\"", exe->c_str()); return true; } @@ -76,8 +76,7 @@ bool FormatCommandLineString(base::string16* exe, } if (!launch_args.empty()) { - base::string16 formatString = L"%s %s"; - *exe = base::StringPrintf(formatString.c_str(), + *exe = base::StringPrintf(L"%ls %ls", exe->c_str(), base::JoinString(launch_args, L" ").c_str()); } @@ -287,7 +286,7 @@ void Browser::SetLoginItemSettings(LoginItemSettings settings) { } Browser::LoginItemSettings Browser::GetLoginItemSettings( - LoginItemSettings options) { + const LoginItemSettings& options) { LoginItemSettings settings; base::string16 keyPath = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; base::win::RegKey key(HKEY_CURRENT_USER, keyPath.c_str(), KEY_ALL_ACCESS); diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index 89c1e372558c..282fa92de69e 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -178,13 +178,23 @@ void CommonWebContentsDelegate::SetOwnerWindow(NativeWindow* owner_window) { void CommonWebContentsDelegate::SetOwnerWindow( content::WebContents* web_contents, NativeWindow* owner_window) { - owner_window_ = owner_window->GetWeakPtr(); + owner_window_ = owner_window ? owner_window->GetWeakPtr() : nullptr; NativeWindowRelay* relay = new NativeWindowRelay(owner_window_); - web_contents->SetUserData(relay->key, relay); + if (owner_window) { + web_contents->SetUserData(relay->key, relay); + } else { + web_contents->RemoveUserData(relay->key); + delete relay; + } } -void CommonWebContentsDelegate::DestroyWebContents() { - web_contents_.reset(); +void CommonWebContentsDelegate::ResetManagedWebContents(bool async) { + if (async) { + base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, + web_contents_.release()); + } else { + web_contents_.reset(); + } } content::WebContents* CommonWebContentsDelegate::GetWebContents() const { @@ -338,7 +348,7 @@ void CommonWebContentsDelegate::DevToolsRequestFileSystems() { } std::vector file_systems; - for (auto file_system_path : file_system_paths) { + for (const auto& file_system_path : file_system_paths) { base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); std::string file_system_id = RegisterFileSystem(GetDevToolsWebContents(), path); diff --git a/atom/browser/common_web_contents_delegate.h b/atom/browser/common_web_contents_delegate.h index c08f8d246cee..d1d26314966e 100644 --- a/atom/browser/common_web_contents_delegate.h +++ b/atom/browser/common_web_contents_delegate.h @@ -42,9 +42,6 @@ class CommonWebContentsDelegate void SetOwnerWindow(content::WebContents* web_contents, NativeWindow* owner_window); - // Destroy the managed InspectableWebContents object. - void DestroyWebContents(); - // Returns the WebContents managed by this delegate. content::WebContents* GetWebContents() const; @@ -114,6 +111,9 @@ class CommonWebContentsDelegate std::string* name, std::string* class_name) override; #endif + // Destroy the managed InspectableWebContents object. + void ResetManagedWebContents(bool async); + private: // Callback for when DevToolsSaveToFile has completed. void OnDevToolsSaveToFile(const std::string& url); diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index 9e245f99078d..4c6a938fba59 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -10,10 +10,6 @@ #include "base/strings/sys_string_conversions.h" #include "base/values.h" -@interface NSWindow (SierraSDK) -@property(class) BOOL allowsAutomaticWindowTabbing; -@end - @implementation AtomApplicationDelegate - (void)setApplicationDockMenu:(atom::AtomMenuModel*)model { @@ -25,10 +21,6 @@ // Don't add the "Enter Full Screen" menu item automatically. [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"]; - // Don't add the "Show Tab Bar" menu item. - if ([NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)]) - NSWindow.allowsAutomaticWindowTabbing = NO; - atom::Browser::Get()->WillFinishLaunching(); } diff --git a/atom/browser/native_browser_view.cc b/atom/browser/native_browser_view.cc new file mode 100644 index 000000000000..949e5e9ec96d --- /dev/null +++ b/atom/browser/native_browser_view.cc @@ -0,0 +1,18 @@ +// 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 "atom/browser/native_browser_view.h" + +#include "atom/browser/api/atom_api_web_contents.h" +#include "brightray/browser/inspectable_web_contents_view.h" + +namespace atom { + +NativeBrowserView::NativeBrowserView( + brightray::InspectableWebContentsView* web_contents_view) + : web_contents_view_(web_contents_view) {} + +NativeBrowserView::~NativeBrowserView() {} + +} // namespace atom diff --git a/atom/browser/native_browser_view.h b/atom/browser/native_browser_view.h new file mode 100644 index 000000000000..4216cc1e3439 --- /dev/null +++ b/atom/browser/native_browser_view.h @@ -0,0 +1,57 @@ +// 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_NATIVE_BROWSER_VIEW_H_ +#define ATOM_BROWSER_NATIVE_BROWSER_VIEW_H_ + +#include "base/macros.h" +#include "third_party/skia/include/core/SkColor.h" + +namespace brightray { +class InspectableWebContentsView; +} + +namespace gfx { +class Rect; +} + +namespace atom { + +namespace api { +class WebContents; +} + +enum AutoResizeFlags { + kAutoResizeWidth = 0x1, + kAutoResizeHeight = 0x2, +}; + +class NativeBrowserView { + public: + virtual ~NativeBrowserView(); + + static NativeBrowserView* Create( + brightray::InspectableWebContentsView* web_contents_view); + + brightray::InspectableWebContentsView* GetInspectableWebContentsView() { + return web_contents_view_; + } + + virtual void SetAutoResizeFlags(uint8_t flags) = 0; + virtual void SetBounds(const gfx::Rect& bounds) = 0; + virtual void SetBackgroundColor(SkColor color) = 0; + + protected: + explicit NativeBrowserView( + brightray::InspectableWebContentsView* web_contents_view); + + brightray::InspectableWebContentsView* web_contents_view_; + + private: + DISALLOW_COPY_AND_ASSIGN(NativeBrowserView); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NATIVE_BROWSER_VIEW_H_ diff --git a/atom/browser/native_browser_view_mac.h b/atom/browser/native_browser_view_mac.h new file mode 100644 index 000000000000..4e7aa429ce27 --- /dev/null +++ b/atom/browser/native_browser_view_mac.h @@ -0,0 +1,30 @@ +// 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_NATIVE_BROWSER_VIEW_MAC_H_ +#define ATOM_BROWSER_NATIVE_BROWSER_VIEW_MAC_H_ + +#import + +#include "atom/browser/native_browser_view.h" + +namespace atom { + +class NativeBrowserViewMac : public NativeBrowserView { + public: + explicit NativeBrowserViewMac( + brightray::InspectableWebContentsView* web_contents_view); + ~NativeBrowserViewMac() override; + + void SetAutoResizeFlags(uint8_t flags) override; + void SetBounds(const gfx::Rect& bounds) override; + void SetBackgroundColor(SkColor color) override; + + private: + DISALLOW_COPY_AND_ASSIGN(NativeBrowserViewMac); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NATIVE_BROWSER_VIEW_MAC_H_ diff --git a/atom/browser/native_browser_view_mac.mm b/atom/browser/native_browser_view_mac.mm new file mode 100644 index 000000000000..2ce2adc1f4fe --- /dev/null +++ b/atom/browser/native_browser_view_mac.mm @@ -0,0 +1,60 @@ +// 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 "atom/browser/native_browser_view_mac.h" + +#include "brightray/browser/inspectable_web_contents_view.h" +#include "skia/ext/skia_utils_mac.h" +#include "ui/gfx/geometry/rect.h" + +// Match view::Views behavior where the view sticks to the top-left origin. +const NSAutoresizingMaskOptions kDefaultAutoResizingMask = + NSViewMaxXMargin | NSViewMinYMargin; + +namespace atom { + +NativeBrowserViewMac::NativeBrowserViewMac( + brightray::InspectableWebContentsView* web_contents_view) + : NativeBrowserView(web_contents_view) { + auto* view = GetInspectableWebContentsView()->GetNativeView(); + view.autoresizingMask = kDefaultAutoResizingMask; +} + +NativeBrowserViewMac::~NativeBrowserViewMac() {} + +void NativeBrowserViewMac::SetAutoResizeFlags(uint8_t flags) { + NSAutoresizingMaskOptions autoresizing_mask = kDefaultAutoResizingMask; + if (flags & kAutoResizeWidth) { + autoresizing_mask |= NSViewWidthSizable; + } + if (flags & kAutoResizeHeight) { + autoresizing_mask |= NSViewHeightSizable; + } + + auto* view = GetInspectableWebContentsView()->GetNativeView(); + view.autoresizingMask = autoresizing_mask; +} + +void NativeBrowserViewMac::SetBounds(const gfx::Rect& bounds) { + auto* view = GetInspectableWebContentsView()->GetNativeView(); + auto* superview = view.superview; + const auto superview_height = superview ? superview.frame.size.height : 0; + view.frame = + NSMakeRect(bounds.x(), superview_height - bounds.y() - bounds.height(), + bounds.width(), bounds.height()); +} + +void NativeBrowserViewMac::SetBackgroundColor(SkColor color) { + auto* view = GetInspectableWebContentsView()->GetNativeView(); + view.wantsLayer = YES; + view.layer.backgroundColor = skia::CGColorCreateFromSkColor(color); +} + +// static +NativeBrowserView* NativeBrowserView::Create( + brightray::InspectableWebContentsView* web_contents_view) { + return new NativeBrowserViewMac(web_contents_view); +} + +} // namespace atom diff --git a/atom/browser/native_browser_view_views.cc b/atom/browser/native_browser_view_views.cc new file mode 100644 index 000000000000..08a8123bcaef --- /dev/null +++ b/atom/browser/native_browser_view_views.cc @@ -0,0 +1,36 @@ +// 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 "atom/browser/native_browser_view_views.h" + +#include "brightray/browser/inspectable_web_contents_view.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/views/background.h" +#include "ui/views/view.h" + +namespace atom { + +NativeBrowserViewViews::NativeBrowserViewViews( + brightray::InspectableWebContentsView* web_contents_view) + : NativeBrowserView(web_contents_view) {} + +NativeBrowserViewViews::~NativeBrowserViewViews() {} + +void NativeBrowserViewViews::SetBounds(const gfx::Rect& bounds) { + auto* view = GetInspectableWebContentsView()->GetView(); + view->SetBoundsRect(bounds); +} + +void NativeBrowserViewViews::SetBackgroundColor(SkColor color) { + auto* view = GetInspectableWebContentsView()->GetView(); + view->set_background(views::Background::CreateSolidBackground(color)); +} + +// static +NativeBrowserView* NativeBrowserView::Create( + brightray::InspectableWebContentsView* web_contents_view) { + return new NativeBrowserViewViews(web_contents_view); +} + +} // namespace atom diff --git a/atom/browser/native_browser_view_views.h b/atom/browser/native_browser_view_views.h new file mode 100644 index 000000000000..5dcda13447cd --- /dev/null +++ b/atom/browser/native_browser_view_views.h @@ -0,0 +1,33 @@ +// 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_NATIVE_BROWSER_VIEW_VIEWS_H_ +#define ATOM_BROWSER_NATIVE_BROWSER_VIEW_VIEWS_H_ + +#include "atom/browser/native_browser_view.h" + +namespace atom { + +class NativeBrowserViewViews : public NativeBrowserView { + public: + explicit NativeBrowserViewViews( + brightray::InspectableWebContentsView* web_contents_view); + ~NativeBrowserViewViews() override; + + uint8_t GetAutoResizeFlags() { return auto_resize_flags_; } + void SetAutoResizeFlags(uint8_t flags) override { + auto_resize_flags_ = flags; + } + void SetBounds(const gfx::Rect& bounds) override; + void SetBackgroundColor(SkColor color) override; + + private: + uint8_t auto_resize_flags_; + + DISALLOW_COPY_AND_ASSIGN(NativeBrowserViewViews); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NATIVE_BROWSER_VIEW_VIEWS_H_ diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 90a823f80b65..9e2c11aec4ab 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -104,8 +104,7 @@ NativeWindow::~NativeWindow() { // static NativeWindow* NativeWindow::FromWebContents( content::WebContents* web_contents) { - WindowList& window_list = *WindowList::GetInstance(); - for (NativeWindow* window : window_list) { + for (const auto& window : WindowList::GetWindows()) { if (window->web_contents() == web_contents) return window; } @@ -347,6 +346,10 @@ void NativeWindow::SetTouchBar( void NativeWindow::RefreshTouchBarItem(const std::string& item_id) { } +void NativeWindow::SetEscapeTouchBarItem( + const mate::PersistentDictionary& item) { +} + void NativeWindow::FocusOnWebView() { web_contents()->GetRenderViewHost()->GetWidget()->Focus(); } @@ -471,6 +474,11 @@ void NativeWindow::NotifyWindowClosed() { observer.OnWindowClosed(); } +void NativeWindow::NotifyWindowEndSession() { + for (NativeWindowObserver& observer : observers_) + observer.OnWindowEndSession(); +} + void NativeWindow::NotifyWindowBlur() { for (NativeWindowObserver& observer : observers_) observer.OnWindowBlur(); @@ -551,6 +559,16 @@ void NativeWindow::NotifyWindowSwipe(const std::string& direction) { observer.OnWindowSwipe(direction); } +void NativeWindow::NotifyWindowSheetBegin() { + for (NativeWindowObserver& observer : observers_) + observer.OnWindowSheetBegin(); +} + +void NativeWindow::NotifyWindowSheetEnd() { + for (NativeWindowObserver& observer : observers_) + observer.OnWindowSheetEnd(); +} + void NativeWindow::NotifyWindowLeaveFullScreen() { for (NativeWindowObserver& observer : observers_) observer.OnWindowLeaveFullScreen(); diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 941f5849a65c..d3f18d8fb95c 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -47,6 +47,8 @@ class Dictionary; namespace atom { +class NativeBrowserView; + struct DraggableRegion; class NativeWindow : public base::SupportsUserData, @@ -144,6 +146,7 @@ class NativeWindow : public base::SupportsUserData, virtual void SetFocusable(bool focusable); virtual void SetMenu(AtomMenuModel* menu); virtual void SetParentWindow(NativeWindow* parent); + virtual void SetBrowserView(NativeBrowserView* browser_view) = 0; virtual gfx::NativeWindow GetNativeWindow() = 0; virtual gfx::AcceleratedWidget GetAcceleratedWidget() = 0; @@ -174,6 +177,7 @@ class NativeWindow : public base::SupportsUserData, virtual void SetTouchBar( const std::vector& items); virtual void RefreshTouchBarItem(const std::string& item_id); + virtual void SetEscapeTouchBarItem(const mate::PersistentDictionary& item); // Webview APIs. virtual void FocusOnWebView(); @@ -214,6 +218,7 @@ class NativeWindow : public base::SupportsUserData, // Public API used by platform-dependent delegates and observers to send UI // related notifications. void NotifyWindowClosed(); + void NotifyWindowEndSession(); void NotifyWindowBlur(); void NotifyWindowFocus(); void NotifyWindowShow(); @@ -229,6 +234,8 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowScrollTouchEnd(); void NotifyWindowScrollTouchEdge(); void NotifyWindowSwipe(const std::string& direction); + void NotifyWindowSheetBegin(); + void NotifyWindowSheetEnd(); void NotifyWindowEnterFullScreen(); void NotifyWindowLeaveFullScreen(); void NotifyWindowEnterHtmlFullScreen(); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index bd34993fb119..af0f157ecaef 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -87,6 +87,7 @@ class NativeWindowMac : public NativeWindow, bool IsDocumentEdited() override; void SetIgnoreMouseEvents(bool ignore) override; void SetContentProtection(bool enable) override; + void SetBrowserView(NativeBrowserView* browser_view) override; void SetParentWindow(NativeWindow* parent) override; gfx::NativeWindow GetNativeWindow() override; gfx::AcceleratedWidget GetAcceleratedWidget() override; @@ -103,6 +104,7 @@ class NativeWindowMac : public NativeWindow, void SetTouchBar( const std::vector& items) override; void RefreshTouchBarItem(const std::string& item_id) override; + void SetEscapeTouchBarItem(const mate::PersistentDictionary& item) override; // content::RenderWidgetHost::InputEventObserver: void OnInputEvent(const blink::WebInputEvent& event) override; @@ -163,6 +165,8 @@ class NativeWindowMac : public NativeWindow, // The view that will fill the whole frameless window. base::scoped_nsobject content_view_; + NativeBrowserView* browser_view_; + std::vector draggable_regions_; bool is_kiosk_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index c88469a9c8a1..9dc119e239dd 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -7,6 +7,7 @@ #include #include +#include "atom/browser/native_browser_view_mac.h" #include "atom/browser/ui/cocoa/atom_touch_bar.h" #include "atom/browser/window_list.h" #include "atom/common/color_util.h" @@ -19,9 +20,9 @@ #include "brightray/browser/inspectable_web_contents_view.h" #include "brightray/browser/mac/event_dispatching_window.h" #include "content/public/browser/browser_accessibility_state.h" -#include "content/public/browser/web_contents.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" #include "native_mate/dictionary.h" #include "skia/ext/skia_utils_mac.h" #include "third_party/skia/include/core/SkRegion.h" @@ -312,6 +313,14 @@ bool ScopedDisableResize::disable_resize_ = false; return rect; } +- (void)windowWillBeginSheet:(NSNotification *)notification { + shell_->NotifyWindowSheetBegin(); +} + +- (void)windowDidEndSheet:(NSNotification *)notification { + shell_->NotifyWindowSheetEnd(); +} + @end @interface AtomPreviewItem : NSObject @@ -336,6 +345,19 @@ bool ScopedDisableResize::disable_resize_ = false; @end +#if !defined(MAC_OS_X_VERSION_10_12) + +enum { + NSWindowTabbingModeDisallowed = 2 +}; + +@interface NSWindow (SierraSDK) +- (void)setTabbingMode:(NSInteger)mode; +- (void)setTabbingIdentifier:(NSString*)identifier; +@end + +#endif // MAC_OS_X_VERSION_10_12 + @interface AtomNSWindow : EventDispatchingWindow { @private atom::NativeWindowMac* shell_; @@ -355,6 +377,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)enableWindowButtonsOffset; - (void)resetTouchBar:(const std::vector&)settings; - (void)refreshTouchBarItem:(const std::string&)item_id; +- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item; @end @@ -397,6 +420,11 @@ bool ScopedDisableResize::disable_resize_ = false; return nil; } +- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item { + if (atom_touch_bar_ && self.touchBar) + [atom_touch_bar_ setEscapeTouchBarItem:item forTouchBar:self.touchBar]; +} + // NSWindow overrides. - (void)swipeWithEvent:(NSEvent *)event { @@ -652,6 +680,7 @@ NativeWindowMac::NativeWindowMac( const mate::Dictionary& options, NativeWindow* parent) : NativeWindow(web_contents, options, parent), + browser_view_(nullptr), is_kiosk_(false), was_fullscreen_(false), zoom_to_page_width_(false), @@ -682,6 +711,9 @@ NativeWindowMac::NativeWindowMac( options.Get(options::kTitleBarStyle, &title_bar_style_); + std::string tabbingIdentifier; + options.Get(options::kTabbingIdentifier, &tabbingIdentifier); + std::string windowType; options.Get(options::kType, &windowType); @@ -754,6 +786,18 @@ NativeWindowMac::NativeWindowMac( [window_ setOpaque:NO]; } + // Create a tab only if tabbing identifier is specified and window has + // a native title bar. + if (tabbingIdentifier.empty() || transparent() || !has_frame()) { + if ([window_ respondsToSelector:@selector(tabbingMode)]) { + [window_ setTabbingMode:NSWindowTabbingModeDisallowed]; + } + } else { + if ([window_ respondsToSelector:@selector(tabbingIdentifier)]) { + [window_ setTabbingIdentifier:base::SysUTF8ToNSString(tabbingIdentifier)]; + } + } + // We will manage window's lifetime ourselves. [window_ setReleasedWhenClosed:NO]; @@ -1235,6 +1279,26 @@ void NativeWindowMac::SetContentProtection(bool enable) { : NSWindowSharingReadOnly]; } +void NativeWindowMac::SetBrowserView(NativeBrowserView* browser_view) { + if (browser_view_) { + [browser_view_->GetInspectableWebContentsView()->GetNativeView() + removeFromSuperview]; + browser_view_ = nullptr; + } + + if (!browser_view) { + return; + } + + browser_view_ = browser_view; + auto* native_view = + browser_view->GetInspectableWebContentsView()->GetNativeView(); + [[window_ contentView] addSubview:native_view + positioned:NSWindowAbove + relativeTo:nil]; + native_view.hidden = NO; +} + void NativeWindowMac::SetParentWindow(NativeWindow* parent) { if (is_modal()) return; @@ -1262,7 +1326,7 @@ void NativeWindowMac::SetProgressBar(double progress, const NativeWindow::Progre NSDockTile* dock_tile = [NSApp dockTile]; // For the first time API invoked, we need to create a ContentView in DockTile. - if (dock_tile.contentView == NULL) { + if (dock_tile.contentView == nullptr) { NSImageView* image_view = [[NSImageView alloc] init]; [image_view setImage:[NSApp applicationIconImage]]; [dock_tile setContentView:image_view]; @@ -1358,22 +1422,22 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { // they are available in the minimum SDK version if (type == "selection") { // NSVisualEffectMaterialSelection - vibrancyType = (NSVisualEffectMaterial) 4; + vibrancyType = static_cast(4); } else if (type == "menu") { // NSVisualEffectMaterialMenu - vibrancyType = (NSVisualEffectMaterial) 5; + vibrancyType = static_cast(5); } else if (type == "popover") { // NSVisualEffectMaterialPopover - vibrancyType = (NSVisualEffectMaterial) 6; + vibrancyType = static_cast(6); } else if (type == "sidebar") { // NSVisualEffectMaterialSidebar - vibrancyType = (NSVisualEffectMaterial) 7; + vibrancyType = static_cast(7); } else if (type == "medium-light") { // NSVisualEffectMaterialMediumLight - vibrancyType = (NSVisualEffectMaterial) 8; + vibrancyType = static_cast(8); } else if (type == "ultra-dark") { // NSVisualEffectMaterialUltraDark - vibrancyType = (NSVisualEffectMaterial) 9; + vibrancyType = static_cast(9); } } @@ -1389,6 +1453,10 @@ void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) { [window_ refreshTouchBarItem:item_id]; } +void NativeWindowMac::SetEscapeTouchBarItem(const mate::PersistentDictionary& item) { + [window_ setEscapeTouchBarItem:item]; +} + void NativeWindowMac::OnInputEvent(const blink::WebInputEvent& event) { switch (event.type) { case blink::WebInputEvent::GestureScrollBegin: diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index 3b8d86e6fb0f..8c908dc8237a 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -40,6 +40,9 @@ class NativeWindowObserver { // Called when the window is closed. virtual void OnWindowClosed() {} + // Called when Windows sends WM_ENDSESSION message + virtual void OnWindowEndSession() {} + // Called when window loses focus. virtual void OnWindowBlur() {} @@ -67,6 +70,8 @@ class NativeWindowObserver { virtual void OnWindowScrollTouchEnd() {} virtual void OnWindowScrollTouchEdge() {} virtual void OnWindowSwipe(const std::string& direction) {} + virtual void OnWindowSheetBegin() {} + virtual void OnWindowSheetEnd() {} virtual void OnWindowEnterFullScreen() {} virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index b0722cbc4afd..7e6f23947c29 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -7,8 +7,8 @@ #include #include +#include "atom/browser/native_browser_view_views.h" #include "atom/browser/ui/views/menu_bar.h" -#include "atom/browser/ui/views/menu_layout.h" #include "atom/browser/window_list.h" #include "atom/common/color_util.h" #include "atom/common/draggable_region.h" @@ -135,6 +135,7 @@ NativeWindowViews::NativeWindowViews( : NativeWindow(web_contents, options, parent), window_(new views::Widget), web_view_(inspectable_web_contents()->GetView()->GetView()), + browser_view_(nullptr), menu_bar_autohide_(false), menu_bar_visible_(false), menu_bar_alt_pressed_(false), @@ -274,9 +275,6 @@ NativeWindowViews::NativeWindowViews( SetWindowType(GetAcceleratedWidget(), window_type); #endif - // Add web view. - SetLayoutManager(new MenuLayout(this, kMenuBarHeight)); - AddChildView(web_view_); #if defined(OS_WIN) @@ -881,6 +879,24 @@ void NativeWindowViews::SetMenu(AtomMenuModel* menu_model) { Layout(); } +void NativeWindowViews::SetBrowserView(NativeBrowserView* browser_view) { + if (browser_view_) { + web_view_->RemoveChildView( + browser_view_->GetInspectableWebContentsView()->GetView()); + browser_view_ = nullptr; + } + + if (!browser_view) { + return; + } + + // Add as child of the main web view to avoid (0, 0) origin from overlapping + // with menu bar. + browser_view_ = browser_view; + web_view_->AddChildView( + browser_view->GetInspectableWebContentsView()->GetView()); +} + void NativeWindowViews::SetParentWindow(NativeWindow* parent) { NativeWindow::SetParentWindow(parent); @@ -1248,6 +1264,43 @@ void NativeWindowViews::HandleKeyboardEvent( } } +void NativeWindowViews::Layout() { + const auto size = GetContentsBounds().size(); + const auto menu_bar_bounds = + menu_bar_visible_ ? gfx::Rect(0, 0, size.width(), kMenuBarHeight) + : gfx::Rect(); + if (menu_bar_) { + menu_bar_->SetBoundsRect(menu_bar_bounds); + } + + const auto old_web_view_size = web_view_ ? web_view_->size() : gfx::Size(); + if (web_view_) { + web_view_->SetBoundsRect( + gfx::Rect(0, menu_bar_bounds.height(), size.width(), + size.height() - menu_bar_bounds.height())); + } + const auto new_web_view_size = web_view_ ? web_view_->size() : gfx::Size(); + + if (browser_view_) { + const auto flags = static_cast(browser_view_) + ->GetAutoResizeFlags(); + int width_delta = 0; + int height_delta = 0; + if (flags & kAutoResizeWidth) { + width_delta = new_web_view_size.width() - old_web_view_size.width(); + } + if (flags & kAutoResizeHeight) { + height_delta = new_web_view_size.height() - old_web_view_size.height(); + } + + auto* view = browser_view_->GetInspectableWebContentsView()->GetView(); + auto new_view_size = view->size(); + new_view_size.set_width(new_view_size.width() + width_delta); + new_view_size.set_height(new_view_size.height() + height_delta); + view->SetSize(new_view_size); + } +} + gfx::Size NativeWindowViews::GetMinimumSize() { return NativeWindow::GetMinimumSize(); } diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index a7f02fb2727d..276cd4adde2d 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -104,6 +104,7 @@ class NativeWindowViews : public NativeWindow, void SetContentProtection(bool enable) override; void SetFocusable(bool focusable) override; void SetMenu(AtomMenuModel* menu_model) override; + void SetBrowserView(NativeBrowserView* browser_view) override; void SetParentWindow(NativeWindow* parent) override; gfx::NativeWindow GetNativeWindow() override; void SetOverlayIcon(const gfx::Image& overlay, @@ -176,6 +177,7 @@ class NativeWindowViews : public NativeWindow, const content::NativeWebKeyboardEvent& event) override; // views::View: + void Layout() override; gfx::Size GetMinimumSize() override; gfx::Size GetMaximumSize() override; bool AcceleratorPressed(const ui::Accelerator& accelerator) override; @@ -189,6 +191,8 @@ class NativeWindowViews : public NativeWindow, std::unique_ptr window_; views::View* web_view_; // Managed by inspectable_web_contents_. + NativeBrowserView* browser_view_; + std::unique_ptr menu_bar_; bool menu_bar_autohide_; bool menu_bar_visible_; diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index 1b523e90b804..abda0d0b0262 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -147,6 +147,11 @@ bool NativeWindowViews::PreHandleMSG( } return false; } + case WM_ENDSESSION: { + if (w_param) { + NotifyWindowEndSession(); + } + } default: return false; } diff --git a/atom/browser/net/atom_cert_verifier.cc b/atom/browser/net/atom_cert_verifier.cc index 5dee107eb36e..2a2229f19d1f 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -50,7 +50,7 @@ class CertVerifierRequest : public AtomCertVerifier::Request { first_response_(true), weak_ptr_factory_(this) {} - ~CertVerifierRequest() { + ~CertVerifierRequest() override { cert_verifier_->RemoveRequest(params_); default_verifier_request_.reset(); while (!response_list_.empty() && !first_response_) { diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 2ba3f8e35781..3a5b667f06d0 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -402,7 +402,7 @@ void AtomNetworkDelegate::OnListenerResultInIO( if (!base::ContainsKey(callbacks_, id)) return; - ReadFromResponseObject(*response.get(), out); + ReadFromResponseObject(*response, out); bool cancel = false; response->GetBoolean("cancel", &cancel); diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 2c7bb61da0b1..9400f361f11a 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -13,6 +13,7 @@ #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/upload_bytes_element_reader.h" +#include "net/url_request/redirect_info.h" namespace { const int kBufferSize = 4096; @@ -58,6 +59,7 @@ scoped_refptr AtomURLRequest::Create( AtomBrowserContext* browser_context, const std::string& method, const std::string& url, + const std::string& redirect_policy, api::URLRequest* delegate) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -76,7 +78,7 @@ scoped_refptr AtomURLRequest::Create( if (content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::DoInitialize, atom_url_request, - request_context_getter, method, url))) { + request_context_getter, method, url, redirect_policy))) { return atom_url_request; } return nullptr; @@ -93,10 +95,12 @@ void AtomURLRequest::Terminate() { void AtomURLRequest::DoInitialize( scoped_refptr request_context_getter, const std::string& method, - const std::string& url) { + const std::string& url, + const std::string& redirect_policy) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK(request_context_getter); + redirect_policy_ = redirect_policy; request_context_getter_ = request_context_getter; request_context_getter_->AddObserver(this); auto context = request_context_getter_->GetURLRequestContext(); @@ -150,6 +154,13 @@ void AtomURLRequest::Cancel() { base::Bind(&AtomURLRequest::DoCancel, this)); } +void AtomURLRequest::FollowRedirect() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoFollowRedirect, this)); +} + void AtomURLRequest::SetExtraHeader(const std::string& name, const std::string& value) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -246,6 +257,13 @@ void AtomURLRequest::DoCancel() { DoTerminate(); } +void AtomURLRequest::DoFollowRedirect() { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (request_ && request_->is_redirecting() && redirect_policy_ == "manual") { + request_->FollowDeferredRedirect(); + } +} + void AtomURLRequest::DoSetExtraHeader(const std::string& name, const std::string& value) const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); @@ -297,6 +315,29 @@ void AtomURLRequest::DoSetLoadFlags(int flags) const { request_->SetLoadFlags(request_->load_flags() | flags); } +void AtomURLRequest::OnReceivedRedirect(net::URLRequest* request, + const net::RedirectInfo& info, + bool* defer_redirect) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (!request_ || redirect_policy_ == "follow") + return; + + if (redirect_policy_ == "error") { + request->Cancel(); + DoCancelWithError( + "Request cannot follow redirect with the current redirect mode", true); + } else if (redirect_policy_ == "manual") { + *defer_redirect = true; + scoped_refptr response_headers = + request->response_headers(); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateReceivedRedirect, this, + info.status_code, info.new_method, info.new_url, + response_headers)); + } +} + void AtomURLRequest::OnAuthRequired(net::URLRequest* request, net::AuthChallengeInfo* auth_info) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); @@ -348,6 +389,14 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { DCHECK_EQ(request, request_.get()); const auto status = request_->status(); + if (status.error() == bytes_read && + bytes_read == net::ERR_CONTENT_DECODING_INIT_FAILED) { + // When the request job is unable to create a source stream for the + // content encoding, we fail the request. + DoCancelWithError(net::ErrorToString(net::ERR_CONTENT_DECODING_INIT_FAILED), + true); + return; + } bool response_error = false; bool data_ended = false; @@ -399,6 +448,16 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { buffer_copy)); } +void AtomURLRequest::InformDelegateReceivedRedirect( + int status_code, + const std::string& method, + const GURL& url, + scoped_refptr response_headers) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (delegate_) + delegate_->OnReceivedRedirect(status_code, method, url, response_headers); +} + void AtomURLRequest::InformDelegateAuthenticationRequired( scoped_refptr auth_info) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index db00390b955c..654798d8aac8 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -30,12 +30,14 @@ class AtomURLRequest : public base::RefCountedThreadSafe, AtomBrowserContext* browser_context, const std::string& method, const std::string& url, + const std::string& redirect_policy, api::URLRequest* delegate); void Terminate(); bool Write(scoped_refptr buffer, bool is_last); void SetChunkedUpload(bool is_chunked_upload); void Cancel(); + void FollowRedirect(); void SetExtraHeader(const std::string& name, const std::string& value) const; void RemoveExtraHeader(const std::string& name) const; void PassLoginInformation(const base::string16& username, @@ -44,6 +46,9 @@ class AtomURLRequest : public base::RefCountedThreadSafe, protected: // Overrides of net::URLRequest::Delegate + void OnReceivedRedirect(net::URLRequest* request, + const net::RedirectInfo& info, + bool* defer_redirect) override; void OnAuthRequired(net::URLRequest* request, net::AuthChallengeInfo* auth_info) override; void OnResponseStarted(net::URLRequest* request) override; @@ -60,11 +65,13 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void DoInitialize(scoped_refptr, const std::string& method, - const std::string& url); + const std::string& url, + const std::string& redirect_policy); void DoTerminate(); void DoWriteBuffer(scoped_refptr buffer, bool is_last); void DoCancel(); + void DoFollowRedirect(); void DoSetExtraHeader(const std::string& name, const std::string& value) const; void DoRemoveExtraHeader(const std::string& name) const; @@ -77,6 +84,11 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void ReadResponse(); bool CopyAndPostBuffer(int bytes_read); + void InformDelegateReceivedRedirect( + int status_code, + const std::string& method, + const GURL& url, + scoped_refptr response_headers) const; void InformDelegateAuthenticationRequired( scoped_refptr auth_info) const; void InformDelegateResponseStarted( @@ -92,6 +104,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, scoped_refptr request_context_getter_; bool is_chunked_upload_; + std::string redirect_policy_; std::unique_ptr chunked_stream_; std::unique_ptr chunked_stream_writer_; std::vector> diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index e95369fba190..9fdeb6099e6a 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -164,7 +164,7 @@ void NodeDebugger::DidRead(net::test_server::StreamListenSocket* socket, buffer_.append(data, len); do { - if (buffer_.size() == 0) + if (buffer_.empty()) return; // Read the "Content-Length" header. diff --git a/atom/browser/osr/osr_render_widget_host_view.cc b/atom/browser/osr/osr_render_widget_host_view.cc index 7355f1a0c1e2..2003118f389e 100644 --- a/atom/browser/osr/osr_render_widget_host_view.cc +++ b/atom/browser/osr/osr_render_widget_host_view.cc @@ -851,12 +851,12 @@ void OffScreenRenderWidgetHostView::SetupFrameRate(bool force) { GetCompositor()->vsync_manager()->SetAuthoritativeVSyncInterval( base::TimeDelta::FromMilliseconds(frame_rate_threshold_ms_)); - if (copy_frame_generator_.get()) { + if (copy_frame_generator_) { copy_frame_generator_->set_frame_rate_threshold_ms( frame_rate_threshold_ms_); } - if (begin_frame_timer_.get()) { + if (begin_frame_timer_) { begin_frame_timer_->SetFrameRateThresholdMs(frame_rate_threshold_ms_); } else { begin_frame_timer_.reset(new AtomBeginFrameTimer( @@ -871,7 +871,7 @@ void OffScreenRenderWidgetHostView::Invalidate() { if (software_output_device_) { software_output_device_->OnPaint(bounds_in_pixels); - } else if (copy_frame_generator_.get()) { + } else if (copy_frame_generator_) { copy_frame_generator_->GenerateCopyFrame(true, bounds_in_pixels); } } diff --git a/atom/browser/osr/osr_render_widget_host_view_mac.mm b/atom/browser/osr/osr_render_widget_host_view_mac.mm index 664261947d1d..7cf010ff8e28 100644 --- a/atom/browser/osr/osr_render_widget_host_view_mac.mm +++ b/atom/browser/osr/osr_render_widget_host_view_mac.mm @@ -145,4 +145,4 @@ OffScreenRenderWidgetHostView::GetDelegatedFrameHost() const { return browser_compositor_->GetDelegatedFrameHost(); } -} // namespace +} // namespace atom diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 091614ffd428..ecd6a28b5cdf 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.6.4 + 1.6.7 CFBundleShortVersionString - 1.6.4 + 1.6.7 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.manifest b/atom/browser/resources/win/atom.manifest index 64c07ded17b0..7608ffb20f6c 100644 --- a/atom/browser/resources/win/atom.manifest +++ b/atom/browser/resources/win/atom.manifest @@ -32,7 +32,7 @@ - true + true/pm true diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 5a2d8ea32ece..f7a9b32351b7 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -56,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,6,4,0 - PRODUCTVERSION 1,6,4,0 + FILEVERSION 1,6,7,0 + PRODUCTVERSION 1,6,7,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.6.4" + VALUE "FileVersion", "1.6.7" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "1.6.4" + VALUE "ProductVersion", "1.6.7" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/ui/certificate_trust.h b/atom/browser/ui/certificate_trust.h new file mode 100644 index 000000000000..7cbf31ea41fb --- /dev/null +++ b/atom/browser/ui/certificate_trust.h @@ -0,0 +1,29 @@ +// 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_UI_CERTIFICATE_TRUST_H_ +#define ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_ + +#include + +#include "base/callback_forward.h" +#include "base/memory/ref_counted.h" +#include "net/cert/x509_certificate.h" + +namespace atom { +class NativeWindow; +} // namespace atom + +namespace certificate_trust { + +typedef base::Callback ShowTrustCallback; + +void ShowCertificateTrust(atom::NativeWindow* parent_window, + const scoped_refptr& cert, + const std::string& message, + const ShowTrustCallback& callback); + +} // namespace certificate_trust + +#endif // ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_ diff --git a/atom/browser/ui/certificate_trust_mac.mm b/atom/browser/ui/certificate_trust_mac.mm new file mode 100644 index 000000000000..e0888dd3ea24 --- /dev/null +++ b/atom/browser/ui/certificate_trust_mac.mm @@ -0,0 +1,112 @@ +// 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 "atom/browser/ui/certificate_trust.h" + +#import +#import + +#include "atom/browser/native_window.h" +#include "base/strings/sys_string_conversions.h" +#include "net/cert/cert_database.h" + +@interface TrustDelegate : NSObject { + @private + certificate_trust::ShowTrustCallback callback_; + SFCertificateTrustPanel* panel_; + scoped_refptr cert_; + SecTrustRef trust_; + CFArrayRef cert_chain_; + SecPolicyRef sec_policy_; +} + +- (id)initWithCallback:(const certificate_trust::ShowTrustCallback&)callback + panel:(SFCertificateTrustPanel*)panel + cert:(const scoped_refptr&)cert + trust:(SecTrustRef)trust + certChain:(CFArrayRef)certChain + secPolicy:(SecPolicyRef)secPolicy; + +- (void)panelDidEnd:(NSWindow*)sheet + returnCode:(int)returnCode + contextInfo:(void*)contextInfo; + +@end + +@implementation TrustDelegate + +- (void)dealloc { + [panel_ release]; + CFRelease(trust_); + CFRelease(cert_chain_); + CFRelease(sec_policy_); + + [super dealloc]; +} + +- (id)initWithCallback:(const certificate_trust::ShowTrustCallback&)callback + panel:(SFCertificateTrustPanel*)panel + cert:(const scoped_refptr&)cert + trust:(SecTrustRef)trust + certChain:(CFArrayRef)certChain + secPolicy:(SecPolicyRef)secPolicy { + if ((self = [super init])) { + callback_ = callback; + panel_ = panel; + cert_ = cert; + trust_ = trust; + cert_chain_ = certChain; + sec_policy_ = secPolicy; + } + + return self; +} + +- (void)panelDidEnd:(NSWindow*)sheet + returnCode:(int)returnCode + contextInfo:(void*)contextInfo { + auto cert_db = net::CertDatabase::GetInstance(); + // This forces Chromium to reload the certificate since it might be trusted + // now. + cert_db->NotifyObserversCertDBChanged(cert_.get()); + + callback_.Run(); + + [self autorelease]; +} + +@end + +namespace certificate_trust { + +void ShowCertificateTrust(atom::NativeWindow* parent_window, + const scoped_refptr& cert, + const std::string& message, + const ShowTrustCallback& callback) { + auto sec_policy = SecPolicyCreateBasicX509(); + auto cert_chain = cert->CreateOSCertChainForCert(); + SecTrustRef trust = nullptr; + SecTrustCreateWithCertificates(cert_chain, sec_policy, &trust); + + NSWindow* window = parent_window ? + parent_window->GetNativeWindow() : + nil; + auto msg = base::SysUTF8ToNSString(message); + + auto panel = [[SFCertificateTrustPanel alloc] init]; + auto delegate = [[TrustDelegate alloc] initWithCallback:callback + panel:panel + cert:cert + trust:trust + certChain:cert_chain + secPolicy:sec_policy]; + [panel beginSheetForWindow:window + modalDelegate:delegate + didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:) + contextInfo:nil + trust:trust + message:msg]; +} + +} // namespace certificate_trust diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index b3e293153f96..d0bbf6a153a3 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -70,7 +70,7 @@ Role kRolesMap[] = { // while its context menu is still open. [self cancel]; - model_ = NULL; + model_ = nullptr; [super dealloc]; } diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h index 29cdf79b2558..26662cf48502 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.h +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -31,6 +31,8 @@ - (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items; - (NSMutableArray*)identifiersFromSettings:(const std::vector&)settings; - (void)refreshTouchBarItem:(NSTouchBar*)touchBar id:(const std::string&)item_id; +- (void)addNonDefaultTouchBarItems:(const std::vector&)items; +- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item forTouchBar:(NSTouchBar*)touchBar; - (NSString*)idFromIdentifier:(NSString*)identifier withPrefix:(NSString*)prefix; diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm index 4952e28876c6..a21fbc5497a3 100644 --- a/atom/browser/ui/cocoa/atom_touch_bar.mm +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -113,19 +113,10 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; return nil; } - - (void)refreshTouchBarItem:(NSTouchBar*)touchBar - id:(const std::string&)item_id { - if (![self hasItemWithID:item_id]) return; - - mate::PersistentDictionary settings = settings_[item_id]; - std::string item_type; - settings.Get("type", &item_type); - - NSTouchBarItemIdentifier identifier = [self identifierFromID:item_id - type:item_type]; - if (!identifier) return; - + id:(NSTouchBarItemIdentifier)identifier + withType:(const std::string&)item_type + withSettings:(const mate::PersistentDictionary&)settings { NSTouchBarItem* item = [touchBar itemForIdentifier:identifier]; if (!item) return; @@ -145,7 +136,56 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; } else if (item_type == "scrubber") { [self updateScrubber:(NSCustomTouchBarItem*)item withSettings:settings]; } +} +- (void)addNonDefaultTouchBarItems:(const std::vector&)items { + [self identifiersFromSettings:items]; +} + +- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item forTouchBar:(NSTouchBar*)touchBar { + std::string type; + std::string item_id; + NSTouchBarItemIdentifier identifier = nil; + if (item.Get("type", &type) && item.Get("id", &item_id)) { + identifier = [self identifierFromID:item_id type:type]; + } + if (identifier) { + [self addNonDefaultTouchBarItems:{ item }]; + touchBar.escapeKeyReplacementItemIdentifier = identifier; + } else { + touchBar.escapeKeyReplacementItemIdentifier = nil; + } +} + +- (void)refreshTouchBarItem:(NSTouchBar*)touchBar + id:(const std::string&)item_id { + if (![self hasItemWithID:item_id]) return; + + mate::PersistentDictionary settings = settings_[item_id]; + std::string item_type; + settings.Get("type", &item_type); + + auto identifier = [self identifierFromID:item_id type:item_type]; + if (!identifier) return; + + std::vector popover_ids; + settings.Get("_popover", &popover_ids); + for (auto& popover_id : popover_ids) { + auto popoverIdentifier = [self identifierFromID:popover_id type:"popover"]; + if (!popoverIdentifier) continue; + + NSPopoverTouchBarItem* popoverItem = + [touchBar itemForIdentifier:popoverIdentifier]; + [self refreshTouchBarItem:popoverItem.popoverTouchBar + id:identifier + withType:item_type + withSettings:settings]; + } + + [self refreshTouchBarItem:touchBar + id:identifier + withType:item_type + withSettings:settings]; } - (void)buttonAction:(id)sender { @@ -270,6 +310,16 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; gfx::Image image; if (settings.Get("icon", &image)) { button.image = image.AsNSImage(); + + std::string iconPosition; + settings.Get("iconPosition", &iconPosition); + if (iconPosition == "left") { + button.imagePosition = NSImageLeft; + } else if (iconPosition == "right") { + button.imagePosition = NSImageRight; + } else { + button.imagePosition = NSImageOverlaps; + } } } @@ -319,7 +369,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; - (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings { std::vector colors; - if (settings.Get("availableColors", &colors) && colors.size() > 0) { + if (settings.Get("availableColors", &colors) && !colors.empty()) { NSColorList* color_list = [[[NSColorList alloc] initWithName:@""] autorelease]; for (size_t i = 0; i < colors.size(); ++i) { [color_list insertColor:[self colorFromHexColorString:colors[i]] @@ -414,7 +464,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; NSMutableArray* generatedItems = [NSMutableArray array]; NSMutableArray* identifiers = [self identifiersFromSettings:items]; - for (NSUInteger i = 0; i < [identifiers count]; i++) { + for (NSUInteger i = 0; i < [identifiers count]; ++i) { if ([identifiers objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identifiers objectAtIndex:i]]; if (generatedItem) { @@ -474,7 +524,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; settings.Get("segments", &segments); control.segmentCount = segments.size(); - for (int i = 0; i < (int)segments.size(); i++) { + for (size_t i = 0; i < segments.size(); ++i) { std::string label; gfx::Image image; bool enabled = true; @@ -581,7 +631,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; std::vector items; if (!settings.Get("items", &items)) return nil; - if (index >= (long)items.size()) return nil; + if (index >= static_cast(items.size())) return nil; mate::PersistentDictionary item = items[index]; diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h index 57d5bd7453bb..6fe7c820a1cc 100644 --- a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -55,6 +55,7 @@ static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = @property(copy) NSArray* defaultItemIdentifiers; @property(copy, readonly) NSArray* itemIdentifiers; @property(copy, nullable) NSTouchBarItemIdentifier principalItemIdentifier; +@property(copy, nullable) NSTouchBarItemIdentifier escapeKeyReplacementItemIdentifier; @property(copy) NSSet* templateItems; @property(nullable, weak) id delegate; diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 70272cbddc8f..80143a987bc7 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -154,7 +154,7 @@ void ShowOpenDialog(const DialogSettings& settings, NSWindow* window = settings.parent_window ? settings.parent_window->GetNativeWindow() : - NULL; + nullptr; [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { if (chosen == NSFileHandlingPanelCancelButton) { @@ -193,7 +193,7 @@ void ShowSaveDialog(const DialogSettings& settings, NSWindow* window = settings.parent_window ? settings.parent_window->GetNativeWindow() : - NULL; + nullptr; [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { if (chosen == NSFileHandlingPanelCancelButton) { diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index 3550e47cf86e..f752f2945c2e 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -71,10 +71,14 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, switch (type) { case MESSAGE_BOX_TYPE_INFORMATION: - [alert setAlertStyle:NSInformationalAlertStyle]; + alert.alertStyle = NSInformationalAlertStyle; break; case MESSAGE_BOX_TYPE_WARNING: - [alert setAlertStyle:NSWarningAlertStyle]; + case MESSAGE_BOX_TYPE_ERROR: + // NSWarningAlertStyle shows the app icon while NSCriticalAlertStyle + // shows a warning icon with an app icon badge. Since there is no + // error variant, lets just use NSCriticalAlertStyle. + alert.alertStyle = NSCriticalAlertStyle; break; default: break; @@ -192,7 +196,7 @@ void ShowErrorBox(const base::string16& title, const base::string16& content) { NSAlert* alert = [[NSAlert alloc] init]; [alert setMessageText:base::SysUTF16ToNSString(title)]; [alert setInformativeText:base::SysUTF16ToNSString(content)]; - [alert setAlertStyle:NSWarningAlertStyle]; + [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; [alert release]; } diff --git a/atom/browser/ui/views/menu_layout.cc b/atom/browser/ui/views/menu_layout.cc deleted file mode 100644 index d70a4655a121..000000000000 --- a/atom/browser/ui/views/menu_layout.cc +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/browser/ui/views/menu_layout.h" - -#if defined(OS_WIN) -#include "atom/browser/native_window_views.h" -#include "ui/display/win/screen_win.h" -#endif - -namespace atom { - -namespace { - -#if defined(OS_WIN) -gfx::Rect SubtractBorderSize(gfx::Rect bounds) { - gfx::Point borderSize = gfx::Point( - GetSystemMetrics(SM_CXSIZEFRAME) - 1, // width - GetSystemMetrics(SM_CYSIZEFRAME) - 1); // height - gfx::Point dpiAdjustedSize = - display::win::ScreenWin::ScreenToDIPPoint(borderSize); - - bounds.set_x(bounds.x() + dpiAdjustedSize.x()); - bounds.set_y(bounds.y() + dpiAdjustedSize.y()); - bounds.set_width(bounds.width() - 2 * dpiAdjustedSize.x()); - bounds.set_height(bounds.height() - 2 * dpiAdjustedSize.y()); - return bounds; -} -#endif - -} // namespace - -MenuLayout::MenuLayout(NativeWindowViews* window, int menu_height) - : window_(window), - menu_height_(menu_height) { -} - -MenuLayout::~MenuLayout() { -} - -void MenuLayout::Layout(views::View* host) { -#if defined(OS_WIN) - // Reserve border space for maximized frameless window so we won't have the - // content go outside of screen. - if (!window_->has_frame() && window_->IsMaximized()) { - gfx::Rect bounds = SubtractBorderSize(host->GetContentsBounds()); - host->child_at(0)->SetBoundsRect(bounds); - return; - } -#endif - - if (!HasMenu(host)) { - views::FillLayout::Layout(host); - return; - } - - gfx::Size size = host->GetContentsBounds().size(); - gfx::Rect menu_Bar_bounds = gfx::Rect(0, 0, size.width(), menu_height_); - gfx::Rect web_view_bounds = gfx::Rect( - 0, menu_height_, size.width(), size.height() - menu_height_); - - views::View* web_view = host->child_at(0); - views::View* menu_bar = host->child_at(1); - web_view->SetBoundsRect(web_view_bounds); - menu_bar->SetBoundsRect(menu_Bar_bounds); -} - -gfx::Size MenuLayout::GetPreferredSize(const views::View* host) const { - gfx::Size size = views::FillLayout::GetPreferredSize(host); - if (!HasMenu(host)) - return size; - - size.set_height(size.height() + menu_height_); - return size; -} - -int MenuLayout::GetPreferredHeightForWidth( - const views::View* host, int width) const { - int height = views::FillLayout::GetPreferredHeightForWidth(host, width); - if (!HasMenu(host)) - return height; - - return height + menu_height_; -} - -bool MenuLayout::HasMenu(const views::View* host) const { - return host->child_count() == 2; -} - -} // namespace atom diff --git a/atom/browser/ui/views/menu_layout.h b/atom/browser/ui/views/menu_layout.h deleted file mode 100644 index 0a8464a1d44a..000000000000 --- a/atom/browser/ui/views/menu_layout.h +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_UI_VIEWS_MENU_LAYOUT_H_ -#define ATOM_BROWSER_UI_VIEWS_MENU_LAYOUT_H_ - -#include "ui/views/layout/fill_layout.h" - -namespace atom { - -class NativeWindowViews; - -class MenuLayout : public views::FillLayout { - public: - MenuLayout(NativeWindowViews* window, int menu_height); - virtual ~MenuLayout(); - - // views::LayoutManager: - void Layout(views::View* host) override; - gfx::Size GetPreferredSize(const views::View* host) const override; - int GetPreferredHeightForWidth( - const views::View* host, int width) const override; - - private: - bool HasMenu(const views::View* host) const; - - NativeWindowViews* window_; - int menu_height_; - - DISALLOW_COPY_AND_ASSIGN(MenuLayout); -}; - -} // namespace atom - -#endif // ATOM_BROWSER_UI_VIEWS_MENU_LAYOUT_H_ diff --git a/atom/browser/ui/views/submenu_button.cc b/atom/browser/ui/views/submenu_button.cc index b398d84deec8..617dd3346dad 100644 --- a/atom/browser/ui/views/submenu_button.cc +++ b/atom/browser/ui/views/submenu_button.cc @@ -16,26 +16,15 @@ namespace atom { -namespace { - -// Filter out the "&" in menu label. -base::string16 FilterAccelerator(const base::string16& label) { - base::string16 out; - base::RemoveChars(label, base::ASCIIToUTF16("&").c_str(), &out); - return out; -} - -} // namespace - SubmenuButton::SubmenuButton(const base::string16& title, views::MenuButtonListener* menu_button_listener, const SkColor& background_color) - : views::MenuButton(FilterAccelerator(title), + : views::MenuButton(gfx::RemoveAcceleratorChar(title, '&', NULL, NULL), menu_button_listener, false), accelerator_(0), show_underline_(false), - underline_start_(-1), - underline_end_(-1), + underline_start_(0), + underline_end_(0), text_width_(0), text_height_(0), underline_color_(SK_ColorBLACK), @@ -117,7 +106,7 @@ bool SubmenuButton::GetUnderlinePosition(const base::string16& text, void SubmenuButton::GetCharacterPosition( const base::string16& text, int index, int* pos) { - int height; + int height = 0; gfx::Canvas::SizeStringInt(text.substr(0, index), GetFontList(), pos, &height, 0, 0); } diff --git a/atom/browser/ui/views/win_frame_view.cc b/atom/browser/ui/views/win_frame_view.cc index fca7cb23347c..3908a2774ef9 100644 --- a/atom/browser/ui/views/win_frame_view.cc +++ b/atom/browser/ui/views/win_frame_view.cc @@ -23,7 +23,6 @@ WinFrameView::WinFrameView() { WinFrameView::~WinFrameView() { } - gfx::Rect WinFrameView::GetWindowBoundsForClientBounds( const gfx::Rect& client_bounds) const { return views::GetWindowBoundsForClientBounds( diff --git a/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc b/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc index 9cc2ef9e8d22..84a6d9aa3e50 100644 --- a/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc +++ b/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc @@ -25,12 +25,4 @@ bool AtomDesktopWindowTreeHostWin::PreHandleMSG( return delegate_->PreHandleMSG(message, w_param, l_param, result); } -/** Override the client area inset - * Returning true forces a border of 0 for frameless windows - */ -bool AtomDesktopWindowTreeHostWin::GetClientAreaInsets( - gfx::Insets* insets) const { - return !HasFrame(); -} - } // namespace atom diff --git a/atom/browser/ui/win/atom_desktop_window_tree_host_win.h b/atom/browser/ui/win/atom_desktop_window_tree_host_win.h index 2df70547c5ba..47e4cb6aed2a 100644 --- a/atom/browser/ui/win/atom_desktop_window_tree_host_win.h +++ b/atom/browser/ui/win/atom_desktop_window_tree_host_win.h @@ -27,7 +27,6 @@ class AtomDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin { protected: bool PreHandleMSG( UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override; - bool GetClientAreaInsets(gfx::Insets* insets) const override; private: MessageHandlerDelegate* delegate_; // weak ref diff --git a/atom/browser/web_contents_permission_helper.cc b/atom/browser/web_contents_permission_helper.cc index 2c79270591b8..ec9e4ad6e941 100644 --- a/atom/browser/web_contents_permission_helper.cc +++ b/atom/browser/web_contents_permission_helper.cc @@ -66,8 +66,9 @@ void WebContentsPermissionHelper::RequestPermission( void WebContentsPermissionHelper::RequestFullscreenPermission( const base::Callback& callback) { - RequestPermission((content::PermissionType)(PermissionType::FULLSCREEN), - callback); + RequestPermission( + static_cast(PermissionType::FULLSCREEN), + callback); } void WebContentsPermissionHelper::RequestMediaAccessPermission( @@ -86,17 +87,17 @@ void WebContentsPermissionHelper::RequestWebNotificationPermission( void WebContentsPermissionHelper::RequestPointerLockPermission( bool user_gesture) { - RequestPermission((content::PermissionType)(PermissionType::POINTER_LOCK), - base::Bind(&OnPointerLockResponse, web_contents_), - user_gesture); + RequestPermission( + static_cast(PermissionType::POINTER_LOCK), + base::Bind(&OnPointerLockResponse, web_contents_), user_gesture); } void WebContentsPermissionHelper::RequestOpenExternalPermission( const base::Callback& callback, bool user_gesture) { - RequestPermission((content::PermissionType)(PermissionType::OPEN_EXTERNAL), - callback, - user_gesture); + RequestPermission( + static_cast(PermissionType::OPEN_EXTERNAL), + callback, user_gesture); } } // namespace atom diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index 448fccd31f5a..fcd598b1aaea 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -51,7 +51,7 @@ class FileSelectHelper : public base::RefCounted, private: friend class base::RefCounted; - ~FileSelectHelper() {} + ~FileSelectHelper() override {} void OnOpenDialogDone(bool result, const std::vector& paths) { std::vector file_info; diff --git a/atom/browser/window_list.cc b/atom/browser/window_list.cc index b835e07e752a..374389e0a786 100644 --- a/atom/browser/window_list.cc +++ b/atom/browser/window_list.cc @@ -26,6 +26,16 @@ WindowList* WindowList::GetInstance() { return instance_; } +// static +WindowList::WindowVector WindowList::GetWindows() { + return GetInstance()->windows_; +} + +// static +bool WindowList::IsEmpty() { + return GetInstance()->windows_.empty(); +} + // static void WindowList::AddWindow(NativeWindow* window) { DCHECK(window); @@ -46,7 +56,7 @@ void WindowList::RemoveWindow(NativeWindow* window) { for (WindowListObserver& observer : observers_.Get()) observer.OnWindowRemoved(window); - if (windows.size() == 0) { + if (windows.empty()) { for (WindowListObserver& observer : observers_.Get()) observer.OnWindowAllClosed(); } @@ -76,6 +86,13 @@ void WindowList::CloseAllWindows() { window->Close(); } +// static +void WindowList::DestroyAllWindows() { + WindowVector windows = GetInstance()->windows_; + for (const auto& window : windows) + window->CloseContents(nullptr); // e.g. Destroy() +} + WindowList::WindowList() { } diff --git a/atom/browser/window_list.h b/atom/browser/window_list.h index d9b307352e90..e336c8073dc8 100644 --- a/atom/browser/window_list.h +++ b/atom/browser/window_list.h @@ -19,23 +19,9 @@ class WindowListObserver; class WindowList { public: typedef std::vector WindowVector; - typedef WindowVector::iterator iterator; - typedef WindowVector::const_iterator const_iterator; - // Windows are added to the list before they have constructed windows, - // so the |window()| member function may return NULL. - const_iterator begin() const { return windows_.begin(); } - const_iterator end() const { return windows_.end(); } - - iterator begin() { return windows_.begin(); } - iterator end() { return windows_.end(); } - - bool empty() const { return windows_.empty(); } - size_t size() const { return windows_.size(); } - - NativeWindow* get(size_t index) const { return windows_[index]; } - - static WindowList* GetInstance(); + static WindowVector GetWindows(); + static bool IsEmpty(); // Adds or removes |window| from the list it is associated with. static void AddWindow(NativeWindow* window); @@ -51,7 +37,12 @@ class WindowList { // Closes all windows. static void CloseAllWindows(); + // Destroy all windows. + static void DestroyAllWindows(); + private: + static WindowList* GetInstance(); + WindowList(); ~WindowList(); diff --git a/atom/common/api/object_life_monitor.cc b/atom/common/api/object_life_monitor.cc index cd5537e8874c..cc68130d34bd 100644 --- a/atom/common/api/object_life_monitor.cc +++ b/atom/common/api/object_life_monitor.cc @@ -12,8 +12,7 @@ namespace atom { ObjectLifeMonitor::ObjectLifeMonitor(v8::Isolate* isolate, v8::Local target) - : context_(isolate, isolate->GetCurrentContext()), - target_(isolate, target), + : target_(isolate, target), weak_ptr_factory_(this) { target_.SetWeak(this, OnObjectGC, v8::WeakCallbackType::kParameter); } diff --git a/atom/common/api/object_life_monitor.h b/atom/common/api/object_life_monitor.h index 73030864b933..e047960e8130 100644 --- a/atom/common/api/object_life_monitor.h +++ b/atom/common/api/object_life_monitor.h @@ -22,7 +22,6 @@ class ObjectLifeMonitor { static void OnObjectGC(const v8::WeakCallbackInfo& data); static void Free(const v8::WeakCallbackInfo& data); - v8::Global context_; v8::Global target_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 73528b45eaae..79ece65408bf 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -7,7 +7,7 @@ #define ATOM_MAJOR_VERSION 1 #define ATOM_MINOR_VERSION 6 -#define ATOM_PATCH_VERSION 4 +#define ATOM_PATCH_VERSION 7 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index 25969dc32eb3..a7908ef30c6b 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -179,7 +179,7 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, google_breakpad::ExceptionHandler::HANDLER_ALL, kSmallDumpType, pipe_name.c_str(), - GetCustomInfo(product_name, version, company_name))); + GetCustomInfo(product_name, version, company_name, upload_to_server))); if (!breakpad_->IsOutOfProcess()) LOG(ERROR) << "Cannot initialize out-of-process crash handler"; @@ -238,14 +238,19 @@ bool CrashReporterWin::MinidumpCallback(const wchar_t* dump_path, google_breakpad::CustomClientInfo* CrashReporterWin::GetCustomInfo( const std::string& product_name, const std::string& version, - const std::string& company_name) { + const std::string& company_name, + bool upload_to_server) { custom_info_entries_.clear(); - custom_info_entries_.reserve(2 + upload_parameters_.size()); + custom_info_entries_.reserve(3 + upload_parameters_.size()); custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( L"prod", L"Electron")); custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( L"ver", base::UTF8ToWide(version).c_str())); + if (!upload_to_server) { + custom_info_entries_.push_back(google_breakpad::CustomInfoEntry( + L"skip_upload", L"1")); + } for (StringMap::const_iterator iter = upload_parameters_.begin(); iter != upload_parameters_.end(); ++iter) { diff --git a/atom/common/crash_reporter/crash_reporter_win.h b/atom/common/crash_reporter/crash_reporter_win.h index 3fc139aca190..5070df206006 100644 --- a/atom/common/crash_reporter/crash_reporter_win.h +++ b/atom/common/crash_reporter/crash_reporter_win.h @@ -56,7 +56,8 @@ class CrashReporterWin : public CrashReporter { google_breakpad::CustomClientInfo* GetCustomInfo( const std::string& product_name, const std::string& version, - const std::string& company_name); + const std::string& company_name, + bool upload_to_server); // Custom information to be passed to crash handler. std::vector custom_info_entries_; diff --git a/atom/common/crash_reporter/win/crash_service.cc b/atom/common/crash_reporter/win/crash_service.cc index 5782fd72a3fb..a306a567a711 100644 --- a/atom/common/crash_reporter/win/crash_service.cc +++ b/atom/common/crash_reporter/win/crash_service.cc @@ -391,7 +391,7 @@ void CrashService::OnClientDumpRequest(void* context, LOG(ERROR) << "could not write custom info file"; } - if (!self->sender_) + if (!self->sender_ || map.find(L"skip_upload") != map.end()) return; // Send the crash dump using a worker thread. This operation has retry diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index 72f1011a72c3..7869e37deeea 100644 --- a/atom/common/native_mate_converters/content_converter.cc +++ b/atom/common/native_mate_converters/content_converter.cc @@ -168,11 +168,13 @@ v8::Local Converter::ToV8( break; } - if (val == (content::PermissionType)(PermissionType::POINTER_LOCK)) + if (val == static_cast(PermissionType::POINTER_LOCK)) return StringToV8(isolate, "pointerLock"); - else if (val == (content::PermissionType)(PermissionType::FULLSCREEN)) + else if (val == + static_cast(PermissionType::FULLSCREEN)) return StringToV8(isolate, "fullscreen"); - else if (val == (content::PermissionType)(PermissionType::OPEN_EXTERNAL)) + else if (val == + static_cast(PermissionType::OPEN_EXTERNAL)) return StringToV8(isolate, "openExternal"); return StringToV8(isolate, "unknown"); diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 94fff2ff6027..b78bc5b8e118 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -26,6 +26,27 @@ namespace mate { +namespace { + +bool CertFromData(const std::string& data, + scoped_refptr* out) { + auto cert_list = net::X509Certificate::CreateCertificateListFromBytes( + data.c_str(), data.length(), + net::X509Certificate::FORMAT_SINGLE_CERTIFICATE); + if (cert_list.empty()) + return false; + + auto leaf_cert = cert_list.front(); + if (!leaf_cert) + return false; + + *out = leaf_cert; + + return true; +} + +} // namespace + // static v8::Local Converter::ToV8( v8::Isolate* isolate, const net::AuthChallengeInfo* val) { @@ -73,6 +94,37 @@ v8::Local Converter>::ToV8( return dict.GetHandle(); } +bool Converter>::FromV8( + v8::Isolate* isolate, v8::Local val, + scoped_refptr* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + + std::string data; + dict.Get("data", &data); + scoped_refptr leaf_cert; + if (!CertFromData(data, &leaf_cert)) + return false; + + scoped_refptr parent; + if (dict.Get("issuerCert", &parent)) { + auto parents = std::vector( + parent->GetIntermediateCertificates()); + parents.insert(parents.begin(), parent->os_cert_handle()); + auto cert = net::X509Certificate::CreateFromHandle( + leaf_cert->os_cert_handle(), parents); + if (!cert) + return false; + + *out = cert; + } else { + *out = leaf_cert; + } + + return true; +} + // static v8::Local Converter::ToV8( v8::Isolate* isolate, const net::CertPrincipal& val) { diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index 33117ca974f1..9e3128fdb546 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -33,6 +33,10 @@ template<> struct Converter> { static v8::Local ToV8(v8::Isolate* isolate, const scoped_refptr& val); + + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + scoped_refptr* out); }; template<> diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 314a7b9c0330..ae757b1da883 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -33,17 +33,18 @@ // Electron's builtin modules. REFERENCE_MODULE(atom_browser_app); REFERENCE_MODULE(atom_browser_auto_updater); +REFERENCE_MODULE(atom_browser_browser_view); REFERENCE_MODULE(atom_browser_content_tracing); -REFERENCE_MODULE(atom_browser_dialog); REFERENCE_MODULE(atom_browser_debugger); REFERENCE_MODULE(atom_browser_desktop_capturer); +REFERENCE_MODULE(atom_browser_dialog); REFERENCE_MODULE(atom_browser_download_item); +REFERENCE_MODULE(atom_browser_global_shortcut); REFERENCE_MODULE(atom_browser_menu); REFERENCE_MODULE(atom_browser_net); REFERENCE_MODULE(atom_browser_power_monitor); REFERENCE_MODULE(atom_browser_power_save_blocker); REFERENCE_MODULE(atom_browser_protocol); -REFERENCE_MODULE(atom_browser_global_shortcut); REFERENCE_MODULE(atom_browser_render_process_preferences); REFERENCE_MODULE(atom_browser_session); REFERENCE_MODULE(atom_browser_system_preferences); @@ -232,6 +233,12 @@ void NodeBindings::RunMessageLoop() { void NodeBindings::UvRunOnce() { node::Environment* env = uv_env(); + // When doing navigation without restarting renderer process, it may happen + // that the node environment is destroyed but the message loop is still there. + // In this case we should not run uv loop. + if (!env) + return; + // Use Locker in browser process. mate::Locker locker(env->isolate()); v8::HandleScope handle_scope(env->isolate()); diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index d040489b9f76..2f1c0368f35e 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -51,6 +51,9 @@ const char kZoomToPageWidth[] = "zoomToPageWidth"; // The requested title bar style for the window const char kTitleBarStyle[] = "titleBarStyle"; +// Tabbing identifier for the window if native tabs are enabled on macOS. +const char kTabbingIdentifier[] = "tabbingIdentifier"; + // The menu bar is hidden unless "Alt" is pressed. const char kAutoHideMenuBar[] = "autoHideMenuBar"; @@ -156,6 +159,9 @@ const char kSecureSchemes[] = "secure-schemes"; // The browser process app model ID const char kAppUserModelId[] = "app-user-model-id"; +// The application path +const char kAppPath[] = "app-path"; + // The command line switch versions of the options. const char kBackgroundColor[] = "background-color"; const char kPreloadScript[] = "preload"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index dd2824c55c74..69e7af029e15 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -36,6 +36,7 @@ extern const char kAcceptFirstMouse[]; extern const char kUseContentSize[]; extern const char kZoomToPageWidth[]; extern const char kTitleBarStyle[]; +extern const char kTabbingIdentifier[]; extern const char kAutoHideMenuBar[]; extern const char kEnableLargerThanScreen[]; extern const char kDarkTheme[]; @@ -80,6 +81,7 @@ extern const char kStandardSchemes[]; extern const char kRegisterServiceWorkerSchemes[]; extern const char kSecureSchemes[]; extern const char kAppUserModelId[]; +extern const char kAppPath[]; extern const char kBackgroundColor[]; extern const char kPreloadScript[]; diff --git a/atom/common/platform_util_linux.cc b/atom/common/platform_util_linux.cc index 923adbd882be..5c0eaecdd1c2 100644 --- a/atom/common/platform_util_linux.cc +++ b/atom/common/platform_util_linux.cc @@ -7,7 +7,9 @@ #include #include "base/cancelable_callback.h" +#include "base/environment.h" #include "base/files/file_util.h" +#include "base/nix/xdg_util.h" #include "base/process/kill.h" #include "base/process/launch.h" #include "url/gurl.h" @@ -100,7 +102,18 @@ bool MoveItemToTrash(const base::FilePath& full_path) { if (getenv(ELECTRON_TRASH) != NULL) { trash = getenv(ELECTRON_TRASH); } else { - trash = ELECTRON_DEFAULT_TRASH; + // Determine desktop environment and set accordingly. + std::unique_ptr env(base::Environment::Create()); + base::nix::DesktopEnvironment desktop_env( + base::nix::GetDesktopEnvironment(env.get())); + if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE4 || + desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE5) { + trash = "kioclient5"; + } else if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3) { + trash = "kioclient"; + } else { + trash = ELECTRON_DEFAULT_TRASH; + } } std::vector argv; diff --git a/atom/common/platform_util_mac.mm b/atom/common/platform_util_mac.mm index aa64678caffe..7259b3b13031 100644 --- a/atom/common/platform_util_mac.mm +++ b/atom/common/platform_util_mac.mm @@ -11,6 +11,7 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/logging.h" +#include "base/mac/foundation_util.h" #include "base/mac/mac_logging.h" #include "base/mac/scoped_aedesc.h" #include "base/strings/stringprintf.h" @@ -71,10 +72,10 @@ std::string MessageForOSStatus(OSStatus status, const char* default_message) { // thread safe, including LSGetApplicationForURL (> 10.2) and // NSWorkspace#openURLs. std::string OpenURL(NSURL* ns_url, bool activate) { - CFURLRef openingApp = NULL; - OSStatus status = LSGetApplicationForURL((CFURLRef)ns_url, + CFURLRef openingApp = nullptr; + OSStatus status = LSGetApplicationForURL(base::mac::NSToCFCast(ns_url), kLSRolesAll, - NULL, + nullptr, &openingApp); if (status != noErr) return MessageForOSStatus(status, "Failed to open"); @@ -156,7 +157,7 @@ bool OpenItem(const base::FilePath& full_path) { // Create the list of files (only ever one) to open. base::mac::ScopedAEDesc fileList; - status = AECreateList(NULL, // factoringPtr + status = AECreateList(nullptr, // factoringPtr 0, // factoredSize false, // isRecord fileList.OutPointer()); // resultList @@ -167,7 +168,8 @@ bool OpenItem(const base::FilePath& full_path) { // Add the single path to the file list. C-style cast to avoid both a // static_cast and a const_cast to get across the toll-free bridge. - CFURLRef pathURLRef = (CFURLRef)[NSURL fileURLWithPath:path_string]; + CFURLRef pathURLRef = base::mac::NSToCFCast( + [NSURL fileURLWithPath:path_string]); FSRef pathRef; if (CFURLGetFSRef(pathURLRef, &pathRef)) { status = AEPutPtr(fileList.OutPointer(), // theAEDescList @@ -202,8 +204,8 @@ bool OpenItem(const base::FilePath& full_path) { kAENoReply + kAEAlwaysInteract, // sendMode kAENormalPriority, // sendPriority kAEDefaultTimeout, // timeOutInTicks - NULL, // idleProc - NULL); // filterProc + nullptr, // idleProc + nullptr); // filterProc if (status != noErr) { OSSTATUS_LOG(WARNING, status) << "Could not send AE to Finder in OpenItem()"; diff --git a/atom/renderer/atom_render_frame_observer.cc b/atom/renderer/atom_render_frame_observer.cc new file mode 100644 index 000000000000..d9a41a48d4a9 --- /dev/null +++ b/atom/renderer/atom_render_frame_observer.cc @@ -0,0 +1,84 @@ +// 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 "atom/renderer/atom_render_frame_observer.h" + +#include "content/public/renderer/render_frame.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" + +namespace atom { + +AtomRenderFrameObserver::AtomRenderFrameObserver( + content::RenderFrame* frame, + RendererClientBase* renderer_client) + : content::RenderFrameObserver(frame), + render_frame_(frame), + renderer_client_(renderer_client) {} + +void AtomRenderFrameObserver::DidClearWindowObject() { + renderer_client_->DidClearWindowObject(render_frame_); +} + +void AtomRenderFrameObserver::DidCreateScriptContext( + v8::Handle context, + int extension_group, + int world_id) { + if (ShouldNotifyClient(world_id)) + renderer_client_->DidCreateScriptContext(context, render_frame_); + + if (renderer_client_->isolated_world() && IsMainWorld(world_id) + && render_frame_->IsMainFrame()) { + CreateIsolatedWorldContext(); + renderer_client_->SetupMainWorldOverrides(context); + } +} + +void AtomRenderFrameObserver::WillReleaseScriptContext( + v8::Local context, + int world_id) { + if (ShouldNotifyClient(world_id)) + renderer_client_->WillReleaseScriptContext(context, render_frame_); +} + +void AtomRenderFrameObserver::OnDestruct() { + delete this; +} + +void AtomRenderFrameObserver::CreateIsolatedWorldContext() { + auto frame = render_frame_->GetWebFrame(); + + // This maps to the name shown in the context combo box in the Console tab + // of the dev tools. + frame->setIsolatedWorldHumanReadableName( + World::ISOLATED_WORLD, + blink::WebString::fromUTF8("Electron Isolated Context")); + + // Setup document's origin policy in isolated world + frame->setIsolatedWorldSecurityOrigin( + World::ISOLATED_WORLD, frame->document().getSecurityOrigin()); + + // Create initial script context in isolated world + blink::WebScriptSource source("void 0"); + frame->executeScriptInIsolatedWorld( + World::ISOLATED_WORLD, &source, 1, ExtensionGroup::MAIN_GROUP); +} + +bool AtomRenderFrameObserver::IsMainWorld(int world_id) { + return world_id == World::MAIN_WORLD; +} + +bool AtomRenderFrameObserver::IsIsolatedWorld(int world_id) { + return world_id == World::ISOLATED_WORLD; +} + +bool AtomRenderFrameObserver::ShouldNotifyClient(int world_id) { + if (renderer_client_->isolated_world() && render_frame_->IsMainFrame()) + return IsIsolatedWorld(world_id); + else + return IsMainWorld(world_id); +} + +} // namespace atom diff --git a/atom/renderer/atom_render_frame_observer.h b/atom/renderer/atom_render_frame_observer.h new file mode 100644 index 000000000000..51cb21b3b0e7 --- /dev/null +++ b/atom/renderer/atom_render_frame_observer.h @@ -0,0 +1,53 @@ +// 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_RENDERER_ATOM_RENDER_FRAME_OBSERVER_H_ +#define ATOM_RENDERER_ATOM_RENDER_FRAME_OBSERVER_H_ + +#include "atom/renderer/renderer_client_base.h" +#include "content/public/renderer/render_frame_observer.h" + +namespace atom { + +enum World { + MAIN_WORLD = 0, + // Use a high number far away from 0 to not collide with any other world + // IDs created internally by Chrome. + ISOLATED_WORLD = 999 +}; + +enum ExtensionGroup { + MAIN_GROUP = 1 +}; + +// Helper class to forward the messages to the client. +class AtomRenderFrameObserver : public content::RenderFrameObserver { + public: + AtomRenderFrameObserver(content::RenderFrame* frame, + RendererClientBase* renderer_client); + + // content::RenderFrameObserver: + void DidClearWindowObject() override; + void DidCreateScriptContext(v8::Handle context, + int extension_group, + int world_id) override; + void WillReleaseScriptContext(v8::Local context, + int world_id) override; + void OnDestruct() override; + + private: + bool ShouldNotifyClient(int world_id); + void CreateIsolatedWorldContext(); + bool IsMainWorld(int world_id); + bool IsIsolatedWorld(int world_id); + + content::RenderFrame* render_frame_; + RendererClientBase* renderer_client_; + + DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); +}; + +} // namespace atom + +#endif // ATOM_RENDERER_ATOM_RENDER_FRAME_OBSERVER_H_ diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 8dd8e04e1ac2..5dafe084ce27 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -9,53 +9,22 @@ #include "atom_natives.h" // NOLINT: This file is generated with js2c -#include "atom/common/api/api_messages.h" #include "atom/common/api/atom_bindings.h" #include "atom/common/api/event_emitter_caller.h" #include "atom/common/asar/asar_util.h" #include "atom/common/atom_constants.h" -#include "atom/common/color_util.h" -#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_bindings.h" #include "atom/common/options_switches.h" #include "atom/renderer/api/atom_api_renderer_ipc.h" +#include "atom/renderer/atom_render_frame_observer.h" #include "atom/renderer/atom_render_view_observer.h" -#include "atom/renderer/content_settings_observer.h" -#include "atom/renderer/guest_view_container.h" #include "atom/renderer/node_array_buffer_bridge.h" -#include "atom/renderer/preferences_manager.h" #include "atom/renderer/web_worker_observer.h" #include "base/command_line.h" -#include "chrome/renderer/media/chrome_key_systems.h" -#include "chrome/renderer/pepper/pepper_helper.h" -#include "chrome/renderer/printing/print_web_view_helper.h" -#include "chrome/renderer/tts_dispatcher.h" -#include "content/public/common/content_constants.h" #include "content/public/renderer/render_frame.h" -#include "content/public/renderer/render_frame_observer.h" -#include "content/public/renderer/render_thread.h" -#include "content/public/renderer/render_view.h" -#include "ipc/ipc_message_macros.h" #include "native_mate/dictionary.h" -#include "third_party/WebKit/public/web/WebCustomElement.h" #include "third_party/WebKit/public/web/WebDocument.h" -#include "third_party/WebKit/public/web/WebFrameWidget.h" -#include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" -#include "third_party/WebKit/public/web/WebPluginParams.h" -#include "third_party/WebKit/public/web/WebRuntimeFeatures.h" -#include "third_party/WebKit/public/web/WebScriptSource.h" -#include "third_party/WebKit/public/web/WebSecurityPolicy.h" -#include "third_party/WebKit/public/web/WebView.h" - -#if defined(OS_MACOSX) -#include "base/mac/mac_util.h" -#include "base/strings/sys_string_conversions.h" -#endif - -#if defined(OS_WIN) -#include -#endif #include "atom/common/node_includes.h" @@ -63,156 +32,11 @@ namespace atom { namespace { -enum World { - MAIN_WORLD = 0, - // Use a high number far away from 0 to not collide with any other world - // IDs created internally by Chrome. - ISOLATED_WORLD = 999 -}; - -enum ExtensionGroup { - MAIN_GROUP = 1 -}; - -// Helper class to forward the messages to the client. -class AtomRenderFrameObserver : public content::RenderFrameObserver { - public: - AtomRenderFrameObserver(content::RenderFrame* frame, - AtomRendererClient* renderer_client) - : content::RenderFrameObserver(frame), - render_frame_(frame), - renderer_client_(renderer_client) {} - - // content::RenderFrameObserver: - void DidClearWindowObject() override { - renderer_client_->DidClearWindowObject(render_frame_); - } - - void CreateIsolatedWorldContext() { - auto frame = render_frame_->GetWebFrame(); - - // This maps to the name shown in the context combo box in the Console tab - // of the dev tools. - frame->setIsolatedWorldHumanReadableName( - World::ISOLATED_WORLD, - blink::WebString::fromUTF8("Electron Isolated Context")); - - // Setup document's origin policy in isolated world - frame->setIsolatedWorldSecurityOrigin( - World::ISOLATED_WORLD, frame->document().getSecurityOrigin()); - - // Create initial script context in isolated world - blink::WebScriptSource source("void 0"); - frame->executeScriptInIsolatedWorld( - World::ISOLATED_WORLD, &source, 1, ExtensionGroup::MAIN_GROUP); - } - - void SetupMainWorldOverrides(v8::Handle context) { - // Setup window overrides in the main world context - v8::Isolate* isolate = context->GetIsolate(); - - // Wrap the bundle into a function that receives the binding object as - // an argument. - std::string bundle(node::isolated_bundle_data, - node::isolated_bundle_data + sizeof(node::isolated_bundle_data)); - std::string wrapper = "(function (binding) {\n" + bundle + "\n})"; - auto script = v8::Script::Compile( - mate::ConvertToV8(isolate, wrapper)->ToString()); - auto func = v8::Handle::Cast( - script->Run(context).ToLocalChecked()); - - auto binding = v8::Object::New(isolate); - api::Initialize(binding, v8::Null(isolate), context, nullptr); - - // Pass in CLI flags needed to setup window - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - mate::Dictionary dict(isolate, binding); - if (command_line->HasSwitch(switches::kGuestInstanceID)) - dict.Set(options::kGuestInstanceID, - command_line->GetSwitchValueASCII(switches::kGuestInstanceID)); - if (command_line->HasSwitch(switches::kOpenerID)) - dict.Set(options::kOpenerID, - command_line->GetSwitchValueASCII(switches::kOpenerID)); - dict.Set("hiddenPage", command_line->HasSwitch(switches::kHiddenPage)); - - v8::Local args[] = { binding }; - ignore_result(func->Call(context, v8::Null(isolate), 1, args)); - } - - bool IsMainWorld(int world_id) { - return world_id == World::MAIN_WORLD; - } - - bool IsIsolatedWorld(int world_id) { - return world_id == World::ISOLATED_WORLD; - } - - bool ShouldNotifyClient(int world_id) { - if (renderer_client_->isolated_world() && render_frame_->IsMainFrame()) - return IsIsolatedWorld(world_id); - else - return IsMainWorld(world_id); - } - - void DidCreateScriptContext(v8::Handle context, - int extension_group, - int world_id) override { - if (ShouldNotifyClient(world_id)) - renderer_client_->DidCreateScriptContext(context, render_frame_); - - if (renderer_client_->isolated_world() && IsMainWorld(world_id) - && render_frame_->IsMainFrame()) { - CreateIsolatedWorldContext(); - SetupMainWorldOverrides(context); - } - } - - void WillReleaseScriptContext(v8::Local context, - int world_id) override { - if (ShouldNotifyClient(world_id)) - renderer_client_->WillReleaseScriptContext(context, render_frame_); - } - - void OnDestruct() override { - delete this; - } - - private: - content::RenderFrame* render_frame_; - AtomRendererClient* renderer_client_; - - DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); -}; - -v8::Local GetRenderProcessPreferences( - const PreferencesManager* preferences_manager, v8::Isolate* isolate) { - if (preferences_manager->preferences()) - return mate::ConvertToV8(isolate, *preferences_manager->preferences()); - else - return v8::Null(isolate); -} - -void AddRenderBindings(v8::Isolate* isolate, - v8::Local process, - const PreferencesManager* preferences_manager) { - mate::Dictionary dict(isolate, process); - dict.SetMethod( - "getRenderProcessPreferences", - base::Bind(GetRenderProcessPreferences, preferences_manager)); -} - bool IsDevToolsExtension(content::RenderFrame* render_frame) { return static_cast(render_frame->GetWebFrame()->document().url()) .SchemeIs("chrome-extension"); } -std::vector ParseSchemesCLISwitch(const char* switch_name) { - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - std::string custom_schemes = command_line->GetSwitchValueASCII(switch_name); - return base::SplitString( - custom_schemes, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); -} - } // namespace AtomRendererClient::AtomRendererClient() @@ -221,11 +45,6 @@ AtomRendererClient::AtomRendererClient() atom_bindings_(new AtomBindings(uv_default_loop())) { isolated_world_ = base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kContextIsolation); - // Parse --standard-schemes=scheme1,scheme2 - std::vector standard_schemes_list = - ParseSchemesCLISwitch(switches::kStandardSchemes); - for (const std::string& scheme : standard_schemes_list) - url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITHOUT_PORT); } AtomRendererClient::~AtomRendererClient() { @@ -233,86 +52,18 @@ AtomRendererClient::~AtomRendererClient() { } void AtomRendererClient::RenderThreadStarted() { - blink::WebCustomElement::addEmbedderCustomElementName("webview"); - blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); - OverrideNodeArrayBuffer(); - - preferences_manager_.reset(new PreferencesManager); - -#if defined(OS_WIN) - // Set ApplicationUserModelID in renderer process. - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - base::string16 app_id = - command_line->GetSwitchValueNative(switches::kAppUserModelId); - if (!app_id.empty()) { - SetCurrentProcessExplicitAppUserModelID(app_id.c_str()); - } -#endif - -#if defined(OS_MACOSX) - // Disable rubber banding by default. - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - if (!command_line->HasSwitch(switches::kScrollBounce)) { - base::ScopedCFTypeRef key( - base::SysUTF8ToCFStringRef("NSScrollViewRubberbanding")); - base::ScopedCFTypeRef value( - base::SysUTF8ToCFStringRef("false")); - CFPreferencesSetAppValue(key, value, kCFPreferencesCurrentApplication); - CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); - } -#endif + RendererClientBase::RenderThreadStarted(); } void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { - new PepperHelper(render_frame); - new AtomRenderFrameObserver(render_frame, this); - new ContentSettingsObserver(render_frame); - new printing::PrintWebViewHelper(render_frame); - - // Allow file scheme to handle service worker by default. - // FIXME(zcbenz): Can this be moved elsewhere? - blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file"); - - // This is required for widevine plugin detection provided during runtime. - blink::resetPluginCache(); - - // Allow access to file scheme from pdf viewer. - blink::WebSecurityPolicy::addOriginAccessWhitelistEntry( - GURL(kPdfViewerUIOrigin), "file", "", true); - - // Parse --secure-schemes=scheme1,scheme2 - std::vector secure_schemes_list = - ParseSchemesCLISwitch(switches::kSecureSchemes); - for (const std::string& secure_scheme : secure_schemes_list) - blink::WebSecurityPolicy::registerURLSchemeAsSecure( - blink::WebString::fromUTF8(secure_scheme)); + RendererClientBase::RenderFrameCreated(render_frame); } void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) { new AtomRenderViewObserver(render_view, this); - - blink::WebFrameWidget* web_frame_widget = render_view->GetWebFrameWidget(); - if (!web_frame_widget) - return; - - base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); - if (cmd->HasSwitch(switches::kGuestInstanceID)) { // webview. - web_frame_widget->setBaseBackgroundColor(SK_ColorTRANSPARENT); - } else { // normal window. - // If backgroundColor is specified then use it. - std::string name = cmd->GetSwitchValueASCII(switches::kBackgroundColor); - // Otherwise use white background. - SkColor color = name.empty() ? SK_ColorWHITE : ParseHexColor(name); - web_frame_widget->setBaseBackgroundColor(color); - } -} - -void AtomRendererClient::DidClearWindowObject( - content::RenderFrame* render_frame) { - // Make sure every page will get a script context created. - render_frame->GetWebFrame()->executeScript(blink::WebScriptSource("void 0")); + RendererClientBase::RenderViewCreated(render_view); } void AtomRendererClient::RunScriptsAtDocumentStart( @@ -335,26 +86,6 @@ void AtomRendererClient::RunScriptsAtDocumentEnd( } } -blink::WebSpeechSynthesizer* AtomRendererClient::OverrideSpeechSynthesizer( - blink::WebSpeechSynthesizerClient* client) { - return new TtsDispatcher(client); -} - -bool AtomRendererClient::OverrideCreatePlugin( - content::RenderFrame* render_frame, - blink::WebLocalFrame* frame, - const blink::WebPluginParams& params, - blink::WebPlugin** plugin) { - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - if (params.mimeType.utf8() == content::kBrowserPluginMimeType || - params.mimeType.utf8() == kPdfPluginMimeType || - command_line->HasSwitch(switches::kEnablePlugins)) - return false; - - *plugin = nullptr; - return true; -} - void AtomRendererClient::DidCreateScriptContext( v8::Handle context, content::RenderFrame* render_frame) { // Only allow node integration for the main frame, unless it is a devtools @@ -374,8 +105,7 @@ void AtomRendererClient::DidCreateScriptContext( // Add Electron extended APIs. atom_bindings_->BindTo(env->isolate(), env->process_object()); - AddRenderBindings(env->isolate(), env->process_object(), - preferences_manager_.get()); + AddRenderBindings(env->isolate(), env->process_object()); // Load everything. node_bindings_->LoadEnvironment(env); @@ -423,22 +153,6 @@ bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, return http_method == "GET"; } -content::BrowserPluginDelegate* AtomRendererClient::CreateBrowserPluginDelegate( - content::RenderFrame* render_frame, - const std::string& mime_type, - const GURL& original_url) { - if (mime_type == content::kBrowserPluginMimeType) { - return new GuestViewContainer(render_frame); - } else { - return nullptr; - } -} - -void AtomRendererClient::AddSupportedKeySystems( - std::vector>* key_systems) { - AddChromeKeySystems(key_systems); -} - void AtomRendererClient::DidInitializeWorkerContextOnWorkerThread( v8::Local context) { if (base::CommandLine::ForCurrentProcess()->HasSwitch( @@ -464,4 +178,38 @@ v8::Local AtomRendererClient::GetContext( return frame->mainWorldScriptContext(); } +void AtomRendererClient::SetupMainWorldOverrides( + v8::Handle context) { + // Setup window overrides in the main world context + v8::Isolate* isolate = context->GetIsolate(); + + // Wrap the bundle into a function that receives the binding object as + // an argument. + std::string bundle(node::isolated_bundle_data, + node::isolated_bundle_data + sizeof(node::isolated_bundle_data)); + std::string wrapper = "(function (binding, require) {\n" + bundle + "\n})"; + auto script = v8::Script::Compile( + mate::ConvertToV8(isolate, wrapper)->ToString()); + auto func = v8::Handle::Cast( + script->Run(context).ToLocalChecked()); + + auto binding = v8::Object::New(isolate); + api::Initialize(binding, v8::Null(isolate), context, nullptr); + + // Pass in CLI flags needed to setup window + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + mate::Dictionary dict(isolate, binding); + if (command_line->HasSwitch(switches::kGuestInstanceID)) + dict.Set(options::kGuestInstanceID, + command_line->GetSwitchValueASCII(switches::kGuestInstanceID)); + if (command_line->HasSwitch(switches::kOpenerID)) + dict.Set(options::kOpenerID, + command_line->GetSwitchValueASCII(switches::kOpenerID)); + dict.Set("hiddenPage", command_line->HasSwitch(switches::kHiddenPage)); + + v8::Local args[] = { binding }; + ignore_result(func->Call(context, v8::Null(isolate), 1, args)); +} + + } // namespace atom diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index af8397854e3e..3feaff452858 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -8,29 +8,31 @@ #include #include -#include "content/public/renderer/content_renderer_client.h" +#include "atom/renderer/renderer_client_base.h" namespace atom { class AtomBindings; -class PreferencesManager; class NodeBindings; -class AtomRendererClient : public content::ContentRendererClient { +class AtomRendererClient : public RendererClientBase { public: AtomRendererClient(); virtual ~AtomRendererClient(); - void DidClearWindowObject(content::RenderFrame* render_frame); - void DidCreateScriptContext( - v8::Handle context, content::RenderFrame* render_frame); - void WillReleaseScriptContext( - v8::Handle context, content::RenderFrame* render_frame); - // Get the context that the Electron API is running in. v8::Local GetContext( blink::WebFrame* frame, v8::Isolate* isolate); - bool isolated_world() { return isolated_world_; } + + // atom::RendererClientBase: + void DidCreateScriptContext( + v8::Handle context, + content::RenderFrame* render_frame) override; + void WillReleaseScriptContext( + v8::Handle context, + content::RenderFrame* render_frame) override; + void SetupMainWorldOverrides(v8::Handle context) override; + bool isolated_world() override { return isolated_world_; } private: enum NodeIntegration { @@ -46,25 +48,12 @@ class AtomRendererClient : public content::ContentRendererClient { void RenderViewCreated(content::RenderView*) override; void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override; void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override; - blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer( - blink::WebSpeechSynthesizerClient* client) override; - bool OverrideCreatePlugin(content::RenderFrame* render_frame, - blink::WebLocalFrame* frame, - const blink::WebPluginParams& params, - blink::WebPlugin** plugin) override; bool ShouldFork(blink::WebLocalFrame* frame, const GURL& url, const std::string& http_method, bool is_initial_navigation, bool is_server_redirect, bool* send_referrer) override; - content::BrowserPluginDelegate* CreateBrowserPluginDelegate( - content::RenderFrame* render_frame, - const std::string& mime_type, - const GURL& original_url) override; - void AddSupportedKeySystems( - std::vector>* key_systems) - override; void DidInitializeWorkerContextOnWorkerThread( v8::Local context) override; void WillDestroyWorkerContextOnWorkerThread( @@ -75,7 +64,6 @@ class AtomRendererClient : public content::ContentRendererClient { std::unique_ptr node_bindings_; std::unique_ptr atom_bindings_; - std::unique_ptr preferences_manager_; bool isolated_world_; DISALLOW_COPY_AND_ASSIGN(AtomRendererClient); diff --git a/atom/renderer/atom_sandboxed_renderer_client.cc b/atom/renderer/atom_sandboxed_renderer_client.cc index 7d0dfde8c5ab..4fe9bd449dd7 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.cc +++ b/atom/renderer/atom_sandboxed_renderer_client.cc @@ -9,6 +9,7 @@ #include "atom_natives.h" // NOLINT: This file is generated with js2c #include "atom/common/api/api_messages.h" +#include "atom/common/api/atom_bindings.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/v8_value_converter.h" #include "atom/common/native_mate_converters/value_converter.h" @@ -19,7 +20,6 @@ #include "base/command_line.h" #include "chrome/renderer/printing/print_web_view_helper.h" #include "content/public/renderer/render_frame.h" -#include "content/public/renderer/render_frame_observer.h" #include "content/public/renderer/render_view.h" #include "content/public/renderer/render_view_observer.h" #include "ipc/ipc_message_macros.h" @@ -85,52 +85,9 @@ void InitializeBindings(v8::Local binding, auto isolate = context->GetIsolate(); mate::Dictionary b(isolate, binding); b.SetMethod("get", GetBinding); + b.SetMethod("crash", AtomBindings::Crash); } -class AtomSandboxedRenderFrameObserver : public content::RenderFrameObserver { - public: - AtomSandboxedRenderFrameObserver(content::RenderFrame* frame, - AtomSandboxedRendererClient* renderer_client) - : content::RenderFrameObserver(frame), - render_frame_(frame), - world_id_(-1), - renderer_client_(renderer_client) {} - - // content::RenderFrameObserver: - void DidClearWindowObject() override { - // Make sure every page will get a script context created. - render_frame_->GetWebFrame()->executeScript( - blink::WebScriptSource("void 0")); - } - - void DidCreateScriptContext(v8::Handle context, - int extension_group, - int world_id) override { - if (world_id_ != -1 && world_id_ != world_id) - return; - world_id_ = world_id; - renderer_client_->DidCreateScriptContext(context, render_frame_); - } - - void WillReleaseScriptContext(v8::Local context, - int world_id) override { - if (world_id_ != world_id) - return; - renderer_client_->WillReleaseScriptContext(context, render_frame_); - } - - void OnDestruct() override { - delete this; - } - - private: - content::RenderFrame* render_frame_; - int world_id_; - AtomSandboxedRendererClient* renderer_client_; - - DISALLOW_COPY_AND_ASSIGN(AtomSandboxedRenderFrameObserver); -}; - class AtomSandboxedRenderViewObserver : public AtomRenderViewObserver { public: AtomSandboxedRenderViewObserver(content::RenderView* render_view, @@ -179,13 +136,13 @@ AtomSandboxedRendererClient::~AtomSandboxedRendererClient() { void AtomSandboxedRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { - new AtomSandboxedRenderFrameObserver(render_frame, this); - new printing::PrintWebViewHelper(render_frame); + RendererClientBase::RenderFrameCreated(render_frame); } void AtomSandboxedRendererClient::RenderViewCreated( content::RenderView* render_view) { new AtomSandboxedRenderViewObserver(render_view, this); + RendererClientBase::RenderViewCreated(render_view); } void AtomSandboxedRendererClient::DidCreateScriptContext( @@ -204,7 +161,7 @@ void AtomSandboxedRendererClient::DidCreateScriptContext( std::string preload_bundle_native(node::preload_bundle_data, node::preload_bundle_data + sizeof(node::preload_bundle_data)); std::stringstream ss; - ss << "(function(binding, preloadPath) {\n"; + ss << "(function(binding, preloadPath, require) {\n"; ss << preload_bundle_native << "\n"; ss << "})"; std::string preload_wrapper = ss.str(); @@ -216,6 +173,7 @@ void AtomSandboxedRendererClient::DidCreateScriptContext( // Create and initialize the binding object auto binding = v8::Object::New(isolate); InitializeBindings(binding, context); + AddRenderBindings(isolate, binding); v8::Local args[] = { binding, mate::ConvertToV8(isolate, preload_script) @@ -234,7 +192,7 @@ void AtomSandboxedRendererClient::WillReleaseScriptContext( void AtomSandboxedRendererClient::InvokeIpcCallback( v8::Handle context, - std::string callback_name, + const std::string& callback_name, std::vector> args) { auto isolate = context->GetIsolate(); auto binding_key = mate::ConvertToV8(isolate, kIpcKey)->ToString(); diff --git a/atom/renderer/atom_sandboxed_renderer_client.h b/atom/renderer/atom_sandboxed_renderer_client.h index 0912c6b24bd0..a8eae0ba8491 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.h +++ b/atom/renderer/atom_sandboxed_renderer_client.h @@ -7,23 +7,27 @@ #include #include -#include "content/public/renderer/content_renderer_client.h" -#include "content/public/renderer/render_frame.h" +#include "atom/renderer/renderer_client_base.h" namespace atom { -class AtomSandboxedRendererClient : public content::ContentRendererClient { +class AtomSandboxedRendererClient : public RendererClientBase { public: AtomSandboxedRendererClient(); virtual ~AtomSandboxedRendererClient(); - void DidCreateScriptContext( - v8::Handle context, content::RenderFrame* render_frame); - void WillReleaseScriptContext( - v8::Handle context, content::RenderFrame* render_frame); void InvokeIpcCallback(v8::Handle context, - std::string callback_name, + const std::string& callback_name, std::vector> args); + // atom::RendererClientBase: + void DidCreateScriptContext( + v8::Handle context, + content::RenderFrame* render_frame) override; + void WillReleaseScriptContext( + v8::Handle context, + content::RenderFrame* render_frame) override; + void SetupMainWorldOverrides(v8::Handle context) override { } + bool isolated_world() override { return false; } // content::ContentRendererClient: void RenderFrameCreated(content::RenderFrame*) override; void RenderViewCreated(content::RenderView*) override; diff --git a/atom/renderer/renderer_client_base.cc b/atom/renderer/renderer_client_base.cc new file mode 100644 index 000000000000..4d3675e2f6f6 --- /dev/null +++ b/atom/renderer/renderer_client_base.cc @@ -0,0 +1,197 @@ +// 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 "atom/renderer/renderer_client_base.h" + +#include +#include + +#include "atom/common/atom_constants.h" +#include "atom/common/color_util.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/options_switches.h" +#include "atom/renderer/atom_render_frame_observer.h" +#include "atom/renderer/content_settings_observer.h" +#include "atom/renderer/guest_view_container.h" +#include "atom/renderer/preferences_manager.h" +#include "base/command_line.h" +#include "base/strings/string_split.h" +#include "chrome/renderer/media/chrome_key_systems.h" +#include "chrome/renderer/pepper/pepper_helper.h" +#include "chrome/renderer/printing/print_web_view_helper.h" +#include "chrome/renderer/tts_dispatcher.h" +#include "content/public/common/content_constants.h" +#include "content/public/renderer/render_view.h" +#include "native_mate/dictionary.h" +#include "third_party/WebKit/public/web/WebCustomElement.h" +#include "third_party/WebKit/public/web/WebFrameWidget.h" +#include "third_party/WebKit/public/web/WebKit.h" +#include "third_party/WebKit/public/web/WebPluginParams.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" +#include "third_party/WebKit/public/web/WebSecurityPolicy.h" + +#if defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#include "base/strings/sys_string_conversions.h" +#endif + +#if defined(OS_WIN) +#include +#endif + +namespace atom { + +namespace { + +v8::Local GetRenderProcessPreferences( + const PreferencesManager* preferences_manager, v8::Isolate* isolate) { + if (preferences_manager->preferences()) + return mate::ConvertToV8(isolate, *preferences_manager->preferences()); + else + return v8::Null(isolate); +} + +std::vector ParseSchemesCLISwitch(const char* switch_name) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + std::string custom_schemes = command_line->GetSwitchValueASCII(switch_name); + return base::SplitString( + custom_schemes, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); +} + +} // namespace + +RendererClientBase::RendererClientBase() { + // Parse --standard-schemes=scheme1,scheme2 + std::vector standard_schemes_list = + ParseSchemesCLISwitch(switches::kStandardSchemes); + for (const std::string& scheme : standard_schemes_list) + url::AddStandardScheme(scheme.c_str(), url::SCHEME_WITHOUT_PORT); +} + +RendererClientBase::~RendererClientBase() { +} + +void RendererClientBase::AddRenderBindings( + v8::Isolate* isolate, + v8::Local binding_object) { + mate::Dictionary dict(isolate, binding_object); + dict.SetMethod( + "getRenderProcessPreferences", + base::Bind(GetRenderProcessPreferences, preferences_manager_.get())); +} + +void RendererClientBase::RenderThreadStarted() { + blink::WebCustomElement::addEmbedderCustomElementName("webview"); + blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); + + preferences_manager_.reset(new PreferencesManager); + +#if defined(OS_WIN) + // Set ApplicationUserModelID in renderer process. + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + base::string16 app_id = + command_line->GetSwitchValueNative(switches::kAppUserModelId); + if (!app_id.empty()) { + SetCurrentProcessExplicitAppUserModelID(app_id.c_str()); + } +#endif + +#if defined(OS_MACOSX) + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + bool scroll_bounce = command_line->HasSwitch(switches::kScrollBounce); + base::ScopedCFTypeRef rubber_banding_key( + base::SysUTF8ToCFStringRef("NSScrollViewRubberbanding")); + CFPreferencesSetAppValue(rubber_banding_key, + scroll_bounce ? kCFBooleanTrue : kCFBooleanFalse, + kCFPreferencesCurrentApplication); + CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); +#endif +} + +void RendererClientBase::RenderFrameCreated( + content::RenderFrame* render_frame) { + new AtomRenderFrameObserver(render_frame, this); + new PepperHelper(render_frame); + new ContentSettingsObserver(render_frame); + new printing::PrintWebViewHelper(render_frame); + + // Allow file scheme to handle service worker by default. + // FIXME(zcbenz): Can this be moved elsewhere? + blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file"); + + // This is required for widevine plugin detection provided during runtime. + blink::resetPluginCache(); + + // Allow access to file scheme from pdf viewer. + blink::WebSecurityPolicy::addOriginAccessWhitelistEntry( + GURL(kPdfViewerUIOrigin), "file", "", true); + + // Parse --secure-schemes=scheme1,scheme2 + std::vector secure_schemes_list = + ParseSchemesCLISwitch(switches::kSecureSchemes); + for (const std::string& secure_scheme : secure_schemes_list) + blink::WebSecurityPolicy::registerURLSchemeAsSecure( + blink::WebString::fromUTF8(secure_scheme)); +} + +void RendererClientBase::RenderViewCreated(content::RenderView* render_view) { + blink::WebFrameWidget* web_frame_widget = render_view->GetWebFrameWidget(); + if (!web_frame_widget) + return; + + base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); + if (cmd->HasSwitch(switches::kGuestInstanceID)) { // webview. + web_frame_widget->setBaseBackgroundColor(SK_ColorTRANSPARENT); + } else { // normal window. + // If backgroundColor is specified then use it. + std::string name = cmd->GetSwitchValueASCII(switches::kBackgroundColor); + // Otherwise use white background. + SkColor color = name.empty() ? SK_ColorWHITE : ParseHexColor(name); + web_frame_widget->setBaseBackgroundColor(color); + } +} + +void RendererClientBase::DidClearWindowObject( + content::RenderFrame* render_frame) { + // Make sure every page will get a script context created. + render_frame->GetWebFrame()->executeScript(blink::WebScriptSource("void 0")); +} + +blink::WebSpeechSynthesizer* RendererClientBase::OverrideSpeechSynthesizer( + blink::WebSpeechSynthesizerClient* client) { + return new TtsDispatcher(client); +} + +bool RendererClientBase::OverrideCreatePlugin( + content::RenderFrame* render_frame, + blink::WebLocalFrame* frame, + const blink::WebPluginParams& params, + blink::WebPlugin** plugin) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (params.mimeType.utf8() == content::kBrowserPluginMimeType || + params.mimeType.utf8() == kPdfPluginMimeType || + command_line->HasSwitch(switches::kEnablePlugins)) + return false; + + *plugin = nullptr; + return true; +} + +content::BrowserPluginDelegate* RendererClientBase::CreateBrowserPluginDelegate( + content::RenderFrame* render_frame, + const std::string& mime_type, + const GURL& original_url) { + if (mime_type == content::kBrowserPluginMimeType) { + return new GuestViewContainer(render_frame); + } else { + return nullptr; + } +} + +void RendererClientBase::AddSupportedKeySystems( + std::vector>* key_systems) { + AddChromeKeySystems(key_systems); +} + +} // namespace atom diff --git a/atom/renderer/renderer_client_base.h b/atom/renderer/renderer_client_base.h new file mode 100644 index 000000000000..c8958de9042b --- /dev/null +++ b/atom/renderer/renderer_client_base.h @@ -0,0 +1,58 @@ +// 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_RENDERER_RENDERER_CLIENT_BASE_H_ +#define ATOM_RENDERER_RENDERER_CLIENT_BASE_H_ + +#include +#include + +#include "content/public/renderer/content_renderer_client.h" + +namespace atom { + +class PreferencesManager; + +class RendererClientBase : public content::ContentRendererClient { + public: + RendererClientBase(); + virtual ~RendererClientBase(); + + virtual void DidCreateScriptContext( + v8::Handle context, content::RenderFrame* render_frame) = 0; + virtual void WillReleaseScriptContext( + v8::Handle context, content::RenderFrame* render_frame) = 0; + virtual void DidClearWindowObject(content::RenderFrame* render_frame); + virtual void SetupMainWorldOverrides(v8::Handle context) = 0; + virtual bool isolated_world() = 0; + + protected: + void AddRenderBindings(v8::Isolate* isolate, + v8::Local binding_object); + + // content::ContentRendererClient: + void RenderThreadStarted() override; + void RenderFrameCreated(content::RenderFrame*) override; + void RenderViewCreated(content::RenderView*) override; + blink::WebSpeechSynthesizer* OverrideSpeechSynthesizer( + blink::WebSpeechSynthesizerClient* client) override; + bool OverrideCreatePlugin(content::RenderFrame* render_frame, + blink::WebLocalFrame* frame, + const blink::WebPluginParams& params, + blink::WebPlugin** plugin) override; + content::BrowserPluginDelegate* CreateBrowserPluginDelegate( + content::RenderFrame* render_frame, + const std::string& mime_type, + const GURL& original_url) override; + void AddSupportedKeySystems( + std::vector>* key_systems) + override; + + private: + std::unique_ptr preferences_manager_; +}; + +} // namespace atom + +#endif // ATOM_RENDERER_RENDERER_CLIENT_BASE_H_ diff --git a/atom/utility/atom_content_utility_client.cc b/atom/utility/atom_content_utility_client.cc index 37fa7558712f..cc36293f4352 100644 --- a/atom/utility/atom_content_utility_client.cc +++ b/atom/utility/atom_content_utility_client.cc @@ -19,4 +19,16 @@ AtomContentUtilityClient::AtomContentUtilityClient() { AtomContentUtilityClient::~AtomContentUtilityClient() { } +bool AtomContentUtilityClient::OnMessageReceived( + const IPC::Message& message) { +#if defined(OS_WIN) + for (auto* handler : handlers_) { + if (handler->OnMessageReceived(message)) + return true; + } +#endif + + return false; +} + } // namespace atom diff --git a/atom/utility/atom_content_utility_client.h b/atom/utility/atom_content_utility_client.h index 0edc4d5d80c9..b4aa7960f6ff 100644 --- a/atom/utility/atom_content_utility_client.h +++ b/atom/utility/atom_content_utility_client.h @@ -20,6 +20,8 @@ class AtomContentUtilityClient : public content::ContentUtilityClient { AtomContentUtilityClient(); ~AtomContentUtilityClient() override; + bool OnMessageReceived(const IPC::Message& message) override; + private: #if defined(OS_WIN) typedef ScopedVector Handlers; diff --git a/chromium_src/chrome/browser/printing/print_view_manager_base.cc b/chromium_src/chrome/browser/printing/print_view_manager_base.cc index bc5d3f31f01d..07ec2992f338 100644 --- a/chromium_src/chrome/browser/printing/print_view_manager_base.cc +++ b/chromium_src/chrome/browser/printing/print_view_manager_base.cc @@ -159,6 +159,7 @@ void PrintViewManagerBase::OnDidPrintPage( ShouldQuitFromInnerMessageLoop(); #else + print_job_->AppendPrintedPage(params.page_number); if (metafile_must_be_valid) { bool print_text_with_gdi = document->settings().print_text_with_gdi() && diff --git a/default_app/index.html b/default_app/index.html index 3f29908a5635..41b5396360db 100644 --- a/default_app/index.html +++ b/default_app/index.html @@ -113,24 +113,6 @@ - -
- +
Docs @@ -162,25 +144,15 @@ Console (or Terminal):

- +

 
     

The path-to-your-app should be the path to your own Electron app.

-

You can read the - - guide in Electron's - +

You can read the quick start + guide in Electron's docs to learn how to write one.

@@ -195,25 +167,7 @@
diff --git a/default_app/renderer.js b/default_app/renderer.js new file mode 100644 index 000000000000..6196195eab93 --- /dev/null +++ b/default_app/renderer.js @@ -0,0 +1,45 @@ +const {remote, shell} = require('electron') +const {execFile} = require('child_process') + +const {execPath} = remote.process + +document.onclick = function (e) { + e.preventDefault() + if (e.target.tagName === 'A') { + shell.openExternal(e.target.href) + } + return false +} + +document.ondragover = document.ondrop = function (e) { + e.preventDefault() + return false +} + +const holder = document.getElementById('holder') +holder.ondragover = function () { + this.className = 'hover' + return false +} + +holder.ondragleave = holder.ondragend = function () { + this.className = '' + return false +} + +holder.ondrop = function (e) { + this.className = '' + e.preventDefault() + + const file = e.dataTransfer.files[0] + execFile(execPath, [file.path], { + detached: true, stdio: 'ignore' + }).unref() + return false +} + +const version = process.versions.electron +document.querySelector('.header-version').innerText = version +document.querySelector('.command-example').innerText = `${execPath} path-to-your-app` +document.querySelector('.quick-start-link').href = `https://github.com/electron/electron/blob/v${version}/docs/tutorial/quick-start.md` +document.querySelector('.docs-link').href = `https://github.com/electron/electron/tree/v${version}/docs#readme` diff --git a/docs-translations/ko-KR/api/protocol.md b/docs-translations/ko-KR/api/protocol.md index dc3bb51ba2f3..38a17bed14ff 100644 --- a/docs-translations/ko-KR/api/protocol.md +++ b/docs-translations/ko-KR/api/protocol.md @@ -1,6 +1,6 @@ # protocol -> 커스텀 프로토콜을 등록하거나 이미 존재하능 프로토콜의 요청의 동작을 변경합니다. +> 커스텀 프로토콜을 등록하거나 이미 존재하는 프로토콜의 요청의 동작을 변경합니다. 프로세스: [메인](../tutorial/quick-start.md#main-process) diff --git a/docs-translations/ko-KR/tutorial/windows-store-guide.md b/docs-translations/ko-KR/tutorial/windows-store-guide.md index 34d36d6bc544..23a4bbc32f63 100644 --- a/docs-translations/ko-KR/tutorial/windows-store-guide.md +++ b/docs-translations/ko-KR/tutorial/windows-store-guide.md @@ -67,8 +67,7 @@ npm install -g electron-windows-store │ └── atom.asar ├── snapshot_blob.bin ├── squirrel.exe -├── ui_resources_200_percent.pak -└── xinput1_3.dll +└── ui_resources_200_percent.pak ``` ## `electron-windows-store` 실행하기 diff --git a/docs-translations/tr-TR/README.md b/docs-translations/tr-TR/README.md index d9d8a1bdceb9..ac11a02f4d3a 100644 --- a/docs-translations/tr-TR/README.md +++ b/docs-translations/tr-TR/README.md @@ -5,12 +5,12 @@ Eğer öyleyse, atom.io üzerinden [mevcut sürümler](https://electron.atom.io/ ## SSS(Sıkça Sorulan Sorular) Bir problem(issue) bildirmeden önce sıkça sorulan sorulara göz atın: -* [Electron SSS](https://github.com/electron/electron/tree/master/docs/faq/electron-faq.md) +* [Electron SSS](https://github.com/electron/electron/blob/master/docs/faq.md) ## Klavuzlar -* [Desteklenen Platformlar ](https://github.com/electron/electron/tree/master/docs/tutorial/supported-platforms.md) -* [Uygulama Dağıtımı](https://github.com/electron/electron/tree/master/docs/tutorial/application-distribution.md) +* [Desteklenen Platformlar ](tutorial/supported-platforms.md) +* [Uygulama Dağıtımı](tutorial/application-distribution.md) * [Mac Uygulama Mağazası Başvuru Klavuzu](https://github.com/electron/electron/tree/master/docs/tutorial/mac-app-store-submission-guide.md) * [Uygulama Paketleme](https://github.com/electron/electron/tree/master/docs/tutorial/application-packaging.md) * [Native Node Modüllerini Kullanma](https://github.com/electron/electron/tree/master/docs/tutorial/using-native-node-modules.md) @@ -23,7 +23,7 @@ Bir problem(issue) bildirmeden önce sıkça sorulan sorulara göz atın: ## Eğitimler -* [Quick Start](https://github.com/electron/electron/tree/master/docs/tutorial/quick-start.md) +* [Hızlı Başlangıç](tutorial/quick-start.md) * [Desktop Environment Integration](https://github.com/electron/electron/tree/master/docs/tutorial/desktop-environment-integration.md) * [Online/Offline Event Detection](https://github.com/electron/electron/tree/master/docs/tutorial/online-offline-events.md) diff --git a/docs-translations/tr-TR/project/README.md b/docs-translations/tr-TR/project/README.md new file mode 100644 index 000000000000..d5be7773932c --- /dev/null +++ b/docs-translations/tr-TR/project/README.md @@ -0,0 +1,85 @@ +[![Electron Logo](https://electron.atom.io/images/electron-logo.svg)](https://electron.atom.io/) + +[![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) +[![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/) + +:memo: Mevcut çeviriler: [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) + +Electron framework JavaScript, HTML ve CSS kullanarak çapraz platform +masaüstü uygulamaları yazmanıza yarar. Electron [Node.js](https://nodejs.org/) ile geliştirilmiş; +[Atom editor](https://github.com/atom/atom) ve birçok uygulama [apps](https://electron.atom.io/apps) tarafından kullanılmaktadir. + +Önemli duyurular için Twitter da [@ElectronJS](https://twitter.com/electronjs) adresini takip edin. + +Bu proje katılımcı sözleşmesine bağlıdır. Katılarak, +bu kodun sürdürülebilir olduğunu üstlenmeniz beklenmekte. +Lütfen uygun olmayan davranışları electron@github.com'a rapor edin. + +## Downloads + +Electron prebuilt mimarisini yüklemek için, +[`npm`](https://docs.npmjs.com/): + +```sh +# Development dependency olarak yükleyin +npm install electron --save-dev + +# `electron` komutunu global olarak $PATH'a yükleyin +npm install electron -g +``` + +Prebuilt mimarileri, debug sembolleri, ve fazlası için +[releases page](https://github.com/electron/electron/releases) sayfasını ziyaret edin. + +### Mirrors + +- [China](https://npm.taobao.org/mirrors/electron) + +## Dokümantasyon + +Klavuz ve API referansları [docs](https://github.com/electron/electron/tree/master/docs) klasöründe bulunabilir. +Aynı zamanda nasıl kurulum gerçekleştirileceği ve Electron'un gelişimine nasıl katılacağınızı +açıklayan dosyalar içermektedir. + +## Dökümantasyon Çevirileri + +- [Brazilian Portuguese](https://github.com/electron/electron/tree/master/docs-translations/pt-BR) +- [Korean](https://github.com/electron/electron/tree/master/docs-translations/ko-KR) +- [Japanese](https://github.com/electron/electron/tree/master/docs-translations/jp) +- [Spanish](https://github.com/electron/electron/tree/master/docs-translations/es) +- [Simplified Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-CN) +- [Traditional Chinese](https://github.com/electron/electron/tree/master/docs-translations/zh-TW) +- [Turkish](https://github.com/electron/electron/tree/master/docs-translations/tr-TR) +- [Thai](https://github.com/electron/electron/tree/master/docs-Translations/th-TH) +- [Ukrainian](https://github.com/electron/electron/tree/master/docs-translations/uk-UA) +- [Russian](https://github.com/electron/electron/tree/master/docs-translations/ru-RU) +- [French](https://github.com/electron/electron/tree/master/docs-translations/fr-FR) + +## Hızlı Başlangıç + +Minimal Electron uygulamasını calışırken görmek için [`electron/electron-quick-start`](https://github.com/electron/electron-quick-start) +repository'ni klonla ve çalıştır. + +## Topluluk + +Asağıdaki sayfalardan sorular sorabilir ve topluluk ile etkileşime geçebilirsiniz: + +- [`electron`](http://discuss.atom.io/c/electron) Atom forumundaki kategoriler +- `#atom-shell` Freenode kanal'ı +- [`Atom`](http://atom-slack.herokuapp.com/) Slack kanal'ı +- [`electron-br`](https://electron-br.slack.com) *(Brazilian Portuguese)* +- [`electron-kr`](http://www.meetup.com/electron-kr/) *(Korean)* +- [`electron-jp`](https://electron-jp.slack.com) *(Japanese)* +- [`electron-tr`](http://www.meetup.com/Electron-JS-Istanbul/) *(Turkish)* +- [`electron-id`](https://electron-id.slack.com) *(Indonesia)* + +Topluluk tarafından sağlanan örnek uygulamaları, aracları ve kaynaklara ulaşmak için +[awesome-electron](https://github.com/sindresorhus/awesome-electron) sayfasını ziyaret et. + +## Lisans + +[MIT](https://github.com/electron/electron/blob/master/LICENSE) + +Electron veya Github logolarını kullandığınızda, [GitHub logo guidelines](https://github.com/logos) sayfasını okuduğunuzdan emin olun. diff --git a/docs-translations/tr-TR/tutorial/application-distribution.md b/docs-translations/tr-TR/tutorial/application-distribution.md new file mode 100644 index 000000000000..319f8af787e5 --- /dev/null +++ b/docs-translations/tr-TR/tutorial/application-distribution.md @@ -0,0 +1,178 @@ +# Application Distribution + +Electron uygulamanızı dağıtmak için önce Electron nun [prebuilt mimarilerini] +(https://github.com/electron/electron/releases) indirmeniz gerekmektedir. +Sonrasında, uygulamanızın bulundugu klasör `app` şeklinde isimlendirilmeli ve +Electron kaynaklar klasörüne aşagıda gösterildiği gibi yerleştirilmelidir. +Unutmayın, Electronun prebuilt mimarileri aşağıdaki örneklerde `electron/` +şeklinde belirtilmiştir. + + +MacOS da: + +```text +electron/Electron.app/Contents/Resources/app/ +├── package.json +├── main.js +└── index.html +``` + +Windows ve Linux da: + +```text +electron/resources/app +├── package.json +├── main.js +└── index.html +``` + +Ardından `Electron.app` (veya `electron` Linux'da, `electron.exe` Windows'da) şeklinde çalıstırın, +ve Electron uygulama şeklinde çalışacaktır. +`electron` klasörü son kullanıcıya aktaracağınız dağıtımınız olacaktır. + +## Uygulamanın bir dosya şeklinde paketlenmesi + +Tüm kaynak kodlarını kopyalama yoluyla uygulamanızı dağıtmak haricinde, +uygulamanızı [asar](https://github.com/electron/asar) ile arşiv haline getirerek, +kaynak kodlarınızın kullanıcılar tarafından görülmesini engelliye bilirsiniz. + +`app` klasörü yerine `asar` arşiv dosyası kullanmak için, arşiv dosyanızı `app.asar` +şeklinde isimlendirmeniz gerekiyor, ve bu dosyayı Electron'nun kaynak klasörüne aşağıdaki +gibi yerleştirmelisiniz. Böylelikle Electron arşivi okuyup ondan başlayacaktır. + + +MacOS'da: + +```text +electron/Electron.app/Contents/Resources/ +└── app.asar +``` + +Windows ve Linux'da: + +```text +electron/resources/ +└── app.asar +``` + +Daha fazla bilgi için [Application packaging](application-packaging.md). + +## İndirilen mimarileri yeniden adlandırma + +Uygulamanızı Electron ile paketledikten sonra ve kullanıcılara uygulamanızı dağıtmadan önce +adını değiştirmek isteye bilirsiniz. + +### Windows + +`electron.exe` istediğiniz şekilde yeniden adlandırabilirsiniz. Icon ve diğer +bilgileri bu gibi araçlar [rcedit](https://github.com/atom/rcedit) ile düzenleye bilirsiniz. + +### macOS + +`Electron.app`'i istediğiniz şekilde yeniden adlandırabilirsiniz, ve aşağıdaki dosyalarda +`CFBundleDisplayName`, `CFBundleIdentifier` ve `CFBundleName` kısımlarınıda düzenlemelisiniz. + +* `Electron.app/Contents/Info.plist` +* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist` + +Görev yöneticisinde `Electron Helper` şeklinde göstermek yerine, +isterseniz helper uygulamasınında adını değiştire bilirsiniz, +ancak dosyanın adını açılabilir olduğundan emin olun. + +Yeniden adlandırılmış uygulamanın klasör yapısı bu şekilde görünecektir: + +``` +MyApp.app/Contents +├── Info.plist +├── MacOS/ +│   └── MyApp +└── Frameworks/ + ├── MyApp Helper EH.app + | ├── Info.plist + | └── MacOS/ + |    └── MyApp Helper EH + ├── MyApp Helper NP.app + | ├── Info.plist + | └── MacOS/ + |    └── MyApp Helper NP + └── MyApp Helper.app + ├── Info.plist + └── MacOS/ +    └── MyApp Helper +``` + +### Linux + +`electron` dosyasını istediğiniz şekilde yeniden adlandırabilirsiniz. + +## Paketleme Araçları + +Uygulamanızı manuel şekilde paketlemek dışında, üçüncü parti +paketleme araçlarıylada otomatik olarak ayni şekilde paketliye bilirsiniz: + +* [electron-builder](https://github.com/electron-userland/electron-builder) +* [electron-packager](https://github.com/electron-userland/electron-packager) + +## Kaynaktan yeniden kurulum yoluyla isim değişikliği + +Ürün adını değiştirip, kaynaktan kurulum yoluylada Electron'nun adını değiştirmek mümkün. +Bunun için `atom.gyp` dosyasını yeniden modifiye edip, tekrardan temiz bir kurulum yapmalısınız. + +### grunt-build-atom-shell + +Manuel olarak Electron kodlarını kontrol edip tekrar kurulum yapmak biraz zor olabilir, +bu yüzden tüm bu işlemleri otomatik olarak gerçekleştirecek bir Grunt görevi oluşturuldu: +[grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). + +Bu görev otomatik olarak `.gyp` dosyasını düzenleyecek, kaynaktan kurulumu gerçekleştirecek, +sonrasında ise uygulamanızın doğal Node modüllerini, yeni yürütülebilen isim ile eşleştirmek icin +tekrardan kuracaktır. + +### Özel bir Electron kopyası oluşturma + +Electron'un size ait bir kopyasını oluşturmak, neredeyse uygulamanızı kurmak için hiç ihtiyacınız +olmayacak bir işlemdir, "Production Level" uygulamalarda buna dahildir. +`electron-packager` veya `electron-builder` gibi araçlar kullanarak yukarıda ki işlemleri +gerçekleştirmeksizin, "Rebrand" Electron işlemini uygulaya bilirsiniz. + +Eğer kendinize ait yüklenemiyen veya resmi versiyondan red edilmiş, +direk olarak Electron a paketlediğiniz C++ kodunuz var ise, +öncelikle Electron'un bir kopyasını oluşturmalısınız. +Electron'nun destekleyicileri olarak, senaryonuzun çalışmasını çok isteriz, +bu yüzden lütfen yapacağınız değişiklikleri Electron'nun resmi versiyonuna +entegre etmeye calışın, bu sizin için daha kolay olacaktır, ve yardimlarınız +için cok minnettar olacağız. + +#### surf-build İle Özel Dağıtım oluşturulması + +1. Npm yoluyla [Surf](https://github.com/surf-build/surf) yükleyin: + `npm install -g surf-build@latest` + + +2. Yeni bir S3 bucket ve aşağıdakı boş klasör yapısını oluşturun: + + ``` + - atom-shell/ + - symbols/ + - dist/ + ``` + +3. Aşağıdaki Ortam Değişkenlerini ayarlayın: + + * `ELECTRON_GITHUB_TOKEN` - GitHub üzerinden dağıtım oluşturan token + * `ELECTRON_S3_ACCESS_KEY`, `ELECTRON_S3_BUCKET`, `ELECTRON_S3_SECRET_KEY` - + node.js bağlantılarını ve sembollerini yükleyeceğiniz yer + * `ELECTRON_RELEASE` - `true` şeklinde ayarlayın ve yükleme işlemi çalışacaktır, + yapmamanız halinde, `surf-build` sadece CI-type kontrolü yapacak, + tüm pull isteklerine uygun hale getirecektir. + * `CI` - `true` olarak ayarlayın yoksa çalışmayacaktır. + * `GITHUB_TOKEN` - bununla aynı şekilde ayarlayın `ELECTRON_GITHUB_TOKEN` + * `SURF_TEMP` - Windowsda ki 'path too long' sorunundan kaçınmak için `C:\Temp` şeklinde ayarlayın + * `TARGET_ARCH` - `ia32` veya `x64` şeklinde ayarlayın + +4. `script/upload.py` dosyasında ki `ELECTRON_REPO` kısmını, kendi kopyanız ile değiştirmek _zorundasınız_, + özellikle eğer bir Electron proper destekleyicisi iseniz. + +5. `surf-build -r https://github.com/MYORG/electron -s YOUR_COMMIT -n 'surf-PLATFORM-ARCH'` + +6. Kurulum bitene kadar uzunca bekleyin. diff --git a/docs-translations/tr-TR/tutorial/quick-start.md b/docs-translations/tr-TR/tutorial/quick-start.md new file mode 100644 index 000000000000..1941060df2a9 --- /dev/null +++ b/docs-translations/tr-TR/tutorial/quick-start.md @@ -0,0 +1,252 @@ +# Hızlı Başlangıç + +Electron, zengin native(işletim sistemi) API runtime sağlayarak, saf Javascript +ile masaüstü uygulamalar geliştirmenize yarar. Electron'u Node.js in, web serverları +yerine masaüstü uygulamalara odaklanmış bir variyasyonu olarak kabul edebilirsiniz. + +Bu Electronun, grafik kullanıcı arayüzüne bir JavaScript bağlantısı olduğu +anlamına gelmez. Aksine, Electron web sayfalarını GUI'si olarak kullanır, +yani onu Javascript tarafından kontrol edilen bir minimal Chromium tarayıcısı +olarak görebilirsiniz. + +### Ana İşlem + +Electron da, `package.json` nun `main` skriptini cağıran işlem _the main process__ dir. +Ana işlemde çalışan bu script, GUI'yi web sayfalarını oluşturarak gösterebilir. + +### Render İşlemi + +Electron, web sayfalarını görüntülemek için Chromium kullandığından, +aynı zamanda Chromiumun multi-işlem mimarisinide kullanmaktadır. +Electron da calıştırılan her web sayfası, __the renderer process__ +adı altında kendi işlemlerini çalıştırırlar. + +Normal tarayıcılarda, web sayfaları genellikle korumalı bir ortamda çalışır ve +yerel kaynaklara erişmesine izin verilmez. Bununla birlikte, elektron kullanıcıları, +alt düzey işletim sistemi etkileşimlerine izin veren web sayfalarında +Node.js API'lerini kullanma imkanina sahiplerdir. + +### Ana işlem ile render işlemi arasındaki farklar + +Ana işlem, `BrowserWindow` örneklerini oluşturarak, web sayfalarını hazır +hale getirir. Her bir `BrowserWindow` örneği web sayfasını kendi render +işleminde çalıştırır. Eger bir `BrowserWindow` örneği ortadan kaldırıldıysa, +bununla bağlantılı olan render işlemide aynı şekilde sonlandırılır. + +Ana işlem tüm web sayfaları ve onların ilgili olduğu render işlemlerini yönetir. +Her bir render işlemi izole edilmiş ve sadece kendisinde çalışan web sayfasıyla ilgilenir. + +Native GUI ile çalışan API ları web sayfalarında çalıştırmaya izin verilmemektedir, +çünkü native GUI kaynaklarının web sayfalarında yönetimi çok tehlikeli ve +kaynakların sızdırılması gayet kolaydır. Eğer GUI operasyonlarını bir web sayfasinda +gerçekleştirmek istiyorsanız, web sayfasının render işlemi, ana işlem ile, bu tür +işlemleri gerçekleştirilmesini talep etmek için kommunikasyon halinde olmalı. + +Electron da ana işlem ve render işlemi arasında birden fazla kommunikasyon yolu vardır. +[`ipcRenderer`](../api/ipc-renderer.md) gibi ve mesaj gönderimi icin +[`ipcMain`](../api/ipc-main.md) modülleri, RPC tarzında kommunikasyon +için ise [remote](../api/remote.md) modülü barındırmakta. +Ayrıca SSS başlıkları [how to share data between web pages][share-data] adresinde bulunabilir. + +## İlk Electron uygulamanızı yazın + +Electron uygulaması genellikle aşağıdaki gibi yapılandırılmıştır: + +```text +your-app/ +├── package.json +├── main.js +└── index.html +``` + +`package.json` dosyasının formatı tamamen Node modüllerine benzer veya aynıdır ve +`main` şeklinde adlandırılmış script uygulamanızı başlatan komut dosyasıdır, +bu komut dosyası daha sonra main process'i çalıştıracak dosyadır. +`package.json` dosyasınızın bir örneği aşağıdaki gibi olabilir: + + +```json +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +__Note__: Eğer `package.json` dosyasında `main` kısmı bulunmuyorsa, Electron standart olarak +`index.js` dosyasını cağıracaktır. + +`main.js` dosyası pencereleri oluşturur, sistem durumlarını handle eder, tipik bir +örnek asağıdaki gibidir: + +```javascript +const {app, BrowserWindow} = require('electron') +const path = require('path') +const url = require('url') + +// Pencere objesini daima global referans olarak tanımla, aksi takdirde, +// eğer JavaScript objesi gereksiz veriler toplayacağı için, pencere +// otomatik olarak kapanacaktır. + +let win + +function createWindow () { + // Tarayıcı pencerelerini oluşturur. + win = new BrowserWindow({width: 800, height: 600}) + + // ve uygulamanın index.html sayfasını yükler. + win.loadURL(url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true + })) + + // DevTools her uygulama başlatıldığında açılır. + + win.webContents.openDevTools() + + // Pencere kapandıktan sonra çağrılacaktır. + win.on('closed', () => { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + win = null + }) +} + +// Bu metod Electronun başlatılması tamamlandıktan sonra +// çagrılacak ve yeni tarayıcı pencereleri açmaya hazır hale gelecektir. +// Bazı API lar sadece bu event gerçekleştikten sonra kullanılabilir. + +app.on('ready', createWindow) + +// Eğer tüm pencereler kapandıysa, çıkış yap. + +app.on('window-all-closed', () => { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', () => { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (win === null) { + createWindow() + } +}) + +// Bu sayfada, uygulamanızın spesifik main process kodlarını dahil edebilirsiniz. +// Aynı zamanda bu kodları ayrı dosyalar halinde oluştura bilir +// ve buraya require yoluyla ekleye bilirsiniz. + +``` + +Son olarak `index.html` yani göstermek istediğiniz web sayfası: + +```html + + + + + Hello World! + + +

Hello World!

+ We are using node , + Chrome , + and Electron . + + +``` + +## Uygulamanızı çalıştırın + +`main.js`, `index.html`, ve `package.json` dosyalarını oluşturduktan sonra, +uygulamanızı lokal olarak test ederek, doğru çalışıp çalışmadığını +test etmek isteye bilirsiniz. O halde aşağıdaki yönergeleri takip edin: + +### `electron` + +[`electron`](https://github.com/electron-userland/electron-prebuilt), +Electron'un pre-compiled versiyonunu içeren bir `npm` modülüdür. + + +Eğer bunu global olarak `npm` yoluyla yüklediyseniz, o halde sadece aşağıdaki komutu +uygulamanızın kaynak klasöründe çalıstırmanız yeterlidir: + +```bash +electron . +``` + +Eğer lokal olarak yüklediyseniz, o zaman aşağıda ki gibi +çalıştırın: + +#### macOS / Linux + +```bash +$ ./node_modules/.bin/electron . +``` + +#### Windows + +```bash +$ .\node_modules\.bin\electron . +``` + +### Manuel olarak indirilmiş Electron mimarisi + +Eğer Electronu manuel olarak indirdiyseniz, aynı zamanda dahili olan +mimariyide kullanarak, uygulamanızı çalıştıra bilirsiniz. + +#### Windows + +```bash +$ .\electron\electron.exe your-app\ +``` + +#### Linux + +```bash +$ ./electron/electron your-app/ +``` + +#### macOS + +```bash +$ ./Electron.app/Contents/MacOS/Electron your-app/ +``` + +`Electron.app` Electron un dağı₺tım paketinin bir parçasıdır, +bunu [adresinden](https://github.com/electron/electron/releases) indirebilirsiniz. + +### Dağıtım olarak çalıştır + +Uygulamanızı yazdıktan sonra, bir dağıtım oluşturmak için +[Application Distribution](./application-distribution.md) +sayfasında ki yönergeleri izleyin ve daha sonra arşivlenmiş uygulamayı çalıştırın. + +### Örneği deneyin + +[`electron/electron-quick-start`](https://github.com/electron/electron-quick-start) repository klonlayarak bu eğitimdeki kodu çalıştıra bilirsiniz. + +**Note**: Bu işlemleri uygulamak için [Git](https://git-scm.com) ve [Node.js](https://nodejs.org/en/download/) ([npm](https://npmjs.org) da bununla birlikte gelir) sisteminizde yüklü olması gerekmektedir. + +```bash +# Repository klonla +$ git clone https://github.com/electron/electron-quick-start +# Electron repositorye git +$ cd electron-quick-start +# Gerekli kütüphaneleri yükle +$ npm install +# Uygulamayı çalıştır +$ npm start +``` + +Daha fazla örnek uygulama için, harika electron topluluğu tarafından oluşturulan, +[list of boilerplates](https://electron.atom.io/community/#boilerplates) +sayfasını ziyaret edin. + +[share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/tr-TR/tutorial/supported-platforms.md b/docs-translations/tr-TR/tutorial/supported-platforms.md new file mode 100644 index 000000000000..4cc131b103a5 --- /dev/null +++ b/docs-translations/tr-TR/tutorial/supported-platforms.md @@ -0,0 +1,29 @@ +# Desteklenen platformlar + +Aşağıdaki platformlar Electron tarafından desteklenmektedir: + +### macOS + +MacOS için sadece 64bit mimariler sağlanmakta olup, desteklenen minimum macOS versiyonu 10.9 dur. + +### Windows + +Windows 7 ve gelişmiş versiyonlar desteklenmektedir, eski işletim sistemleri desteklenmemektedir +(ve çalışmayacaktır). + +Windows `ia32` (`x86`) ve `x64` (`amd64`) mimarileri desteklenmektedir. +Unutmayın, `ARM` mimarisi al₺tında çalışan Windows işletim sistemleri şuan için desteklenmemektedir. + +### Linux + +Electron `ia32` (`i686`) ve `x64` (`amd64`) Prebuilt mimarileri Ubuntu 12.04 üzerinde kurulmuştur, +`arm` mimarisi ARM v7 ye karşılık olarak, hard-float ABI ve NEON Debian Wheezy ile kurulmuştur. + +Prebuilt +Prebuilt mimarisi ancak Electron'nun yapı platformu ile bağlantılı olan kütüphaneleri içeren dağıtımlar ile çalışır. +Bu yüzden sadece Ubuntu 12.04 üzerinde çalışması garanti ediliyor, fakat aşagidaki platformlarında +Electron Prebuilt mimarisini çalıştıra bileceği doğrulanmıştır. + +* Ubuntu 12.04 ve sonrası +* Fedora 21 +* Debian 8 diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index 8d1fd8500d70..706738d60d2c 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -26,6 +26,7 @@ * [使用 Widevine CDM 插件](tutorial/using-widevine-cdm-plugin.md) * [通过自动化持续集成系统(CI)进行测试 (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) * [离屏渲染](tutorial/offscreen-rendering.md) +* [快捷键](tutorial/keyboard-shortcuts.md) ## 教程 @@ -96,3 +97,6 @@ * [调试步骤 (Windows)](development/debug-instructions-windows.md) * [在调试中使用 Symbol Server](development/setting-up-symbol-server.md) * [文档风格指南](styleguide.md) +* [升级 Chrome](development/upgrading-chrome.md) +* [Chromium 开发](development/chromium-development.md) +* [V8 开发](development/v8-development.md) diff --git a/docs-translations/zh-CN/api/accelerator.md b/docs-translations/zh-CN/api/accelerator.md index 0b1e2cd3f4df..0e6d44b742be 100644 --- a/docs-translations/zh-CN/api/accelerator.md +++ b/docs-translations/zh-CN/api/accelerator.md @@ -4,15 +4,30 @@ 例如: -* `Command+A` -* `Ctrl+Shift+Z` +* `CommandOrControl+A` +* `CommandOrControl+Shift+Z` + +快捷键使用 [`globalShortcut`](global-shortcut.md)里的 [`register`](global-shortcut.md#globalshortcutregisteraccelerator-callback) 方法注册 + +```javascript +const {app, globalShortcut} = require('electron') + +app.on('ready', () => { + // Register a 'CommandOrControl+Y' shortcut listener. + globalShortcut.register('CommandOrControl+Y', () => { + // Do stuff when Y and either Command/Control is pressed. + }) +}) +``` ## 运行平台相关的提示 在 Linux 和 Windows 上,`Command` 键并不存在,因此我们通常用 `CommandOrControl` 来表示“在 macOS 下为 `Command` 键,但在 Linux 和 Windows 下为 `Control` 键。 -`Super` 键是指 Linux 和 Windows 上的 `Windows` 键,但是在 macOS 下为 `Command` 键。 +使用 `Alt` 键 代替 `Option`。`Option` 键只在 macOS 系统上存在,而 `Alt` 键在任何系统上都有效。 + +`Super` 键是指 Linux 和 Windows 上的 `Windows` 键,但是在 macOS 下为 `Cmd` 键。 ## 可用的功能按键 @@ -20,6 +35,8 @@ Linux 和 Windows 下为 `Control` 键。 * `Control`(缩写为 `Ctrl`) * `CommandOrControl`(缩写为 `CmdOrCtrl`) * `Alt` +* `Option` +* `AltGr` * `Shift` * `Super` @@ -31,6 +48,7 @@ Linux 和 Windows 下为 `Control` 键。 * 类似与 `~`、`!`、`@`、`#`、`$` 的标点符号。 * `Plus` * `Space` +* `Tab` * `Backspace` * `Delete` * `Insert` @@ -41,3 +59,4 @@ Linux 和 Windows 下为 `Control` 键。 * `Escape`(缩写为 `Esc`) * `VolumeUp`、`VolumeDown` 和 `VolumeMute` * `MediaNextTrack`、`MediaPreviousTrack`、`MediaStop` 和 `MediaPlayPause` +* `PrintScreen` diff --git a/docs-translations/zh-CN/api/app.md b/docs-translations/zh-CN/api/app.md index 99429119f3f6..04680d548ba5 100644 --- a/docs-translations/zh-CN/api/app.md +++ b/docs-translations/zh-CN/api/app.md @@ -356,6 +356,27 @@ Windows, 使应用的第一个窗口获取焦点. * `pictures` 用户图片目录的路径. * `videos` 用户视频目录的路径. +### `app.getFileIcon(path[, options], callback)` +* `path` String +* `options` Object(可选) + * `size` String + * `small` - 16x16 + * `normal` - 32x32 + * `large` - Linux 为 48x48, Windows 为 32x32, Mac 系统不支持 +* `callback` Function + * `error` Error + * `icon` [NativeImage](native-image.md) + + +获取文件关联的图标. + +在 Windows 系统中, 有2种图标类型: + +- 图标与某些文件扩展名关联, 比如 `.mp3`, `.png`, 等等. +- 图标在文件内部, 比如 `.exe`, `.dll`, `.ico`. + +在 Linux 和 Mac 系统中, 图标取决于应用程序相关文件的 mime 类型 + ### `app.setPath(name, path)` * `name` String diff --git a/docs-translations/zh-CN/api/dialog.md b/docs-translations/zh-CN/api/dialog.md index 702b6ea3e2bf..41ec25672196 100644 --- a/docs-translations/zh-CN/api/dialog.md +++ b/docs-translations/zh-CN/api/dialog.md @@ -107,20 +107,23 @@ console.log(dialog) * `options` Object * `type` String - 可以是 `"none"`, `"info"`, `"error"`, `"question"` 或 `"warning"`. 在 Windows, "question" 与 "info" 展示图标相同, 除非你使用 "icon" 参数. - * `buttons` Array - buttons 内容,数组. - * `defaultId` Integer - 在message box 对话框打开的时候,设置默认button选中,值为在 buttons 数组中的button索引. - * `title` String - message box 的标题,一些平台不显示. - * `message` String - message box 内容. - * `detail` String - 额外信息. - * `icon` [NativeImage](native-image.md) - * `cancelId` Integer - 当用户关闭对话框的时候,不是通过点击对话框的button,就返回值.默认值为对应 "cancel" 或 "no" 标签button 的索引值, 或者如果没有这种button,就返回0. 在 macOS 和 Windows 上, "Cancel" button 的索引值将一直是 `cancelId`, 不管之前是不是特别指出的. - * `noLink` Boolean - 在 Windows ,Electron 将尝试识别哪个button 是普通 button (如 "Cancel" 或 "Yes"), 然后在对话框中以链接命令(command links)方式展现其它的 button . 这能让对话框展示得很炫酷.如果你不喜欢这种效果,你可以设置 `noLink` 为 `true`. - * `callback` Function (可选) - * `response` Number - The index of the button that was clicked + * `buttons` String[]- (可选) - 按钮上文字的数组,在 Windows 系统中,空数组在按钮上会显示 “OK”. + * `defaultId` Integer (可选) - 在 message box 对话框打开的时候,设置默认选中的按钮,值为在 buttons 数组中的索引. + * `title` String (可选) - message box 的标题,一些平台不显示. + * `message` String (可选) - message box 的内容. + * `detail` String (可选)- 额外信息. + * `checkboxLabel` String (可选) - 如果有该参数,message box 中会显示一个 checkbox 复选框,它的勾选状态可以在 `callback` 回调方法中获取。 + * `checkboxChecked` Boolean (可选) - checkbox 的初始值,默认为`false`. + * `icon` [NativeImage](native-image.md)(可选) + * `cancelId` Integer - 当用户不是通过按钮而是使用其他方式关闭对话框时,比如按`Esc`键,就返回该值.默认值为对应 "cancel" 或 "no" 标签 button 的索引值, 如果没有这种 button,就返回0. 该选项在 Windows 上无效. + * `noLink` Boolean(可选) - 在 Windows 系统中,Electron 将尝试识别哪个button 是普通 button (如 "Cancel" 或 "Yes"), 然后在对话框中以链接命令(command links)方式展现其它的 button . 这能让对话框展示得很炫酷.如果你不喜欢这种效果,你可以设置 `noLink` 为 `true`. +* `callback` Function (可选) + * `response` Number - 被点击按钮的索引值。 + * `checkboxChecked` Boolean - 如果设置了 `checkboxLabel` ,会显示 checkbox 的选中状态,否则显示 `false` 返回 `Integer`,如果提供了回调,它会返回点击的按钮的索引或者 undefined 。 -展示 message box, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值。 +显示 message box 时, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值。 `browserWindow` 参数允许对话框将自身附加到父窗口,使其成为模态。 diff --git a/docs-translations/zh-CN/api/menu-item.md b/docs-translations/zh-CN/api/menu-item.md index c2091674a52a..75c3b935fd84 100644 --- a/docs-translations/zh-CN/api/menu-item.md +++ b/docs-translations/zh-CN/api/menu-item.md @@ -15,26 +15,24 @@ * `menuItem` MenuItem * `browserWindow` BrowserWindow * `event` Event - * `role` String (可选) - 定义菜单项的行为,在指定 `click` 属性时将会被忽略。 - * `type` String (可选) - 取值 `normal`, `separator`, `submenu`, `checkbox` or `radio`。 + * `role` String (可选) - 定义菜单项的行为,在指定 `click` 属性时将会被忽略。参见 [roles](#roles). + * `type` String (可选) - 取值 `normal`, `separator`, `submenu`, `checkbox` 或 `radio`。 * `label` String - (可选) * `sublabel` String - (可选) * `accelerator` [Accelerator](accelerator.md) (可选) * `icon` ([NativeImage](native-image.md) | String) (可选) * `enabled` Boolean (可选) - 如果为 false,菜单项将显示为灰色不可点击。 - unclickable. * `visible` Boolean (可选) - 如果为 false,菜单项将完全隐藏。 * `checked` Boolean (可选) - 只为 `checkbox` 或 `radio` 类型的菜单项。 * `submenu` (MenuItemConstructorOptions[] | Menu) (可选) - 应当作为 `submenu` 菜单项的特定类型,当它作为 `type: 'submenu'` 菜单项的特定类型时可以忽略。如果它的值不是 `Menu`,将自动转为 `Menu.buildFromTemplate`。 - * `id` String (可选) - 标志一个菜单的唯一性。如果被定义使用,它将被用作这个菜单项的参考位置属性。 + * `id` String (可选) - 菜单的唯一标识。如果被定义使用,它将被用作这个菜单项的参考位置属性。 * `position` String (可选) - 定义菜单的具体指定位置信息。 -在创建菜单项时,如果有匹配的方法,建议指定 `role` 属性, -而不是试图手动实现在一个 `click` 函数中的行为。 -内置的 `role` 行为将提供最好的原生体验。 +### Roles +Roles 允许菜单项有预定义的行为。最好为每个菜单项指定一个行为,而不是自己实现一个 `click` 函数中的行为。内置的 `role` 行为将提供最好的原生体验。 + +当使用 `role` 时,`label` 和 `accelerator` 的值是可选的,会针对每个平台设置默认值。 -当使用 `role' 时,`label' 和 `accelerator` 是可选的,默认为 -到每个平台的适当值。 `role`属性值可以为: @@ -56,6 +54,8 @@ * `resetzoom` - 将对焦页面的缩放级别重置为原始大小 * `zoomin` - 将聚焦页面缩小10% * `zoomout` - 将聚焦页面放大10% +* `editMenu` - 完整的默认 "Edit" 编辑菜单(拷贝,黏贴,等) +* `windowMenu` - 完整的默认 "Window" 窗口菜单(最小化,关闭,等) 在 macOS 上,`role` 还可以有以下值: diff --git a/docs-translations/zh-CN/api/menu.md b/docs-translations/zh-CN/api/menu.md index 5d31d62f1a95..ebccfe5ee93d 100644 --- a/docs-translations/zh-CN/api/menu.md +++ b/docs-translations/zh-CN/api/menu.md @@ -19,6 +19,8 @@ 在 macOS 上设置应用菜单 `menu`。 在 windows 和 linux,是为每个窗口都在其顶部设置菜单 `menu`。 +设置为 `null` 时,将在 Windows 和 Linux 上删除菜单条,但在 macOS 系统中无效。 + **注意:** 这个API必须在 `app` 模块的 `ready` 事件后调用。 #### `Menu.getApplicationMenu()` @@ -29,7 +31,7 @@ * `action` String -发送 `action` 给应用的第一个响应器.这个用来模仿 Cocoa 菜单的默认行为,通常你只需要使用 `MenuItem` 的属性 `role`. +发送 `action` 给应用的第一个响应器.这个用来模仿 Cocoa 菜单的默认行为,通常你只需要使用 [`MenuItem`](menu-item.md) 的属性 [`role`](menu-item.md#roles). 查看更多 macOS 的原生 action [macOS Cocoa Event Handling Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW7) . @@ -47,15 +49,23 @@ `menu` 对象有如下实例方法 -#### `menu.popup([browserWindow, x, y, positioningItem])` +#### `menu.popup([browserWindow, options])` -* `browserWindow` BrowserWindow (可选) - 默认为 `null`. -* `x` Number (可选) - 默认为 -1. -* `y` Number (**必须** 如果x设置了) - 默认为 -1. -* `positioningItem` Number (可选) _macOS_ - 在指定坐标鼠标位置下面的菜单项的索引. 默认为 +* `browserWindow` BrowserWindow (可选) - 默认为当前激活的窗口. +* `options` Object (可选) + * `x` Number (可选) - 默认为当前光标所在的位置. + * `y` Number (**必须** 如果x设置了) - 默认为当前光标所在的位置. + * `async` Boolean (可选) - 设置为 `true` 时,调用这个方法会立即返回。设置为 `false` 时,当菜单被选择或者被关闭时才会返回。默认为 `false`。 + * `positioningItem` Number (可选) _macOS_ - 指定坐标鼠标位置下面的菜单项的索引. 默认为 -1. -在 `browserWindow` 中弹出 context menu .你可以选择性地提供指定的 `x, y` 来设置菜单应该放在哪里,否则它将默认地放在当前鼠标的位置. +在 `browserWindow` 中弹出菜单. + +#### `menu.closePopup([browserWindow])` + +* `browserWindow` BrowserWindow (可选) - 默认为当前激活的窗口. + +在 `browserWindow` 关闭菜单. #### `menu.append(menuItem)` @@ -95,76 +105,36 @@ const template = [ { label: 'Edit', submenu: [ - { - role: 'undo' - }, - { - role: 'redo' - }, - { - type: 'separator' - }, - { - role: 'cut' - }, - { - role: 'copy' - }, - { - role: 'paste' - }, - { - role: 'pasteandmatchstyle' - }, - { - role: 'delete' - }, - { - role: 'selectall' - } + {role: 'undo'}, + {role: 'redo'}, + {type: 'separator'}, + {role: 'cut'}, + {role: 'copy'}, + {role: 'paste'}, + {role: 'pasteandmatchstyle'}, + {role: 'delete'}, + {role: 'selectall'} ] }, { label: 'View', submenu: [ - { - role: 'reload' - }, - { - role: 'forcereload' - }, - { - role: 'toggledevtools' - }, - { - type: 'separator' - }, - { - role: 'resetzoom' - }, - { - role: 'zoomin' - }, - { - role: 'zoomout' - }, - { - type: 'separator' - }, - { - role: 'togglefullscreen' - } + {role: 'reload'}, + {role: 'forcereload'}, + {role: 'toggledevtools'}, + {type: 'separator'}, + {role: 'resetzoom'}, + {role: 'zoomin'}, + {role: 'zoomout'}, + {type: 'separator'}, + {role: 'togglefullscreen'} ] }, { role: 'window', submenu: [ - { - role: 'minimize' - }, - { - role: 'close' - } + {role: 'minimize'}, + {role: 'close'} ] }, { @@ -182,76 +152,37 @@ if (process.platform === 'darwin') { template.unshift({ label: app.getName(), submenu: [ - { - role: 'about' - }, - { - type: 'separator' - }, - { - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - role: 'hide' - }, - { - role: 'hideothers' - }, - { - role: 'unhide' - }, - { - type: 'separator' - }, - { - role: 'quit' - } + {role: 'about'}, + {type: 'separator'}, + {role: 'services', submenu: []}, + {type: 'separator'}, + {role: 'hide'}, + {role: 'hideothers'}, + {role: 'unhide'}, + {type: 'separator'}, + {role: 'quit'} ] }) - // Edit menu. + + // Edit menu template[1].submenu.push( - { - type: 'separator' - }, + {type: 'separator'}, { label: 'Speech', submenu: [ - { - role: 'startspeaking' - }, - { - role: 'stopspeaking' - } + {role: 'startspeaking'}, + {role: 'stopspeaking'} ] } ) - // Window menu. + + // Window menu template[3].submenu = [ - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - }, - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Zoom', - role: 'zoom' - }, - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' - } + {role: 'close'}, + {role: 'minimize'}, + {role: 'zoom'}, + {type: 'separator'}, + {role: 'front'} ] } diff --git a/docs-translations/zh-CN/api/shell.md b/docs-translations/zh-CN/api/shell.md index 1f36046b1e17..22fa0d1cd1ff 100644 --- a/docs-translations/zh-CN/api/shell.md +++ b/docs-translations/zh-CN/api/shell.md @@ -1,4 +1,7 @@ # shell +> 使用系统默认应用管理文件和 URL . + +进程: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) `shell` 模块提供了集成其他桌面客户端的关联功能. @@ -11,7 +14,7 @@ const {shell} = require('electron') shell.openExternal('https://github.com') ``` -## Methods +## 方法 `shell` 模块包含以下函数: @@ -19,27 +22,60 @@ shell.openExternal('https://github.com') * `fullPath` String -打开文件所在文件夹,一般情况下还会选中它. +Returns `Boolean` - +是否成功打开文件所在文件夹,一般情况下还会选中它. ### `shell.openItem(fullPath)` * `fullPath` String -以默认打开方式打开文件. +Returns `Boolean` - 是否成功的以默认打开方式打开文件. + ### `shell.openExternal(url)` * `url` String +* `options` Object (可选) _macOS_ + * `activate` Boolean - `true` 让打开的应用在前面显示,默认为 `true`. +* `callback` Function (可选) - 如果指定将执行异步打开. _macOS_ + * `error` Error -以系统默认设置打开外部协议.(例如,mailto: somebody@somewhere.io会打开用户默认的邮件客户端) +Returns `Boolean` - 应用程序是否打开URL.如果指定了 callback 回调方法, 则返回 true. + +以系统默认设置打开外部协议.(例如,mailto: URLs 会打开用户默认的邮件客户端) ### `shell.moveItemToTrash(fullPath)` * `fullPath` String +Returns `Boolean` - 文件是否成功移动到垃圾桶 + 删除指定路径文件,并返回此操作的状态值(boolean类型). ### `shell.beep()` 播放 beep 声音. + +### `shell.writeShortcutLink(shortcutPath[, operation], options)` _Windows_ + +* `shortcutPath` String +* `operation` String (可选) - 默认为 `create`, 可以为下列的值: + * `create` - 创建一个新的快捷方式,如果存在的话会覆盖. + * `update` - 仅在现有快捷方式上更新指定属性. + * `replace` - 覆盖现有的快捷方式,如果快捷方式不存在则会失败. +* `options` [ShortcutDetails](structures/shortcut-details.md) + +Returns `Boolean` - 快捷方式是否成功创建 + +为 `shortcutPath` 创建或更新快捷链接. + +### `shell.readShortcutLink(shortcutPath)` _Windows_ + +* `shortcutPath` String + +Returns [`ShortcutDetails`](structures/shortcut-details.md) + +读取 `shortcutPath` 的快捷连接的信息. + +发生错误时,会抛出异常信息. diff --git a/docs-translations/zh-CN/api/structures/bluetooth-device.md b/docs-translations/zh-CN/api/structures/bluetooth-device.md new file mode 100644 index 000000000000..2fa0c799a10e --- /dev/null +++ b/docs-translations/zh-CN/api/structures/bluetooth-device.md @@ -0,0 +1,4 @@ +# 蓝牙设备 Object + +* `deviceName` String +* `deviceId` String diff --git a/docs-translations/zh-CN/api/structures/certificate-principal.md b/docs-translations/zh-CN/api/structures/certificate-principal.md new file mode 100644 index 000000000000..f4184e8faf94 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/certificate-principal.md @@ -0,0 +1,8 @@ +# CertificatePrincipal Object + +* `commonName` String - 通用名 +* `organizations` String[] - 组织名 +* `organizationUnits` String[] - 组织单位名称 +* `locality` String - 地区 +* `state` String - 州或省 +* `country` String - 国家或地区 diff --git a/docs-translations/zh-CN/api/structures/certificate.md b/docs-translations/zh-CN/api/structures/certificate.md new file mode 100644 index 000000000000..546cefb42da8 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/certificate.md @@ -0,0 +1,12 @@ +# Certificate Object 证书对象 + +* `data` String - PEM encoded data +* `issuer` [CertificatePrincipal](certificate-principal.md) - Issuer principal +* `issuerName` String - Issuer's Common Name +* `issuerCert` Certificate - Issuer certificate (if not self-signed) +* `subject` [CertificatePrincipal](certificate-principal.md) - Subject principal +* `subjectName` String - Subject's Common Name +* `serialNumber` String - Hex value represented string +* `validStart` Number - Start date of the certificate being valid in seconds +* `validExpiry` Number - End date of the certificate being valid in seconds +* `fingerprint` String - Fingerprint of the certificate diff --git a/docs-translations/zh-CN/api/structures/cookie.md b/docs-translations/zh-CN/api/structures/cookie.md new file mode 100644 index 000000000000..79f21e1080ac --- /dev/null +++ b/docs-translations/zh-CN/api/structures/cookie.md @@ -0,0 +1,12 @@ +# Cookie Object + +* `name` String - cookie 的名称. +* `value` String - cookie 的值. +* `domain` String (optional) - cookie 的域名. +* `hostOnly` Boolean (optional) - cookie 的类型是否为 host-only. +* `path` String (optional) - cookie 的路径. +* `secure` Boolean (optional) - cookie 是否标记为安全. +* `httpOnly` Boolean (optional) - cookie 是否只标记为 HTTP. +* `session` Boolean (optional) - cookie 是否是一个 session cookie, 还是一个带有过期时间的持续 cookie. +* `expirationDate` Double (optional) - cookie 距离 UNIX 时间戳的过期时间,数值为秒。不需要提供给 session + cookies. diff --git a/docs-translations/zh-CN/api/structures/crash-report.md b/docs-translations/zh-CN/api/structures/crash-report.md new file mode 100644 index 000000000000..f16b5acdb6d5 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/crash-report.md @@ -0,0 +1,4 @@ +# 崩溃报告的对象 + +* `date` String +* `ID` Integer diff --git a/docs-translations/zh-CN/api/structures/desktop-capturer-source.md b/docs-translations/zh-CN/api/structures/desktop-capturer-source.md new file mode 100644 index 000000000000..1ab3db17414e --- /dev/null +++ b/docs-translations/zh-CN/api/structures/desktop-capturer-source.md @@ -0,0 +1,7 @@ +# DesktopCapturerSource Object + +* `id` String - 窗口或者屏幕的标识符,当调用 [`navigator.webkitGetUserMedia`] 时可以被当成 `chromeMediaSourceId` 使用。 +标识符的格式为`window:XX` 或 `screen:XX`,`XX` 是一个随机生成的数字. +* `name` String - 窗口的来源将被命名为 `Entire Screen` 或 `Screen `,而窗口来源的名字将会和窗口的标题匹配. +* `thumbnail` [NativeImage](../native-image.md) - 缩略图. **注:** 通过 `desktopCapturer.getSources` 方法, +不能保证缩略图的大小与 `options` 中指定的 `thumbnailSize` 相同。实际大小取决于窗口或者屏幕的比例。 diff --git a/docs-translations/zh-CN/api/structures/display.md b/docs-translations/zh-CN/api/structures/display.md new file mode 100644 index 000000000000..f5f5b9866ba1 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/display.md @@ -0,0 +1,15 @@ +# Display Object + +* `id` Number - Unique identifier associated with the display. +* `rotation` Number - Can be 0, 90, 180, 270, represents screen rotation in + clock-wise degrees. +* `scaleFactor` Number - Output device's pixel scale factor. +* `touchSupport` String - Can be `available`, `unavailable`, `unknown`. +* `bounds` [Rectangle](rectangle.md) +* `size` [Size](size.md) +* `workArea` [Rectangle](rectangle.md) +* `workAreaSize` [Size](size.md) + +The `Display` object represents a physical display connected to the system. A +fake `Display` may exist on a headless system, or a `Display` may correspond to +a remote, virtual display. diff --git a/docs-translations/zh-CN/api/structures/file-filter.md b/docs-translations/zh-CN/api/structures/file-filter.md new file mode 100644 index 000000000000..014350a60f86 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/file-filter.md @@ -0,0 +1,4 @@ +# FileFilter Object + +* `name` String +* `extensions` String[] diff --git a/docs-translations/zh-CN/api/structures/jump-list-category.md b/docs-translations/zh-CN/api/structures/jump-list-category.md new file mode 100644 index 000000000000..07627e78c98e --- /dev/null +++ b/docs-translations/zh-CN/api/structures/jump-list-category.md @@ -0,0 +1,21 @@ +# JumpListCategory Object + +* `type` String (optional) - One of the following: + * `tasks` - Items in this category will be placed into the standard `Tasks` + category. There can be only one such category, and it will always be + displayed at the bottom of the Jump List. + * `frequent` - Displays a list of files frequently opened by the app, the + name of the category and its items are set by Windows. + * `recent` - Displays a list of files recently opened by the app, the name + of the category and its items are set by Windows. Items may be added to + this category indirectly using `app.addRecentDocument(path)`. + * `custom` - Displays tasks or file links, `name` must be set by the app. +* `name` String (optional) - Must be set if `type` is `custom`, otherwise it should be + omitted. +* `items` JumpListItem[] (optional) - Array of [`JumpListItem`](jump-list-item.md) objects if `type` is `tasks` or + `custom`, otherwise it should be omitted. + +**Note:** If a `JumpListCategory` object has neither the `type` nor the `name` +property set then its `type` is assumed to be `tasks`. If the `name` property +is set but the `type` property is omitted then the `type` is assumed to be +`custom`. diff --git a/docs-translations/zh-CN/api/structures/jump-list-item.md b/docs-translations/zh-CN/api/structures/jump-list-item.md new file mode 100644 index 000000000000..f17d72e0a41b --- /dev/null +++ b/docs-translations/zh-CN/api/structures/jump-list-item.md @@ -0,0 +1,28 @@ +# JumpListItem Object + +* `type` String (optional) - One of the following: + * `task` - A task will launch an app with specific arguments. + * `separator` - Can be used to separate items in the standard `Tasks` + category. + * `file` - A file link will open a file using the app that created the + Jump List, for this to work the app must be registered as a handler for + the file type (though it doesn't have to be the default handler). +* `path` String (optional) - Path of the file to open, should only be set if `type` is + `file`. +* `program` String (optional) - Path of the program to execute, usually you should + specify `process.execPath` which opens the current program. Should only be + set if `type` is `task`. +* `args` String (optional) - The command line arguments when `program` is executed. Should + only be set if `type` is `task`. +* `title` String (optional) - The text to be displayed for the item in the Jump List. + Should only be set if `type` is `task`. +* `description` String (optional) - Description of the task (displayed in a tooltip). + Should only be set if `type` is `task`. +* `iconPath` String (optional) - The absolute path to an icon to be displayed in a + Jump List, which can be an arbitrary resource file that contains an icon + (e.g. `.ico`, `.exe`, `.dll`). You can usually specify `process.execPath` to + show the program icon. +* `iconIndex` Number (optional) - The index of the icon in the resource file. If a + resource file contains multiple icons this value can be used to specify the + zero-based index of the icon that should be displayed for this task. If a + resource file contains only one icon, this property should be set to zero. diff --git a/docs-translations/zh-CN/api/structures/memory-usage-details.md b/docs-translations/zh-CN/api/structures/memory-usage-details.md new file mode 100644 index 000000000000..d77e07dedfc2 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/memory-usage-details.md @@ -0,0 +1,5 @@ +# MemoryUsageDetails Object + +* `count` Number +* `size` Number +* `liveSize` Number diff --git a/docs-translations/zh-CN/api/structures/mime-typed-buffer.md b/docs-translations/zh-CN/api/structures/mime-typed-buffer.md new file mode 100644 index 000000000000..08e5cd47a4a9 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/mime-typed-buffer.md @@ -0,0 +1,4 @@ +# MimeTypedBuffer Object + +* `mimeType` String - The mimeType of the Buffer that you are sending +* `data` Buffer - The actual Buffer content diff --git a/docs-translations/zh-CN/api/structures/point.md b/docs-translations/zh-CN/api/structures/point.md new file mode 100644 index 000000000000..69b87cbdf9c4 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/point.md @@ -0,0 +1,4 @@ +# Point Object + +* `x` Number +* `y` Number diff --git a/docs-translations/zh-CN/api/structures/rectangle.md b/docs-translations/zh-CN/api/structures/rectangle.md new file mode 100644 index 000000000000..0cd000699ea0 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/rectangle.md @@ -0,0 +1,6 @@ +# Rectangle Object + +* `x` Number - The x coordinate of the origin of the rectangle +* `y` Number - The y coordinate of the origin of the rectangle +* `width` Number +* `height` Number diff --git a/docs-translations/zh-CN/api/structures/remove-client-certificate.md b/docs-translations/zh-CN/api/structures/remove-client-certificate.md new file mode 100644 index 000000000000..7ec853f1633b --- /dev/null +++ b/docs-translations/zh-CN/api/structures/remove-client-certificate.md @@ -0,0 +1,5 @@ +# RemoveClientCertificate Object + +* `type` String - `clientCertificate`. +* `origin` String - Origin of the server whose associated client certificate + must be removed from the cache. diff --git a/docs-translations/zh-CN/api/structures/remove-password.md b/docs-translations/zh-CN/api/structures/remove-password.md new file mode 100644 index 000000000000..28a9ed8ae104 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/remove-password.md @@ -0,0 +1,15 @@ +# RemovePassword Object + +* `type` String - `password`. +* `origin` String (optional) - When provided, the authentication info + related to the origin will only be removed otherwise the entire cache + will be cleared. +* `scheme` String (optional) - Scheme of the authentication. + Can be `basic`, `digest`, `ntlm`, `negotiate`. Must be provided if + removing by `origin`. +* `realm` String (optional) - Realm of the authentication. Must be provided if + removing by `origin`. +* `username` String (optional) - Credentials of the authentication. Must be + provided if removing by `origin`. +* `password` String (optional) - Credentials of the authentication. Must be + provided if removing by `origin`. diff --git a/docs-translations/zh-CN/api/structures/scrubber-item.md b/docs-translations/zh-CN/api/structures/scrubber-item.md new file mode 100644 index 000000000000..0dd3c4ff0fd4 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/scrubber-item.md @@ -0,0 +1,4 @@ +# ScrubberItem Object + +* `label` String - (Optional) The text to appear in this item +* `icon` NativeImage - (Optional) The image to appear in this item diff --git a/docs-translations/zh-CN/api/structures/segmented-control-segment.md b/docs-translations/zh-CN/api/structures/segmented-control-segment.md new file mode 100644 index 000000000000..ae01a07f32d2 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/segmented-control-segment.md @@ -0,0 +1,5 @@ +# SegmentedControlSegment Object + +* `label` String - (Optional) The text to appear in this segment +* `icon` NativeImage - (Optional) The image to appear in this segment +* `enabled` Boolean - (Optional) Whether this segment is selectable. Default: true diff --git a/docs-translations/zh-CN/api/structures/shortcut-details.md b/docs-translations/zh-CN/api/structures/shortcut-details.md new file mode 100644 index 000000000000..e7b272d09994 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/shortcut-details.md @@ -0,0 +1,15 @@ +# ShortcutDetails Object + +* `target` String - The target to launch from this shortcut. +* `cwd` String (optional) - The working directory. Default is empty. +* `args` String (optional) - The arguments to be applied to `target` when +launching from this shortcut. Default is empty. +* `description` String (optional) - The description of the shortcut. Default +is empty. +* `icon` String (optional) - The path to the icon, can be a DLL or EXE. `icon` +and `iconIndex` have to be set together. Default is empty, which uses the +target's icon. +* `iconIndex` Number (optional) - The resource ID of icon when `icon` is a +DLL or EXE. Default is 0. +* `appUserModelId` String (optional) - The Application User Model ID. Default +is empty. diff --git a/docs-translations/zh-CN/api/structures/size.md b/docs-translations/zh-CN/api/structures/size.md new file mode 100644 index 000000000000..1d9c8b1f5a12 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/size.md @@ -0,0 +1,4 @@ +# Size Object + +* `width` Number +* `height` Number diff --git a/docs-translations/zh-CN/api/structures/task.md b/docs-translations/zh-CN/api/structures/task.md new file mode 100644 index 000000000000..61a28de879e7 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/task.md @@ -0,0 +1,14 @@ +# Task Object + +* `program` String - Path of the program to execute, usually you should + specify `process.execPath` which opens the current program. +* `arguments` String - The command line arguments when `program` is + executed. +* `title` String - The string to be displayed in a JumpList. +* `description` String - Description of this task. +* `iconPath` String - The absolute path to an icon to be displayed in a + JumpList, which can be an arbitrary resource file that contains an icon. You + can usually specify `process.execPath` to show the icon of the program. +* `iconIndex` Number - The icon index in the icon file. If an icon file + consists of two or more icons, set this value to identify the icon. If an + icon file consists of one icon, this value is 0. diff --git a/docs-translations/zh-CN/api/structures/thumbar-button.md b/docs-translations/zh-CN/api/structures/thumbar-button.md new file mode 100644 index 000000000000..259195852a4f --- /dev/null +++ b/docs-translations/zh-CN/api/structures/thumbar-button.md @@ -0,0 +1,21 @@ +# ThumbarButton Object + +* `icon` [NativeImage](../native-image.md) - The icon showing in thumbnail + toolbar. +* `click` Function +* `tooltip` String (optional) - The text of the button's tooltip. +* `flags` String[] (optional) - Control specific states and behaviors of the + button. By default, it is `['enabled']`. + +The `flags` is an array that can include following `String`s: + +* `enabled` - The button is active and available to the user. +* `disabled` - The button is disabled. It is present, but has a visual state + indicating it will not respond to user action. +* `dismissonclick` - When the button is clicked, the thumbnail window closes + immediately. +* `nobackground` - Do not draw a button border, use only the image. +* `hidden` - The button is not shown to the user. +* `noninteractive` - The button is enabled but not interactive; no pressed + button state is drawn. This value is intended for instances where the button + is used in a notification. diff --git a/docs-translations/zh-CN/api/structures/upload-blob.md b/docs-translations/zh-CN/api/structures/upload-blob.md new file mode 100644 index 000000000000..be93cacb495e --- /dev/null +++ b/docs-translations/zh-CN/api/structures/upload-blob.md @@ -0,0 +1,4 @@ +# UploadBlob Object + +* `type` String - `blob`. +* `blobUUID` String - UUID of blob data to upload. diff --git a/docs-translations/zh-CN/api/structures/upload-data.md b/docs-translations/zh-CN/api/structures/upload-data.md new file mode 100644 index 000000000000..8e5c07725a54 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/upload-data.md @@ -0,0 +1,6 @@ +# UploadData Object + +* `bytes` Buffer - Content being sent. +* `file` String - Path of file being uploaded. +* `blobUUID` String - UUID of blob data. Use [ses.getBlobData](../session.md#sesgetblobdataidentifier-callback) method + to retrieve the data. diff --git a/docs-translations/zh-CN/api/structures/upload-file-system.md b/docs-translations/zh-CN/api/structures/upload-file-system.md new file mode 100644 index 000000000000..d62b0a8ba788 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/upload-file-system.md @@ -0,0 +1,9 @@ +# UploadFileSystem Object + +* `type` String - `fileSystem`. +* `filsSystemURL` String - FileSystem url to read data for upload. +* `offset` Integer - Defaults to `0`. +* `length` Integer - Number of bytes to read from `offset`. + Defaults to `0`. +* `modificationTime` Double - Last Modification time in + number of seconds sine the UNIX epoch. diff --git a/docs-translations/zh-CN/api/structures/upload-file.md b/docs-translations/zh-CN/api/structures/upload-file.md new file mode 100644 index 000000000000..8a2197301444 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/upload-file.md @@ -0,0 +1,9 @@ +# UploadFile Object + +* `type` String - `file`. +* `filePath` String - Path of file to be uploaded. +* `offset` Integer - Defaults to `0`. +* `length` Integer - Number of bytes to read from `offset`. + Defaults to `0`. +* `modificationTime` Double - Last Modification time in + number of seconds sine the UNIX epoch. diff --git a/docs-translations/zh-CN/api/structures/upload-raw-data.md b/docs-translations/zh-CN/api/structures/upload-raw-data.md new file mode 100644 index 000000000000..4fe162311fa1 --- /dev/null +++ b/docs-translations/zh-CN/api/structures/upload-raw-data.md @@ -0,0 +1,4 @@ +# UploadRawData Object + +* `type` String - `rawData`. +* `bytes` Buffer - Data to be uploaded. diff --git a/docs-translations/zh-CN/api/web-contents.md b/docs-translations/zh-CN/api/web-contents.md index b13628533ee1..dd5c926126c2 100644 --- a/docs-translations/zh-CN/api/web-contents.md +++ b/docs-translations/zh-CN/api/web-contents.md @@ -48,15 +48,14 @@ console.log(webContents) 可使用的进程: [主进程](../tutorial/quick-start.md#main-process) -## 事件 +### 实例事件 -`webContents` 对象可发出下列事件: -### Event: 'did-finish-load' +#### Event: 'did-finish-load' -当导航完成时发出事件,`onload` 事件也完成。 +当导航完成时,发出 `onload` 事件。 -### Event: 'did-fail-load' +#### Event: 'did-fail-load' 返回: @@ -66,9 +65,9 @@ console.log(webContents) * `validatedURL` String * `isMainFrame` Boolean -这个事件类似 `did-finish-load` ,但是是在加载失败或取消加载时发出, 例如, `window.stop()` 请求结束。错误代码的完整列表和它们的含义都可以在 [这里](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) 找到。 +这个事件类似 `did-finish-load` ,但是是在加载失败或取消加载时发出, 例如, `window.stop()` 被调用时。错误代码的完整列表和它们的含义都可以在 [这里](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) 找到。 -### Event: 'did-frame-finish-load' +#### Event: 'did-frame-finish-load' 返回: @@ -77,15 +76,15 @@ console.log(webContents) 当一个 frame 导航完成的时候发出事件。 -### Event: 'did-start-loading' +#### Event: 'did-start-loading' 当 tab 的spinner 开始 spinning的时候。 -### Event: 'did-stop-loading' +#### Event: 'did-stop-loading' 当 tab 的spinner 结束 spinning的时候。 -### Event: 'did-get-response-details' +#### Event: 'did-get-response-details' 返回: @@ -102,7 +101,7 @@ console.log(webContents) 当有关请求资源的详细信息可用的时候发出事件。 `status` 标识了 socket 链接来下载资源。 -### Event: 'did-get-redirect-request' +#### Event: 'did-get-redirect-request' 返回: @@ -117,7 +116,7 @@ console.log(webContents) 当在请求资源时收到重定向的时候发出事件。 -### Event: 'dom-ready' +#### Event: 'dom-ready' 返回: @@ -125,7 +124,7 @@ console.log(webContents) 当指定 frame 中的 文档加载完成的时候发出事件。 -### Event: 'page-favicon-updated' +#### Event: 'page-favicon-updated' 返回: @@ -134,15 +133,15 @@ console.log(webContents) 当 page 收到图标 url 的时候发出事件。 -### Event: 'new-window' +#### Event: 'new-window' 返回: * `event` Event * `url` String * `frameName` String -* `disposition` String - 可为 `default`、 `foreground-tab`、 `background-tab`、 - `new-window` 和 `other`。 +* `disposition` String - 可为 `default`, `foreground-tab`, `background-tab`, + `new-window`, `save-to-disk` 和 `other` * `options` Object - 创建新的 `BrowserWindow`时使用的参数。 * `additionalFeatures` String[] - 非标准功能(功能未处理    由 Chromium 或 Electron )赋予 `window.open()`。 @@ -168,7 +167,7 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { }) ``` -### Event: 'will-navigate' +#### Event: 'will-navigate' 返回: @@ -183,7 +182,7 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { 调用 `event.preventDefault()` 可以阻止导航。 -### Event: 'did-navigate' +#### Event: 'did-navigate' 返回: @@ -194,22 +193,28 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { 页内跳转时不会发出这个事件,例如点击锚链接或更新 `window.location.hash`。使用 `did-navigate-in-page` 事件可以达到目的。 -### Event: 'did-navigate-in-page' +#### Event: 'did-navigate-in-page' 返回: * `event` Event * `url` String +* `isMainFrame ` Boolean 当页内导航发生的时候发出事件。 当页内导航发生的时候,page 的url 改变,但是不会跳出界面。例如当点击锚链接时或者 DOM 的 `hashchange` 事件发生。 -### Event: 'crashed' +#### Event: 'crashed' + +返回: + +* `event` Event +* `killed` Boolean 当渲染进程崩溃的时候发出事件。 -### Event: 'plugin-crashed' +#### Event: 'plugin-crashed' 返回: @@ -219,54 +224,70 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { 当插件进程崩溃时候发出事件。 -### Event: 'destroyed' +#### Event: 'destroyed' 当 `webContents` 被删除的时候发出事件。 -### Event: 'devtools-opened' +#### Event: 'before-input-event' + +返回: + +* `event` Event +* `input` Object - 属性 + * `type` String - `keyUp` 或者 `keyDown` + * `key` String - [KeyboardEvent.key][keyboardevent] + * `code` String - [KeyboardEvent.code][keyboardevent] + * `isAutoRepeat` Boolean - [KeyboardEvent.repeat][keyboardevent] + * `shift` Boolean - [KeyboardEvent.shiftKey][keyboardevent] + * `control` Boolean - [KeyboardEvent.controlKey][keyboardevent] + * `alt` Boolean - [KeyboardEvent.altKey][keyboardevent] + * `meta` Boolean - [KeyboardEvent.metaKey][keyboardevent] + +在 `keydown` 和 `keyup` 事件触发前发送。调用 `event.preventDefault` 方法可以阻止页面的 `keydown/keyup` 事件。 + +#### Event: 'devtools-opened' 当开发者工具栏打开的时候发出事件。 -### Event: 'devtools-closed' +#### Event: 'devtools-closed' 当开发者工具栏关闭时候发出事件。 -### Event: 'devtools-focused' +#### Event: 'devtools-focused' 当开发者工具栏获得焦点或打开的时候发出事件。 -### Event: 'certificate-error' +#### Event: 'certificate-error' 返回: * `event` Event * `url` URL * `error` String - The error code -* `certificate` Object - * `data` Buffer - PEM encoded data - * `issuerName` String +* `certificate` [Certificate](structures/certificate.md) * `callback` Function + * `isTrusted ` Boolean - 表明证书是否可以被认为是可信的 -当验证证书或 `url` 失败的时候发出事件。 +当验证 `certificate` 或 `url` 失败的时候发出事件。 使用方法类似 [`app` 的 `certificate-error` 事件](app.md#event-certificate-error)。 -### Event: 'select-client-certificate' + +#### Event: 'select-client-certificate' 返回: * `event` Event * `url` URL -* `certificateList` [Objects] - * `data` Buffer - PEM encoded data - * `issuerName` String - Issuer's Common Name +* `certificateList` [Certificate[]](structures/certificate.md) * `callback` Function + * `certificate` [Certificate](structures/certificate.md) - 证书必须来自于指定的列表 当请求客户端证书的时候发出事件。 使用方法类似 [`app` 的 `select-client-certificate` 事件](app.md#event-select-client-certificate)。 -### Event: 'login' +#### Event: 'login' 返回: @@ -282,34 +303,37 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { * `port` Integer * `realm` String * `callback` Function + * `username` String + * `password` String 当 `webContents` 想做基本验证的时候发出事件. 使用方法类似 [the `login` event of `app`](app.md#event-login)。 -### Event: 'found-in-page' +#### Event: 'found-in-page' 返回: * `event` Event * `result` Object * `requestId` Integer - * `finalUpdate` Boolean - 标识是否还有更多的值可以查看。 - * `activeMatchOrdinal` Integer (可选) - 活动匹配位置。 - * `matches` Integer (可选) - 匹配数量。 - * `selectionArea` Object (可选) - 协调首个匹配位置。 + * `activeMatchOrdinal` Integer - 活动匹配位置。 + * `matches` Integer - 匹配数量。 + * `selectionArea` Object - 协调首个匹配位置。 + * `finalUpdate` Boolean + 当使用 [`webContents.findInPage`] 进行页内查找并且找到可用值得时候发出事件。 -### Event: 'media-started-playing' +#### Event: 'media-started-playing' 当媒体开始播放的时候发出事件。 -### Event: 'media-paused' +#### Event: 'media-paused' 当媒体停止播放的时候发出事件。 -### Event: 'did-change-theme-color' +#### Event: 'did-change-theme-color' 当page 的主题色时候发出事件。这通常由于引入了一个 meta 标签: @@ -317,14 +341,29 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { ``` -### Event: 'cursor-changed' +#### Event: 'update-target-url' + +返回: + +* `event` Event +* `url` String + +当鼠标移到一个链接上或键盘焦点移动到一个链接上时发送。 + +#### Event: 'cursor-changed' 返回: * `event` Event * `type` String * `image` NativeImage (可选) -* `scale` Float (可选) +* `scale` Float (可选) 自定义光标的比例 +* `size` Object (可选) - `image`的大小 + * `width` Integer + * `height` Integer +* `hotspot` Object (可选) - 自定义光标热点的坐标 + * `x` Integer - x 坐标 + * `y` Integer - y 坐标 当鼠标的类型发生改变的时候发出事件. `type` 的参数可以是 `default`, `crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, @@ -333,221 +372,428 @@ myBrowserWindow.webContents.on('new-window', (event, url) => { `row-resize`, `m-panning`, `e-panning`, `n-panning`, `ne-panning`, `nw-panning`, `s-panning`, `se-panning`, `sw-panning`, `w-panning`, `move`, `vertical-text`, `cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, -`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`。 +`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. -如果 `type` 参数值为 `custom`、 `image` 参数会在一个`NativeImage` 中控制自定义鼠标图片,并且 `scale` 会控制图片的缩放比例。 +如果 `type` 参数值为 `custom`、 `image` 参数会在一个`NativeImage` 中控制自定义鼠标图片,并且 `scale` 、`size` 和 `hotspot`会控制自定义光标的属性。 -## 实例方法 +#### Event: 'context-menu' -`webContents` 对象有如下的实例方法: +Returns: -### `webContents.loadURL(url[, options])` +* `event` Event +* `params` Object + * `x` Integer - x 坐标 + * `y` Integer - y 坐标 + * `linkURL` String - 菜单中调用的节点的 URL 链接. + * `linkText` String - 链接的文本。当链接是一个图像时文本可以为空. + * `pageURL` String - 菜单被调用时顶级页面的 URL 链接. + * `frameURL` String - 菜单被调用时子级页面的 URL 链接. + * `srcURL` String - 菜单被调用时元素的原始链接。带有链接的元素可以为图像、音频和视频。 + * `mediaType` String - 菜单被调用时的节点类型。可以为 `none`, `image`, `audio`, `video`, `canvas`, `file` or `plugin`. + * `hasImageContents` Boolean - 菜单中是否含有图像. + * `isEditable` Boolean - 菜单是否可以被编辑. + * `selectionText` String - Text of the selection that the context menu was + invoked on. + * `titleText` String - Title or alt text of the selection that the context + was invoked on. + * `misspelledWord` String - The misspelled word under the cursor, if any. + * `frameCharset` String - The character encoding of the frame on which the + menu was invoked. + * `inputFieldType` String - If the context menu was invoked on an input + field, the type of that field. Possible values are `none`, `plainText`, + `password`, `other`. + * `menuSourceType` String - Input source that invoked the context menu. + Can be `none`, `mouse`, `keyboard`, `touch`, `touchMenu`. + * `mediaFlags` Object - The flags for the media element the context menu was + invoked on. + * `inError` Boolean - Whether the media element has crashed. + * `isPaused` Boolean - Whether the media element is paused. + * `isMuted` Boolean - Whether the media element is muted. + * `hasAudio` Boolean - Whether the media element has audio. + * `isLooping` Boolean - Whether the media element is looping. + * `isControlsVisible` Boolean - Whether the media element's controls are + visible. + * `canToggleControls` Boolean - Whether the media element's controls are + toggleable. + * `canRotate` Boolean - Whether the media element can be rotated. + * `editFlags` Object - These flags indicate whether the renderer believes it + is able to perform the corresponding action. + * `canUndo` Boolean - Whether the renderer believes it can undo. + * `canRedo` Boolean - Whether the renderer believes it can redo. + * `canCut` Boolean - Whether the renderer believes it can cut. + * `canCopy` Boolean - Whether the renderer believes it can copy + * `canPaste` Boolean - Whether the renderer believes it can paste. + * `canDelete` Boolean - Whether the renderer believes it can delete. + * `canSelectAll` Boolean - Whether the renderer believes it can select all. + +Emitted when there is a new context menu that needs to be handled. + +#### Event: 'select-bluetooth-device' + +Returns: + +* `event` Event +* `devices` [BluetoothDevice[]](structures/bluetooth-device.md) +* `callback` Function + * `deviceId` String + +Emitted when bluetooth device needs to be selected on call to +`navigator.bluetooth.requestDevice`. To use `navigator.bluetooth` api +`webBluetooth` should be enabled. If `event.preventDefault` is not called, +first available device will be selected. `callback` should be called with +`deviceId` to be selected, passing empty string to `callback` will +cancel the request. + +```javascript +const {app, webContents} = require('electron') +app.commandLine.appendSwitch('enable-web-bluetooth') + +app.on('ready', () => { + webContents.on('select-bluetooth-device', (event, deviceList, callback) => { + event.preventDefault() + let result = deviceList.find((device) => { + return device.deviceName === 'test' + }) + if (!result) { + callback('') + } else { + callback(result.deviceId) + } + }) +}) +``` + +#### Event: 'paint' + +Returns: + +* `event` Event +* `dirtyRect` [Rectangle](structures/rectangle.md) +* `image` [NativeImage](native-image.md) - The image data of the whole frame. + +Emitted when a new frame is generated. Only the dirty area is passed in the +buffer. + +```javascript +const {BrowserWindow} = require('electron') + +let win = new BrowserWindow({webPreferences: {offscreen: true}}) +win.webContents.on('paint', (event, dirty, image) => { + // updateBitmap(dirty, image.getBitmap()) +}) +win.loadURL('http://github.com') +``` + +#### Event: 'devtools-reload-page' +当 devtools 调试工具页面重新加载时发送 + +#### Event: 'will-attach-webview' + +Returns: + +* `event` Event +* `webPreferences` Object - The web preferences that will be used by the guest + page. This object can be modified to adjust the preferences for the guest + page. +* `params` Object - The other `` parameters such as the `src` URL. + This object can be modified to adjust the parameters of the guest page. + +Emitted when a ``'s web contents is being attached to this web +contents. Calling `event.preventDefault()` will destroy the guest page. + +This event can be used to configure `webPreferences` for the `webContents` +of a `` before it's loaded, and provides the ability to set settings +that can't be set via `` attributes. +### 实例方法 + + +#### `contents.loadURL(url[, options])` * `url` URL * `options` Object (可选) - * `httpReferrer` String - A HTTP Referrer url. + * `httpReferrer` String - HTTP 的 url 链接. * `userAgent` String - 产生请求的用户代理 * `extraHeaders` String - 以 "\n" 分隔的额外头 + * `postData` ([UploadRawData](structures/upload-raw-data.md) | [UploadFile](structures/upload-file.md) | [UploadFileSystem](structures/upload-file-system.md) | [UploadBlob](structures/upload-blob.md))[] - (optional) + * `baseURLForDataURL` String (optional) - 由数据URL加载的文件的基本URL(带尾随路径分隔符)。只有当指定的URL是一个数据URL并且需要加载其他文件时才需要设置。 -在窗口中加载 `url` 、 `url` 必须包含协议前缀, +在窗口中加载 `url`。 `url` 必须包含协议前缀, 比如 `http://` 或 `file://`。如果加载想要忽略 http 缓存,可以使用 `pragma` 头来达到目的。 ```javascript -const options = {'extraHeaders': 'pragma: no-cache\n'} -webContents.loadURL(url, options) +const {webContents} = require('electron') +const options = {extraHeaders: 'pragma: no-cache\n'} +webContents.loadURL('https://github.com', options) ``` -### `webContents.downloadURL(url)` +#### `contents.downloadURL(url)` * `url` URL 初始化一个指定 `url` 的资源下载,不导航跳转。 `session` 的 `will-download` 事件会触发。 -### `webContents.getURL()` +#### `contents.getURL()` -返回当前 page 的 url。 +Returns `String` - 当前页面的 URL. ```javascript -var win = new BrowserWindow({width: 800, height: 600}) +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({width: 800, height: 600}) win.loadURL('http://github.com') -var currentURL = win.webContents.getURL() +let currentURL = win.webContents.getURL() +console.log(currentURL) ``` -### `webContents.getTitle()` +#### `contents.getTitle()` -返回当前 page 的标题。 +Returns `String` - 当前页面的标题. -### `webContents.isLoading()` +#### `contents.isDestroyed()` -返回一个布尔值,标识当前页是否正在加载。 +Returns `Boolean` - 当前的页面是否被销毁了 -### `webContents.isWaitingForResponse()` +#### `contents.isFocused()` -返回一个布尔值,标识当前页是否正在等待主要资源的第一次响应。 +Returns `Boolean` - 焦点是否在当前页面上. -### `webContents.stop()` +#### `contents.isLoading()` + +Returns `Boolean` - 当前页是否正在加载资源。 + +#### `contents.isLoadingMainFrame()` + +Returns `Boolean` - 是否主框架(而不仅是 iframe 或者在帧内)仍在加载中。 + +#### `contents.isWaitingForResponse()` + +Returns `Boolean` - 当前页是否正在等待主要资源的第一次响应。 + +#### `contents.stop()` 停止还为开始的导航。 -### `webContents.reload()` +#### `contents.reload()` 重载当前页。 -### `webContents.reloadIgnoringCache()` +#### `contents.reloadIgnoringCache()` 重载当前页,忽略缓存。 -### `webContents.canGoBack()` +#### `contents.canGoBack()` -返回一个布尔值,标识浏览器是否能回到前一个page。 +Returns `Boolean` - 浏览器是否能回到前一个页面。 -### `webContents.canGoForward()` +#### `contents.canGoForward()` -返回一个布尔值,标识浏览器是否能前往下一个page。 +Returns `Boolean` - 浏览器是否能前往下一个页面。 -### `webContents.canGoToOffset(offset)` +#### `contents.canGoToOffset(offset)` * `offset` Integer -返回一个布尔值,标识浏览器是否能前往指定 `offset` 的page。 +Returns `Boolean` - 页面是否能前往 `offset`。 -### `webContents.clearHistory()` +#### `contents.clearHistory()` 清除导航历史。 -### `webContents.goBack()` +#### `contents.goBack()` -让浏览器回退到前一个page。 +让浏览器回退到前一个页面。 -### `webContents.goForward()` +#### `contents.goForward()` -让浏览器回前往下一个page。 +让浏览器回前往下一个页面。 -### `webContents.goToIndex(index)` +#### `contents.goToIndex(index)` * `index` Integer -让浏览器回前往指定 `index` 的page。 +让浏览器回前往指定 `index` 的页面。 -### `webContents.goToOffset(offset)` +#### `contents.goToOffset(offset)` * `offset` Integer 导航到相对于当前页的偏移位置页。 -### `webContents.isCrashed()` +#### `contents.isCrashed()` -渲染进程是否崩溃。 +Returns `Boolean` - 渲染进程是否崩溃。 -### `webContents.setUserAgent(userAgent)` +#### `contents.setUserAgent(userAgent)` * `userAgent` String 重写本页用户代理。 -### `webContents.getUserAgent()` +#### `contents.getUserAgent()` -返回一个 `String` ,标识本页用户代理信息。 +Returns `String` - 页面的用户代理信息。 -### `webContents.insertCSS(css)` +#### `contents.insertCSS(css)` * `css` String 为当前页插入css。 -### `webContents.executeJavaScript(code[, userGesture, callback])` +#### `contents.executeJavaScript(code[, userGesture, callback])` * `code` String -* `userGesture` Boolean (可选) +* `userGesture` Boolean (可选) - 默认值为 `false` * `callback` Function (可选) - 脚本执行完成后调用的回调函数. - * `result` + * `result` Any -评估 page `代码`。 +Returns `Promise` - 成功了返回 resolves,失败了返回 rejected + +评估页面的 `code` 代码。 浏览器窗口中的一些 HTML API ,例如 `requestFullScreen`,只能被用户手势请求。设置 `userGesture` 为 `true` 可以取消这个限制。 -### `webContents.setAudioMuted(muted)` +如果执行的代码的结果是一个 promise,回调方法将为 promise 的 resolved 的值。我们建议您使用返回的 Promise 来处理导致结果的代码。 + +```js +contents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1").then(resp => resp.json())', true) + .then((result) => { + console.log(result) // Will be the JSON object from the fetch call + }) +``` +#### `contents.setAudioMuted(muted)` * `muted` Boolean -减缓当前页的 audio 的播放速度。 +设置当前页为静音。 -### `webContents.isAudioMuted()` +#### `contents.isAudioMuted()` -返回一个布尔值,标识当前页是否减缓了 audio 的播放速度。 +Returns `Boolean` - 当前页是否为静音状态 -### `webContents.undo()` +#### `contents.setZoomFactor(factor)` -执行网页的编辑命令 `undo`。 +* `factor` Number - 缩放系数 -### `webContents.redo()` +改变缩放系数为指定的数值。缩放系数是缩放的百分比除以100,比如 300% = 3.0 -执行网页的编辑命令 `redo`。 +#### `contents.getZoomFactor(callback)` -### `webContents.cut()` +* `callback` Function + * `zoomFactor` Number -执行网页的编辑命令 `cut`。 +发送一个请求来获取当前的缩放系数,`callback` 回调方法会在 `callback(zoomFactor)` 中调用. -### `webContents.copy()` +#### `contents.setZoomLevel(level)` -执行网页的编辑命令 `copy`。 +* `level` Number - 缩放等级 -### `webContents.paste()` +改变缩放等级为指定的等级。原始大小为0,每升高或降低一次,代表 20% 的大小缩放。限制为原始大小的 300% 到 50%。 -执行网页的编辑命令 `paste`。 +#### `contents.getZoomLevel(callback)` -### `webContents.pasteAndMatchStyle()` +* `callback` Function + * `zoomLevel` Number -执行网页的编辑命令 `pasteAndMatchStyle`。 +发送一个请求来获取当前的缩放等级,`callback` 回调方法会在 `callback(zoomLevel)` 中调用. +#### `contents.setZoomLevelLimits(minimumLevel, maximumLevel)` -### `webContents.delete()` +* `minimumLevel` Number +* `maximumLevel` Number -执行网页的编辑命令 `delete`。 +**废弃:** 使用 `setVisualZoomLevelLimits` 来代替. 这个方法将在 Electron 2.0 中移除. -### `webContents.selectAll()` +#### `contents.setVisualZoomLevelLimits(minimumLevel, maximumLevel)` -执行网页的编辑命令 `selectAll`。 +* `minimumLevel` Number +* `maximumLevel` Number -### `webContents.unselect()` +设置最大和最小的手势缩放等级。 -执行网页的编辑命令 `unselect`。 +#### `contents.setLayoutZoomLevelLimits(minimumLevel, maximumLevel)` -### `webContents.replace(text)` +* `minimumLevel` Number +* `maximumLevel` Number + +设置最大和最小的 layout-based (i.e. non-visual) zoom level. + +#### `contents.undo()` + +在网页中执行编辑命令 `undo`。 + +#### `contents.redo()` + +在网页中执行编辑命令 `redo`。 + +#### `contents.cut()` + +在网页中执行编辑命令 `cut`。 + +#### `contents.copy()` + +在网页中执行编辑命令 `copy`。 + +#### `contents.copyImageAt(x, y)` + +* `x` Integer +* `y` Integer + +拷贝剪贴板中指定位置的图像. + +#### `contents.paste()` + +在网页中执行编辑命令 `paste`。 + +#### `contents.pasteAndMatchStyle()` + +在网页中执行编辑命令 `pasteAndMatchStyle`。 + +#### `contents.delete()` + +在网页中执行编辑命令 `delete`。 + +#### `contents.selectAll()` + +在网页中执行编辑命令 `selectAll`。 + +#### `contents.unselect()` + +在网页中执行编辑命令 `unselect`。 + +#### `contents.replace(text)` * `text` String -执行网页的编辑命令 `replace`。 +在网页中执行编辑命令 `replace`。 -### `webContents.replaceMisspelling(text)` +#### `contents.replaceMisspelling(text)` * `text` String -执行网页的编辑命令 `replaceMisspelling`。 +在网页中执行编辑命令 `replaceMisspelling`。 -### `webContents.insertText(text)` +#### `contents.insertText(text)` * `text` String 插入 `text` 到获得了焦点的元素。 -### `webContents.findInPage(text[, options])` +#### `contents.findInPage(text[, options])` * `text` String - 查找内容,不能为空。 * `options` Object (可选) - * `forward` Boolean - 是否向前或向后查找,默认为 `true`。 - * `findNext` Boolean - 当前操作是否是第一次查找或下一次查找, + * `forward` Boolean - (可选) 是否向前或向后查找,默认为 `true`。 + * `findNext` Boolean - (可选) 当前操作是否是第一次查找或下一次查找, 默认为 `false`。 - * `matchCase` Boolean - 查找是否区分大小写, + * `matchCase` Boolean - (可选) 查找是否区分大小写, 默认为 `false`。 - * `wordStart` Boolean -是否仅以首字母查找, + * `wordStart` Boolean - (可选) 是否仅以首字母查找, 默认为 `false`。 - * `medialCapitalAsWordStart` Boolean - 是否结合 `wordStart`,如果匹配是大写字母开头,后面接小写字母或无字母,那么就接受这个词中匹配。接受几个其它的合成词匹配,默认为 `false`。 + * `medialCapitalAsWordStart` Boolean - (可选) 是否结合 `wordStart`,如果匹配是大写字母开头,后面接小写字母或无字母,那么就接受这个词中匹配。接受几个其它的合成词匹配,默认为 `false`。 发起请求,在网页中查找所有与 `text` 相匹配的项,并且返回一个 `Integer` 来表示这个请求用的请求 Id。这个请求结果可以通过订阅 [`found-in-page`](web-contents.md#event-found-in-page) 事件来取得。 -### `webContents.stopFindInPage(action)` +#### `contents.stopFindInPage(action)` * `action` String - 指定一个行为来接替停止 [`webContents.findInPage`] 请求。 @@ -558,47 +804,62 @@ var currentURL = win.webContents.getURL() 使用给定的 `action` 来为 `webContents` 停止任何 `findInPage` 请求。 ```javascript -webContents.on('found-in-page', function (event, result) { +const {webContents} = require('electron') +webContents.on('found-in-page', (event, result) => { if (result.finalUpdate) webContents.stopFindInPage('clearSelection') }) const requestId = webContents.findInPage('api') +console.log(requestId) ``` -### `webContents.hasServiceWorker(callback)` +#### `contents.capturePage([rect, ]callback)` + +* `rect` [Rectangle](structures/rectangle.md) (optional) - 页面被捕捉的区域 +* `callback` Function + * `image` [NativeImage](native-image.md) + +捕捉页面 `rect` 区域的快照。在完成后 `callback` 方法会通过 `callback(image)` 调用 。`image` 是 [NativeImage](native-image.md) 的实例。省略 `rect` 参数会捕捉整个页面的可视区域。 + +#### `contents.hasServiceWorker(callback)` * `callback` Function - + * `hasWorker` Boolean + 检查是否有任何 ServiceWorker 注册了,并且返回一个布尔值,来作为 `callback`响应的标识。 -### `webContents.unregisterServiceWorker(callback)` +#### `contents.unregisterServiceWorker(callback)` * `callback` Function - + * `success` Boolean + 如果存在任何 ServiceWorker,则全部注销,并且当JS承诺执行行或JS拒绝执行而失败的时候,返回一个布尔值,它标识了相应的 `callback`。 -### `webContents.print([options])` +#### `contents.print([options])` * `options` Object (可选) * `silent` Boolean - 不需要请求用户的打印设置. 默认为 `false`。 * `printBackground` Boolean - 打印背景和网页图片. 默认为 `false`。 -打印窗口的网页。当设置 `silent` 为 `false` 的时候,Electron 将使用系统默认的打印机和打印方式来打印。 +打印窗口的网页。当设置 `silent` 为 `true` 的时候,Electron 将使用系统默认的打印机和打印方式来打印。 在网页中调用 `window.print()` 和 调用 `webContents.print({silent: false, printBackground: false})`具有相同的作用。 -**注意:** 在 Windows,打印 API 依赖于 `pdf.dll`。如果你的应用不使用任何的打印,你可以安全删除 `pdf.dll` 来减少二进制文件的size。 +使用 `page-break-before: always; ` CSS 的样式将强制打印到一个新的页面. -### `webContents.printToPDF(options, callback)` +#### `contents.printToPDF(options, callback)` * `options` Object * `marginsType` Integer - 指定使用的 margin type。默认 margin 使用 0,无 margin 使用 1,最小化 margin 使用 2。 * `pageSize` String - 指定生成的PDF文件的page size. 可以是 `A3`、 - `A4`、 `A5`、 `Legal`、`Letter` 和 `Tabloid`。 + `A4`、 `A5`、 `Legal`、`Letter` 和 `Tabloid`。或者是一个包含 `height` + 和 `width` 的对象,单位是微米。 * `printBackground` Boolean - 是否打印 css 背景。 * `printSelectionOnly` Boolean - 是否只打印选中的部分。 * `landscape` Boolean - landscape 为 `true`, portrait 为 `false`。 * `callback` Function + * `error` Error + * `data` Buffer 打印窗口的网页为 pdf,使用 Chromium 预览打印的自定义设置。 @@ -615,18 +876,22 @@ const requestId = webContents.findInPage('api') } ``` +使用 `page-break-before: always; ` CSS 的样式将强制打印到一个新的页面. + +一个 `webContents.printToPDF` 的示例: + ```javascript -const BrowserWindow = require('electron').BrowserWindow +const {BrowserWindow} = require('electron') const fs = require('fs') -var win = new BrowserWindow({width: 800, height: 600}) +let win = new BrowserWindow({width: 800, height: 600}) win.loadURL('http://github.com') -win.webContents.on('did-finish-load', function () { +win.webContents.on('did-finish-load', () => { // Use default printing options - win.webContents.printToPDF({}, function (error, data) { + win.webContents.printToPDF({}, (error, data) => { if (error) throw error - fs.writeFile('/tmp/print.pdf', data, function (error) { + fs.writeFile('/tmp/print.pdf', data, (error) => { if (error) throw error console.log('Write PDF successfully.') }) @@ -634,59 +899,61 @@ win.webContents.on('did-finish-load', function () { }) ``` -### `webContents.addWorkSpace(path)` +#### `contents.addWorkSpace(path)` * `path` String 添加指定的路径给开发者工具栏的 workspace。必须在 DevTools 创建之后使用它: ```javascript -mainWindow.webContents.on('devtools-opened', function () { - mainWindow.webContents.addWorkSpace(__dirname) +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() +win.webContents.on('devtools-opened', () => { + win.webContents.addWorkSpace(__dirname) }) ``` -### `webContents.removeWorkSpace(path)` +#### `contents.removeWorkSpace(path)` * `path` String 从开发者工具栏的 workspace 删除指定的路径。 -### `webContents.openDevTools([options])` +#### `contents.openDevTools([options])` * `options` Object (可选) - * `detach` Boolean - 在一个新窗口打开开发者工具栏 + * `mode` String - 打开开发者工具的状态属性。可以为 `right`, `bottom`, `undocked`, `detach`。默认值为上一次使用时的状态。在`undocked`模式可以把工具栏放回来,`detach`模式不可以。 打开开发者工具栏。 -### `webContents.closeDevTools()` +#### `contents.closeDevTools()` 关闭开发者工具栏。 -### `webContents.isDevToolsOpened()` +#### `contents.isDevToolsOpened()` -返回布尔值,开发者工具栏是否打开。 +Returns `Boolean` - 开发者工具栏是否打开。 -### `webContents.isDevToolsFocused()` +#### `contents.isDevToolsFocused()` -返回布尔值,开发者工具栏视图是否获得焦点。 +Returns `Boolean` - 开发者工具栏视图是否获得焦点。 -### `webContents.toggleDevTools()` +#### `contents.toggleDevTools()` Toggles 开发者工具。 -### `webContents.inspectElement(x, y)` +#### `contents.inspectElement(x, y)` * `x` Integer * `y` Integer 在 (`x`, `y`) 开始检测元素。 -### `webContents.inspectServiceWorker()` +#### `contents.inspectServiceWorker()` 为 service worker 上下文打开开发者工具栏。 -### `webContents.send(channel[, arg1][, arg2][, ...])` +#### `contents.send(channel[, arg1][, arg2][, ...])` * `channel` String * `arg` (可选) @@ -695,16 +962,18 @@ Toggles 开发者工具。 渲染进程可以通过使用 `ipcRenderer` 监听 `channel` 来处理消息。 -例子,从主进程向渲染进程发送消息: +从主进程向渲染进程发送消息的示例: ```javascript // 主进程. -var window = null -app.on('ready', function () { - window = new BrowserWindow({width: 800, height: 600}) - window.loadURL(`file://${__dirname}/index.html`) - window.webContents.on('did-finish-load', function () { - window.webContents.send('ping', 'whoooooooh!') +const {app, BrowserWindow} = require('electron') +let win = null + +app.on('ready', () => { + win = new BrowserWindow({width: 800, height: 600}) + win.loadURL(`file://${__dirname}/index.html`) + win.webContents.on('did-finish-load', () => { + win.webContents.send('ping', 'whoooooooh!') }) }) ``` @@ -714,31 +983,31 @@ app.on('ready', function () { ``` -### `webContents.enableDeviceEmulation(parameters)` +#### `contents.enableDeviceEmulation(parameters)` -`parameters` Object, properties: +`parameters` Object, 属性为: * `screenPosition` String - 指定需要模拟的屏幕 (默认 : `desktop`) - * `desktop` - * `mobile` -* `screenSize` Object - 设置模拟屏幕 size (screenPosition == mobile) + * `desktop` - 桌面屏幕模式 + * `mobile` - 手机屏幕模式 +* `screenSize` Object - 设置模拟屏幕的尺寸 (screenPosition == mobile) * `width` Integer - 设置模拟屏幕 width * `height` Integer - 设置模拟屏幕 height -* `viewPosition` Object - 在屏幕放置 view +* `viewPosition` Object - 屏幕中可视区域的位置 (screenPosition == mobile) (默认: `{x: 0, y: 0}`) * `x` Integer - 设置偏移左上角的x轴 * `y` Integer - 设置偏移左上角的y轴 -* `deviceScaleFactor` Integer - 设置设备比例因子 (如果为0,默认为原始屏幕比例) (默认: `0`) -* `viewSize` Object - 设置模拟视图 size (空表示不覆盖) +* `deviceScaleFactor` Integer - 设置设备缩放比例系数 (如果为0,默认为原始屏幕比例) (默认: `0`) +* `viewSize` Object - 设置模拟视图的尺寸 (空表示不覆盖) * `width` Integer - 设置模拟视图 width * `height` Integer - 设置模拟视图 height * `fitToView` Boolean - 如果有必要的话,是否把模拟视图按比例缩放来适应可用空间 (默认: `false`) @@ -749,32 +1018,30 @@ app.on('ready', function () { 使用给定的参数来开启设备模拟。 -### `webContents.disableDeviceEmulation()` +#### `contents.disableDeviceEmulation()` -使用 `webContents.enableDeviceEmulation` 关闭设备模拟。 +关闭模拟器,使用 `webContents.enableDeviceEmulation` 来启用。 -### `webContents.sendInputEvent(event)` +#### `contents.sendInputEvent(event)` * `event` Object * `type` String (**必需**) - 事件类型,可以是 `mouseDown`, `mouseUp`, `mouseEnter`, `mouseLeave`, `contextMenu`, `mouseWheel`, `mouseMove`, `keyDown`, `keyUp`, `char`. - * `modifiers` Array - 事件的 modifiers 数组, 可以是 - include `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, + * `modifiers` String[] - 事件的 modifiers 数组, 可以包含 `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, `numLock`, `left`, `right`. -向 page 发送一个输入 `event`。 +向页面发送一个输入 `event`。 对键盘事件来说,`event` 对象还有如下属性: -* `keyCode` String (**必需**) - 特点是将作为键盘事件发送。可用的 key codes [Accelerator](accelerator.md)。 - +* `keyCode` String (**必需**) - 将字符串作为键盘事件发送。可用的 key codes [Accelerator](accelerator.md)。 对鼠标事件来说,`event` 对象还有如下属性: -* `x` Integer (**required**) -* `y` Integer (**required**) +* `x` Integer (**必需**) +* `y` Integer (**必需**) * `button` String - button 按下, 可以是 `left`, `middle`, `right` * `globalX` Integer * `globalY` Integer @@ -793,122 +1060,151 @@ app.on('ready', function () { * `hasPreciseScrollingDeltas` Boolean * `canScroll` Boolean -### `webContents.beginFrameSubscription(callback)` - +#### `contents.beginFrameSubscription(callback)` +* `onlyDirty` Boolean (可选) - 默认值为 `false` * `callback` Function - + * `frameBuffer` Buffer + * `dirtyRect` [Rectangle](structures/rectangle.md) + 开始订阅 提交 事件和捕获数据帧,当有提交事件时,使用 `callback(frameBuffer)` 调用 `callback`。 `frameBuffer` 是一个包含原始像素数据的 `Buffer`,像素数据是按照 32bit BGRA 格式有效存储的,但是实际情况是取决于处理器的字节顺序的(大多数的处理器是存放小端序的,如果是在大端序的处理器上,数据是 32bit ARGB 格式)。 -### `webContents.endFrameSubscription()` +`dirtyRect` 脏区域是一个包含 `x, y, width, height` 属性的对象,它们描述了一个页面中的重绘区域。如果 `onlyDirty` 被设置为`true`, `frameBuffer` 将只包含重绘区域。`onlyDirty` 的默认值为 `false`. + +#### `contents.endFrameSubscription()` 停止订阅帧提交事件。 -### `webContents.savePage(fullPath, saveType, callback)` +#### `contents.startDrag(item)` + +* `item` Object + * `file` String 或 `files` Array - 要拖拽的文件(可以为多个)的路径。 + * `icon` [NativeImage](native-image.md) - 在 macOS 中图标不能为空. + +设置 `item` 作为当前拖拽操作的对象。`file` 是作为拖拽文件的绝对路径。`icon` 是拖拽时光标下面显示的图像。 + +#### `contents.savePage(fullPath, saveType, callback)` * `fullPath` String - 文件的完整路径. * `saveType` String - 指定保存类型. * `HTMLOnly` - 只保存html. * `HTMLComplete` - 保存整个 page 内容. * `MHTML` - 保存完整的 html 为 MHTML. -* `callback` Function - `function(error) {}`. +* `callback` Function - `(error) => {}`. * `error` Error -如果保存界面过程初始化成功,返回 true。 +Returns `Boolean` - 如果保存界面过程初始化成功,返回 true。 ```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow() + win.loadURL('https://github.com') -win.webContents.on('did-finish-load', function () { - win.webContents.savePage('/tmp/test.html', 'HTMLComplete', function (error) { +win.webContents.on('did-finish-load', () => { + win.webContents.savePage('/tmp/test.html', 'HTMLComplete', (error) => { if (!error) console.log('Save page successfully') }) }) ``` -## 实例属性 +#### `contents.showDefinitionForSelection()` _macOS_ + +在页面上显示搜索的弹窗。 + +#### `contents.setSize(options)` + +设置页面的大小。This is only supported for `` guest contents. + +* `options` Object + * `normal` Object (可选) - Normal size of the page. This can be used in + combination with the [`disableguestresize`](web-view-tag.md#disableguestresize) + attribute to manually resize the webview guest contents. + * `width` Integer + * `height` Integer + +#### `contents.isOffscreen()` + +Returns `Boolean` - 表明是否设置了 *offscreen rendering*. + +#### `contents.startPainting()` + +如果设置了 *offscreen rendering* 并且没有绘制,开始绘制. + +#### `contents.stopPainting()` + +如果设置了 *offscreen rendering* 并且绘制了,停止绘制. + +#### `contents.isPainting()` + +Returns `Boolean` - 如果设置了 *offscreen rendering* ,返回当前是否正在绘制. + +#### `contents.setFrameRate(fps)` + +* `fps` Integer + +如果设置了 *offscreen rendering*,设置帧频为制定数值。有效范围为1-60. + +#### `contents.getFrameRate()` + +Returns `Integer` - 如果设置了 *offscreen rendering*,返回当前的帧频 + +#### `contents.invalidate()` + +全部重新绘制整个页面的内容。 + +如果设置了*offscreen rendering* ,让页面失效并且生成一个新的`'paint'`事件 + +#### `contents.getWebRTCIPHandlingPolicy()` + +Returns `String` - Returns the WebRTC IP Handling Policy. + +#### `contents.setWebRTCIPHandlingPolicy(policy)` + +* `policy` String - Specify the WebRTC IP Handling Policy. + * `default` - Exposes user's public and local IPs. This is the default + behavior. When this policy is used, WebRTC has the right to enumerate all + interfaces and bind them to discover public interfaces. + * `default_public_interface_only` - Exposes user's public IP, but does not + expose user's local IP. When this policy is used, WebRTC should only use the + default route used by http. This doesn't expose any local addresses. + * `default_public_and_private_interfaces` - Exposes user's public and local + IPs. When this policy is used, WebRTC should only use the default route used + by http. This also exposes the associated default private address. Default + route is the route chosen by the OS on a multi-homed endpoint. + * `disable_non_proxied_udp` - Does not expose public or local IPs. When this + policy is used, WebRTC should only use TCP to contact peers or servers unless + the proxy server supports UDP. + +Setting the WebRTC IP handling policy allows you to control which IPs are +exposed via WebRTC. See [BrowserLeaks](https://browserleaks.com/webrtc) for +more details. + +### 实例属性 `WebContents` 对象也有下列属性: -### `webContents.session` +#### `contents.id` + +表明 WebContents 唯一标示的整数 + +#### `contents.session` 返回这个 `webContents` 使用的 [session](session.md) 对象。 -### `webContents.hostWebContents` +#### `contents.hostWebContents` -返回这个 `webContents` 的父 `webContents`。 +返回这个 `webContents` 的父 [`WebContents`](web-contents.md)。 -### `webContents.devToolsWebContents` +#### `contents.devToolsWebContents` 获取这个 `WebContents` 的开发者工具栏的 `WebContents`。 **注意:** 用户不可保存这个对象,因为当开发者工具栏关闭的时候它的值为 `null`。 -### `webContents.debugger` +#### `contents.debugger` -调试 API 为 [remote debugging protocol][rdp] 提供交替传送。 +webContents 的 [Debugger](debugger.md) 实例. -```javascript -try { - win.webContents.debugger.attach('1.1') -} catch (err) { - console.log('Debugger attach failed : ', err) -} - -win.webContents.debugger.on('detach', function (event, reason) { - console.log('Debugger detached due to : ', reason) -}) - -win.webContents.debugger.on('message', function (event, method, params) { - if (method === 'Network.requestWillBeSent') { - if (params.request.url === 'https://www.github.com') { - win.webContents.debugger.detach() - } - } -}) - -win.webContents.debugger.sendCommand('Network.enable') -``` - -#### `webContents.debugger.attach([protocolVersion])` - -* `protocolVersion` String (可选) - 请求调试协议版本。 - -添加 `webContents` 调试。 - -#### `webContents.debugger.isAttached()` - -返回一个布尔值,标识是否已经给 `webContents` 添加了调试。 - -#### `webContents.debugger.detach()` - -删除 `webContents` 调试。 - -#### `webContents.debugger.sendCommand(method[, commandParams, callback])` - -* `method` String - 方法名, 应该是由远程调试协议定义的方法。 -* `commandParams` Object (可选) - 请求参数为 JSON 对象。 -* `callback` Function (可选) - Response - * `error` Object - 错误消息,标识命令失败。 - * `result` Object - 回复在远程调试协议中由 'returns'属性定义的命令描述。 - -发送给定命令给调试目标。 - -### Event: 'detach' - -* `event` Event -* `reason` String - 拆分调试器原因。 - -在调试 session 结束时发出事件.这在 `webContents` 关闭时或 `webContents` 请求开发者工具栏时发生。 - -### Event: 'message' - -* `event` Event -* `method` String - 方法名。 -* `params` Object - 在远程调试协议中由 'parameters' 属性定义的事件参数。 - -每当调试目标发出事件时发出。 - -[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol -[`webContents.findInPage`]: web-contents.md#webcontentsfindinpagetext-options +[keyboardevent]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent diff --git a/docs-translations/zh-CN/tutorial/electron-versioning.md b/docs-translations/zh-CN/tutorial/electron-versioning.md index 18bd3735404f..7027347b3927 100644 --- a/docs-translations/zh-CN/tutorial/electron-versioning.md +++ b/docs-translations/zh-CN/tutorial/electron-versioning.md @@ -5,7 +5,7 @@ 版本号使用参照以下规则: * 主要版本: 适用于 Electron API 的突破性变更 - 如果您从 `0.37.0` 升级到 `1.0.0`, 您将需要升级您的应用程序。 -* 次要版本: 适用于 Chrome 主要版本 和 Node 次要版本升级; 或重大的 Electron 变动 - 如果您从 `0.37.0` 升级到 `1.0.0`, 您的应用程序仍然可以正常运行, 但你可能需要解决一些小幅的变动。 -* 补丁版本: 适用于新功能的添加和 bug 修复 - 如果您从 `0.37.0` 升级到 `1.0.0`, 你的应用程序仍然像之前一样正常运行。 +* 次要版本: 适用于 Chrome 主要版本 和 Node 次要版本升级; 或重大的 Electron 变动 - 如果您从 `1.0.0` 升级到 `1.1.0`, 您的应用程序仍然可以正常运行, 但你可能需要解决一些小幅的变动。 +* 补丁版本: 适用于新功能的添加和 bug 修复 - 如果您从 `1.0.0` 升级到 `1.0.1`, 你的应用程序仍然像之前一样正常运行。 如果你使用 `electron` 或 `electron-prebuilt`,我们建议您设置固定的版本号(如 1.1.0 而不是 ^1.1.0),以确保Electron的所有升级都是由您(开发人员)进行的手动操作。 diff --git a/docs-translations/zh-CN/tutorial/windows-store-guide.md b/docs-translations/zh-CN/tutorial/windows-store-guide.md index 67cd39e5a520..a34f40d2ee40 100644 --- a/docs-translations/zh-CN/tutorial/windows-store-guide.md +++ b/docs-translations/zh-CN/tutorial/windows-store-guide.md @@ -49,8 +49,7 @@ npm install -g electron-windows-store │   └── atom.asar ├── snapshot_blob.bin ├── squirrel.exe -├── ui_resources_200_percent.pak -└── xinput1_3.dll +└── ui_resources_200_percent.pak ``` ## 步骤 2: 运行 electron-windows-store diff --git a/docs/README.md b/docs/README.md index c02e1dfbb694..dbb1421ab744 100644 --- a/docs/README.md +++ b/docs/README.md @@ -104,3 +104,6 @@ an issue: * [Debug Instructions (Windows)](development/debug-instructions-windows.md) * [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) * [Documentation Styleguide](styleguide.md) +* [Upgrading Chrome](development/upgrading-chrome.md) +* [Chromium Development](development/chromium-development.md) +* [V8 Development](development/v8-development.md) diff --git a/docs/api/browser-view.md b/docs/api/browser-view.md new file mode 100644 index 000000000000..3b7e6f9b9bf3 --- /dev/null +++ b/docs/api/browser-view.md @@ -0,0 +1,74 @@ +## Class: BrowserView + +> Create and control views. + +**Note:** The BrowserView API is currently experimental and may change or be +removed in future Electron releases. + +Process: [Main](../glossary.md#main-process) + +A `BrowserView` can be used to embed additional web content into a +`BrowserWindow`. It is like a child window, except that it is positioned +relative to its owning window. It is meant to be an alternative to the +`webview` tag. + +## Example + +```javascript +// In the main process. +const {BrowserView, BrowserWindow} = require('electron') + +let win = new BrowserWindow({width: 800, height: 600}) +win.on('closed', () => { + win = null +}) + +let view = new BrowserView({ + webPreferences: { + nodeIntegration: false + } +}) +win.addChildView(view) +view.setBounds(0, 0, 300, 300) +view.webContents.loadURL('https://electron.atom.io') +``` + +### `new BrowserView([options])` _Experimental_ + +* `options` Object (optional) + * `webPreferences` Object (optional) - See [BrowserWindow](browser-window.md). + +### Instance Properties + +Objects created with `new BrowserView` have the following properties: + +#### `view.webContents` _Experimental_ + +A [`webContents`](web-contents.md) object owned by this view. + +#### `win.id` _Experimental_ + +A `Integer` representing the unique ID of the view. + +### Instance Methods + +Objects created with `new BrowserWindow` have the following instance methods: + +#### `win.setAutoResize(options)` _Experimental_ + +* `options` Object + * `width`: If `true`, the view's width will grow and shrink together with + the window. `false` by default. + * `height`: If `true`, the view's height will grow and shrink together with + the window. `false` by default. + +#### `win.setBounds(bounds)` _Experimental_ + +* `bounds` [Rectangle](structures/rectangle.md) + +Resizes and moves the view to the supplied bounds relative to the window. + +#### `win.setBackgroundColor(color)` _Experimental_ + +* `color` String - Color in `#aarrggbb` or `#argb` form. The alpha channel is + optional. diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 2456c73e8033..8fcc3f8213d0 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -211,6 +211,9 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. width of the web page when zoomed, `false` will cause it to zoom to the width of the screen. This will also affect the behavior when calling `maximize()` directly. Default is `false`. + * `tabbingIdentifier` String (optional) - Tab group name, allows opening the + window as a native tab on macOS 10.12+. Windows with the same tabbing + identifier will be grouped together. * `webPreferences` Object (optional) - Settings of web page's features. * `devTools` Boolean (optional) - Whether to enable DevTools. If it is set to `false`, can not use `BrowserWindow.webContents.openDevTools()` to open DevTools. Default is `true`. * `nodeIntegration` Boolean (optional) - Whether node integration is enabled. Default @@ -225,6 +228,13 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. When node integration is turned off, the preload script can reintroduce Node global symbols back to the global scope. See example [here](process.md#event-loaded). + * `sandbox` Boolean (optional) - If set, this will sandbox the renderer + associated with the window, making it compatible with the Chromium + OS-level sandbox and disabling the Node.js engine. This is not the same as + the `nodeIntegration` option and the APIs available to the preload script + are more limited. Read more about the option [here](sandbox-option.md). + **Note:** This option is currently experimental and may change or be + removed in future Electron releases. * `session` [Session](session.md#class-session) (optional) - Sets the session used by the page. Instead of passing the Session object directly, you can also choose to use the `partition` option instead, which accepts a partition string. When @@ -282,7 +292,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. window. Defaults to `false`. See the [offscreen rendering tutorial](../tutorial/offscreen-rendering.md) for more details. - * `sandbox` Boolean (optional) - Whether to enable Chromium OS-level sandbox. * `contextIsolation` Boolean (optional) - Whether to run Electron APIs and the specified `preload` script in a separate JavaScript context. Defaults to `false`. The context that the `preload` script runs in will still @@ -367,6 +376,11 @@ window.onbeforeunload = (e) => { Emitted when the window is closed. After you have received this event you should remove the reference to the window and avoid using it any more. +#### Event: 'session-end' _Windows_ + +Emitted when window session is going to end due to force shutdown or machine restart +or session log off. + #### Event: 'unresponsive' Emitted when the web page becomes unresponsive. @@ -489,6 +503,14 @@ Returns: Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`. +#### Event: 'sheet-begin' _macOS_ + +Emitted when the window opens a sheet. + +#### Event: 'sheet-end' _macOS_ + +Emitted when the window has closed a sheet. + ### Static Methods The `BrowserWindow` class has the following static methods: @@ -673,10 +695,8 @@ Returns `Boolean` - Whether the window is in fullscreen mode. * `aspectRatio` Float - The aspect ratio to maintain for some portion of the content view. -* `extraSize` Object (optional) - The extra size not to be included while +* `extraSize` [Size](structures/size.md) - The extra size not to be included while maintaining the aspect ratio. - * `width` Integer - * `height` Integer This will make a window maintain an aspect ratio. The extra size allows a developer to have space, specified in pixels, not included within the aspect @@ -1282,6 +1302,13 @@ machine has a touch bar and is running on macOS 10.12.1+. **Note:** The TouchBar API is currently experimental and may change or be removed in future Electron releases. +#### `win.setBrowserView(browserView)` _Experimental_ + +* `browserView` [BrowserView](browser-view.md) + +**Note:** The BrowserView API is currently experimental and may change or be +removed in future Electron releases. + [blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [quick-look]: https://en.wikipedia.org/wiki/Quick_Look [vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc diff --git a/docs/api/client-request.md b/docs/api/client-request.md index 0b722f2f09d3..9821b44261a5 100644 --- a/docs/api/client-request.md +++ b/docs/api/client-request.md @@ -29,6 +29,11 @@ the hostname and the port number 'hostname:port' * `hostname` String (optional) - The server host name. * `port` Integer (optional) - The server's listening port number. * `path` String (optional) - The path part of the request URL. + * `redirect` String (optional) - The redirect mode for this request. Should be +one of `follow`, `error` or `manual`. Defaults to `follow`. When mode is `error`, +any redirection will be aborted. When mode is `manual` the redirection will be +deferred until [`request.followRedirect`](#requestfollowRedirect) is invoked. Listen for the [`redirect`](#event-redirect) event in +this mode to get more details about the redirect request. `options` properties such as `protocol`, `host`, `hostname`, `port` and `path` strictly follow the Node.js model as described in the @@ -65,6 +70,8 @@ Returns: * `port` Integer * `realm` String * `callback` Function + * `username` String + * `password` String Emitted when an authenticating proxy is asking for user credentials. @@ -119,6 +126,19 @@ Emitted as the last event in the HTTP request-response transaction. The `close` event indicates that no more events will be emitted on either the `request` or `response` objects. + +#### Event: 'redirect' + +Returns: + +* `statusCode` Integer +* `method` String +* `redirectUrl` String +* `responseHeaders` Object + +Emitted when there is redirection and the mode is `manual`. Calling +[`request.followRedirect`](#requestfollowRedirect) will continue with the redirection. + ### Instance Properties #### `request.chunkedEncoding` @@ -138,17 +158,18 @@ internally buffered inside Electron process memory. #### `request.setHeader(name, value)` * `name` String - An extra HTTP header name. -* `value` String - An extra HTTP header value. +* `value` Object - An extra HTTP header value. Adds an extra HTTP header. The header name will issued as it is without lowercasing. It can be called only before first write. Calling this method after -the first write will throw an error. +the first write will throw an error. If the passed value is not a `String`, its +`toString()` method will be called to obtain the final value. #### `request.getHeader(name)` * `name` String - Specify an extra header name. -Returns String - The value of a previously set extra header name. +Returns Object - The value of a previously set extra header name. #### `request.removeHeader(name)` @@ -190,3 +211,7 @@ Cancels an ongoing HTTP transaction. If the request has already emitted the `close` event, the abort operation will have no effect. Otherwise an ongoing event will emit `abort` and `close` events. Additionally, if there is an ongoing response object,it will emit the `aborted` event. + +#### `request.followRedirect()` + +Continues any deferred redirection request when the redirection mode is `manual`. diff --git a/docs/api/cookies.md b/docs/api/cookies.md index 8e6420296733..ba3cff33609f 100644 --- a/docs/api/cookies.md +++ b/docs/api/cookies.md @@ -104,3 +104,9 @@ on complete. Removes the cookies matching `url` and `name`, `callback` will called with `callback()` on complete. + +#### `cookies.flushStore(callback)` + +* `callback` Function + +Writes any unwritten cookies data to disk. diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 98bf0f3662f2..2f0848c1193e 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -40,7 +40,7 @@ The `crashReporter` module has the following methods: * `companyName` String (optional) * `submitURL` String - URL that crash reports will be sent to as POST. * `productName` String (optional) - Defaults to `app.getName()`. - * `uploadToServer` Boolean (optional) _Linux_ _macOS_ - Whether crash reports should be sent to the server + * `uploadToServer` Boolean (optional) - Whether crash reports should be sent to the server Default is `true`. * `ignoreSystemCrashHandler` Boolean (optional) - Default is `false`. * `extra` Object (optional) - An object you can define that will be sent along with the diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 85755dc03ef9..00542f402f45 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -60,8 +60,8 @@ The `desktopCapturer` module has the following methods: * `options` Object * `types` String[] - An array of Strings that lists the types of desktop sources to be captured, available types are `screen` and `window`. - * `thumbnailSize` Object (optional) - The suggested size that the media source - thumbnail should be scaled to, defaults to `{width: 150, height: 150}`. + * `thumbnailSize` [Size](structures/size.md) (optional) - The size that the media source thumbnail + should be scaled to. Default is `150` x `150`. * `callback` Function * `error` Error * `sources` [DesktopCapturerSource[]](structures/desktop-capturer-source.md) diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 230930abd948..615e3b2c6f93 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -46,16 +46,8 @@ The `dialog` module has the following methods: * `noResolveAliases` - Disable the automatic alias (symlink) path resolution. Selected aliases will now return the alias path instead of their target path. _macOS_ - * `normalizeAccessKeys` Boolean (optional) - Normalize the keyboard access keys - across platforms. Default is `false`. Enabling this assumes `&` is used in - the button labels for the placement of the keyboard shortcut access key - and labels will be converted so they work correctly on each platform, `&` - characters are removed on macOS, converted to `_` on Linux, and left - untouched on Windows. For example, a button label of `Vie&w` will be - converted to `Vie_w` on Linux and `View` on macOS and can be selected - via `Alt-W` on Windows and Linux. - * `message` String (optional) _macOS_ - Message to display above input - boxes. + * `message` String (optional) _macOS_ - Message to display above input + boxes. * `callback` Function (optional) * `filePaths` String[] - An array of file paths chosen by the user @@ -123,8 +115,9 @@ will be passed via `callback(filename)` * `browserWindow` BrowserWindow (optional) * `options` Object * `type` String (optional) - Can be `"none"`, `"info"`, `"error"`, `"question"` or - `"warning"`. On Windows, "question" displays the same icon as "info", unless - you set an icon using the "icon" option. + `"warning"`. On Windows, `"question"` displays the same icon as `"info"`, unless + you set an icon using the `"icon"` option. On macOS, both `"warning"` and + `"error"` display the same warning icon. * `buttons` String[] (optional) - Array of texts for buttons. On Windows, an empty array will result in one button labeled "OK". * `defaultId` Integer (optional) - Index of the button in the buttons array which will @@ -147,6 +140,14 @@ will be passed via `callback(filename)` others as command links in the dialog. This can make the dialog appear in the style of modern Windows apps. If you don't like this behavior, you can set `noLink` to `true`. + * `normalizeAccessKeys` Boolean (optional) - Normalize the keyboard access keys + across platforms. Default is `false`. Enabling this assumes `&` is used in + the button labels for the placement of the keyboard shortcut access key + and labels will be converted so they work correctly on each platform, `&` + characters are removed on macOS, converted to `_` on Linux, and left + untouched on Windows. For example, a button label of `Vie&w` will be + converted to `Vie_w` on Linux and `View` on macOS and can be selected + via `Alt-W` on Windows and Linux. * `callback` Function (optional) * `response` Number - The index of the button that was clicked * `checkboxChecked` Boolean - The checked state of the checkbox if @@ -175,6 +176,20 @@ it is usually used to report errors in early stage of startup. If called before the app `ready`event on Linux, the message will be emitted to stderr, and no GUI dialog will appear. +### `dialog.showCertificateTrustDialog([browserWindow, ]options, callback)` _macOS_ + +* `browserWindow` BrowserWindow (optional) +* `options` Object + * `certificate` [Certificate](structures/certificate.md) - The certificate to trust/import. + * `message` String - The message to display to the user. +* `callback` Function + +Displays a modal dialog that shows a message and certificate information, and +gives the user the option of trusting/importing the certificate. + +The `browserWindow` argument allows the dialog to attach itself to a parent +window, making it modal. + ## Sheets On macOS, dialogs are presented as sheets attached to a window if you provide diff --git a/docs/api/download-item.md b/docs/api/download-item.md index 6603b619aaf9..c07bf98a869f 100644 --- a/docs/api/download-item.md +++ b/docs/api/download-item.md @@ -100,6 +100,8 @@ Returns `Boolean` - Whether the download is paused. Resumes the download that has been paused. +**Note:** To enable resumable downloads the server you are downloading from must support range requests and provide both `Last-Modified` and `ETag` header values. Otherwise `resume()` will dismiss previously received bytes and restart the download from the beginning. + #### `downloadItem.canResume()` Resumes `Boolean` - Whether the download can resume. diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index 3ef5cdc93e9e..4f091ec6c3c9 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -98,6 +98,8 @@ By default, the frameless window is non-draggable. Apps need to specify `-webkit-app-region: no-drag` to exclude the non-draggable area from the draggable region. Note that only rectangular shapes are currently supported. +Note: `-webkit-app-region: drag` is known to have problems while the developer tools are open. See this [GitHub issue](https://github.com/electron/electron/issues/3647) for more information including a workaround. + To make the whole window draggable, you can add `-webkit-app-region: drag` as `body`'s style: diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 1c12d9fdacd1..a7486f3003cf 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -15,7 +15,7 @@ See [`Menu`](menu.md) for examples. * `browserWindow` BrowserWindow * `event` Event * `role` String (optional) - Define the action of the menu item, when specified the - `click` property will be ignored. + `click` property will be ignored. See [roles](#roles). * `type` String (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or `radio`. * `label` String - (optional) @@ -36,12 +36,16 @@ See [`Menu`](menu.md) for examples. * `position` String (optional) - This field allows fine-grained definition of the specific location within a given menu. +### Roles + +Roles allow menu items to have predefined behaviors. + It is best to specify `role` for any menu item that matches a standard role, rather than trying to manually implement the behavior in a `click` function. The built-in `role` behavior will give the best native experience. -The `label` and `accelerator` are optional when using a `role` and will default -to appropriate values for each platform. +The `label` and `accelerator` values are optional when using a `role` and will +default to appropriate values for each platform. The `role` property can have following values: @@ -63,8 +67,10 @@ The `role` property can have following values: * `resetzoom` - Reset the focused page's zoom level to the original size * `zoomin` - Zoom in the focused page by 10% * `zoomout` - Zoom out the focused page by 10% +* `editMenu` - Whole default "Edit" menu (Undo, Copy, etc.) +* `windowMenu` - Whole default "Window" menu (Minimize, Close, etc.) -On macOS `role` can also have following additional values: +The following additional roles are available on macOS: * `about` - Map to the `orderFrontStandardAboutPanel` action * `hide` - Map to the `hide` action @@ -78,8 +84,8 @@ On macOS `role` can also have following additional values: * `help` - The submenu is a "Help" menu * `services` - The submenu is a "Services" menu -When specifying `role` on macOS, `label` and `accelerator` are the only options -that will affect the MenuItem. All other options will be ignored. +When specifying a `role` on macOS, `label` and `accelerator` are the only +options that will affect the menu item. All other options will be ignored. ### Instance Properties @@ -114,4 +120,4 @@ A String representing the menu items visible label #### `menuItem.click` -A Function that is fired when the MenuItem recieves a click event +A Function that is fired when the MenuItem receives a click event diff --git a/docs/api/menu.md b/docs/api/menu.md index cd6817625a35..beceb2059c6c 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -16,8 +16,11 @@ The `menu` class has the following static methods: * `menu` Menu -Sets `menu` as the application menu on macOS. On Windows and Linux, the `menu` -will be set as each window's top menu. +Sets `menu` as the application menu on macOS. On Windows and Linux, the +`menu` will be set as each window's top menu. + +Passing `null` will remove the menu bar on Windows and Linux but has no +effect on macOS. **Note:** This API has to be called after the `ready` event of `app` module. @@ -25,13 +28,17 @@ will be set as each window's top menu. Returns `Menu` - The application menu, if set, or `null`, if not set. +**Note:** The returned `Menu` instance doesn't support dynamic addition or +removal of menu items. [Instance properties](#instance-properties) can still +be dynamically modified. + #### `Menu.sendActionToFirstResponder(action)` _macOS_ * `action` String Sends the `action` to the first responder of application. This is used for -emulating default Cocoa menu behaviors, usually you would just use the -`role` property of `MenuItem`. +emulating default macOS menu behaviors. Usually you would just use the +[`role`](menu-item.md#roles) property of a [`MenuItem`](menu-item.md). See the [macOS Cocoa Event Handling Guide](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html#//apple_ref/doc/uid/10000060i-CH3-SW7) for more information on macOS' native actions. @@ -115,76 +122,36 @@ const template = [ { label: 'Edit', submenu: [ - { - role: 'undo' - }, - { - role: 'redo' - }, - { - type: 'separator' - }, - { - role: 'cut' - }, - { - role: 'copy' - }, - { - role: 'paste' - }, - { - role: 'pasteandmatchstyle' - }, - { - role: 'delete' - }, - { - role: 'selectall' - } + {role: 'undo'}, + {role: 'redo'}, + {type: 'separator'}, + {role: 'cut'}, + {role: 'copy'}, + {role: 'paste'}, + {role: 'pasteandmatchstyle'}, + {role: 'delete'}, + {role: 'selectall'} ] }, { label: 'View', submenu: [ - { - role: 'reload' - }, - { - role: 'forcereload' - }, - { - role: 'toggledevtools' - }, - { - type: 'separator' - }, - { - role: 'resetzoom' - }, - { - role: 'zoomin' - }, - { - role: 'zoomout' - }, - { - type: 'separator' - }, - { - role: 'togglefullscreen' - } + {role: 'reload'}, + {role: 'forcereload'}, + {role: 'toggledevtools'}, + {type: 'separator'}, + {role: 'resetzoom'}, + {role: 'zoomin'}, + {role: 'zoomout'}, + {type: 'separator'}, + {role: 'togglefullscreen'} ] }, { role: 'window', submenu: [ - { - role: 'minimize' - }, - { - role: 'close' - } + {role: 'minimize'}, + {role: 'close'} ] }, { @@ -202,76 +169,37 @@ if (process.platform === 'darwin') { template.unshift({ label: app.getName(), submenu: [ - { - role: 'about' - }, - { - type: 'separator' - }, - { - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - role: 'hide' - }, - { - role: 'hideothers' - }, - { - role: 'unhide' - }, - { - type: 'separator' - }, - { - role: 'quit' - } + {role: 'about'}, + {type: 'separator'}, + {role: 'services', submenu: []}, + {type: 'separator'}, + {role: 'hide'}, + {role: 'hideothers'}, + {role: 'unhide'}, + {type: 'separator'}, + {role: 'quit'} ] }) - // Edit menu. + + // Edit menu template[1].submenu.push( - { - type: 'separator' - }, + {type: 'separator'}, { label: 'Speech', submenu: [ - { - role: 'startspeaking' - }, - { - role: 'stopspeaking' - } + {role: 'startspeaking'}, + {role: 'stopspeaking'} ] } ) - // Window menu. + + // Window menu template[3].submenu = [ - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - }, - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Zoom', - role: 'zoom' - }, - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' - } + {role: 'close'}, + {role: 'minimize'}, + {role: 'zoom'}, + {type: 'separator'}, + {role: 'front'} ] } diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 100fb0e1a222..da496436b324 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -219,10 +219,7 @@ Returns `Boolean` - Whether the image is empty. #### `image.getSize()` -Returns `Object`: - -* `width` Integer -* `height` Integer +Returns [`Size`](structures/size.md) #### `image.setTemplateImage(option)` @@ -236,11 +233,7 @@ Returns `Boolean` - Whether the image is a template image. #### `image.crop(rect)` -* `rect` Object - The area of the image to crop - * `x` Integer - * `y` Integer - * `width` Integer - * `height` Integer +* `rect` [Rectangle](structures/rectangle.md) - The area of the image to crop Returns `NativeImage` - The cropped image. diff --git a/docs/api/process.md b/docs/api/process.md index 951abc7df1af..84320ca98bb0 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -32,38 +32,38 @@ process.once('loaded', () => { ### `process.noAsar` -Setting this to `true` can disable the support for `asar` archives in Node's -built-in modules. +A `Boolean` that controls ASAR support inside your application. Setting this to `true` +will disable the support for `asar` archives in Node's built-in modules. ### `process.type` -Current process's type, can be `"browser"` (i.e. main process) or `"renderer"`. +A `String` representing the current process's type, can be `"browser"` (i.e. main process) or `"renderer"`. ### `process.versions.electron` -Electron's version string. +A `String` representing Electron's version string. ### `process.versions.chrome` -Chrome's version string. +A `String` representing Chrome's version string. ### `process.resourcesPath` -Path to the resources directory. +A `String` representing the path to the resources directory. ### `process.mas` -For Mac App Store build, this property is `true`, for other builds it is +A `Boolean`. For Mac App Store build, this property is `true`, for other builds it is `undefined`. ### `process.windowsStore` -If the app is running as a Windows Store app (appx), this property is `true`, +A `Boolean`. If the app is running as a Windows Store app (appx), this property is `true`, for otherwise it is `undefined`. ### `process.defaultApp` -When app is started by being passed as parameter to the default app, this +A `Boolean`. When app is started by being passed as parameter to the default app, this property is `true` in the main process, otherwise it is `undefined`. ## Methods diff --git a/docs/api/remote.md b/docs/api/remote.md index 0bed3ad9b511..2abb2a843110 100644 --- a/docs/api/remote.md +++ b/docs/api/remote.md @@ -141,6 +141,36 @@ The `remote` module has the following methods: * `module` String Returns `any` - The object returned by `require(module)` in the main process. +Modules specified by their relative path will resolve relative to the entrypoint +of the main process. + +e.g. + +``` +project/ +├── main +│   ├── foo.js +│   └── index.js +├── package.json +└── renderer + └── index.js +``` + +```js +// main process: main/index.js +const {app} = require('electron') +app.on('ready', () => { /* ... */ }) +``` + +```js +// some relative module: main/foo.js +module.exports = 'bar' +``` + +```js +// renderer process: renderer/index.js +const foo = require('electron').remote.require('./foo') // bar +``` ### `remote.getCurrentWindow()` diff --git a/docs/api/sandbox-option.md b/docs/api/sandbox-option.md new file mode 100644 index 000000000000..9598e47257d1 --- /dev/null +++ b/docs/api/sandbox-option.md @@ -0,0 +1,195 @@ +# `sandbox` Option + +> Create a browser window with a renderer that can run inside Chromium OS sandbox. With this +option enabled, the renderer must communicate via IPC to the main process in order to access node APIs. +However, in order to enable the Chromium OS sandbox, electron must be run with the `--enable-sandbox` +command line argument. + +One of the key security features of Chromium is that all blink rendering/JavaScript +code is executed within a sandbox. This sandbox uses OS-specific features to ensure +that exploits in the renderer process cannot harm the system. + +In other words, when the sandbox is enabled, the renderers can only make changes +to the system by delegating tasks to the main process via IPC. +[Here's](https://www.chromium.org/developers/design-documents/sandbox) more +information about the sandbox. + +Since a major feature in electron is the ability to run node.js in the +renderer process (making it easier to develop desktop applications using web +technologies), the sandbox is disabled by electron. This is because +most node.js APIs require system access. `require()` for example, is not +possible without file system permissions, which are not available in a sandboxed +environment. + +Usually this is not a problem for desktop applications since the code is always +trusted, but it makes electron less secure than chromium for displaying +untrusted web content. For applications that require more security, the +`sandbox` flag will force electron to spawn a classic chromium renderer that is +compatible with the sandbox. + +A sandboxed renderer doesn't have a node.js environment running and doesn't +expose node.js JavaScript APIs to client code. The only exception is the preload script, +which has access to a subset of the electron renderer API. + +Another difference is that sandboxed renderers don't modify any of the default +JavaScript APIs. Consequently, some APIs such as `window.open` will work as they +do in chromium (i.e. they do not return a `BrowserWindowProxy`). + +## Example + +To create a sandboxed window, simply pass `sandbox: true` to `webPreferences`: + +```js +let win +app.on('ready', () => { + win = new BrowserWindow({ + webPreferences: { + sandbox: true + } + }) + w.loadURL('http://google.com') +}) +``` + +In the above code the `BrowserWindow` that was created has node.js disabled and can communicate +only via IPC. The use of this option stops electron from creating a node.js runtime in the renderer. Also, +within this new window `window.open` follows the native behaviour (by default electron creates a `BrowserWindow` +and returns a proxy to this via `window.open`). + +It is important to note that this option alone won't enable the OS-enforced sandbox. To enable this feature, the +`--enable-sandbox` command-line argument must be passed to electron, which will +force `sandbox: true` for all `BrowserWindow` instances. + + +```js +let win +app.on('ready', () => { + // no need to pass `sandbox: true` since `--enable-sandbox` was enabled. + win = new BrowserWindow() + w.loadURL('http://google.com') +}) +``` + +Note that it is not enough to call +`app.commandLine.appendSwitch('--enable-sandbox')`, as electron/node startup +code runs after it is possible to make changes to chromium sandbox settings. The +switch must be passed to electron on the command-line: + +``` +electron --enable-sandbox app.js +``` + +It is not possible to have the OS sandbox active only for some renderers, if +`--enable-sandbox` is enabled, normal electron windows cannot be created. + +If you need to mix sandboxed and non-sandboxed renderers in one application, +simply omit the `--enable-sandbox` argument. Without this argument, windows +created with `sandbox: true` will still have node.js disabled and communicate +only via IPC, which by itself is already a gain from security POV. + +## Preload + +An app can make customizations to sandboxed renderers using a preload script. +Here's an example: + +```js +let win +app.on('ready', () => { + win = new BrowserWindow({ + webPreferences: { + sandbox: true, + preload: 'preload.js' + } + }) + w.loadURL('http://google.com') +}) +``` + +and preload.js: + +```js +// This file is loaded whenever a javascript context is created. It runs in a +// private scope that can access a subset of electron renderer APIs. We must be +// careful to not leak any objects into the global scope! +const fs = require('fs') +const {ipcRenderer} = require('electron') + +// read a configuration file using the `fs` module +const buf = fs.readFileSync('allowed-popup-urls.json') +const allowedUrls = JSON.parse(buf.toString('utf8')) + +const defaultWindowOpen = window.open + +function customWindowOpen (url, ...args) { + if (allowedUrls.indexOf(url) === -1) { + ipcRenderer.sendSync('blocked-popup-notification', location.origin, url) + return null + } + return defaultWindowOpen(url, ...args) +} + +window.open = customWindowOpen +``` + +Important things to notice in the preload script: + +- Even though the sandboxed renderer doesn't have node.js running, it still has + access to a limited node-like environment: `Buffer`, `process`, `setImmediate` + and `require` are available. +- The preload script can indirectly access all APIs from the main process through the + `remote` and `ipcRenderer` modules. This is how `fs` (used above) and other + modules are implemented: They are proxies to remote counterparts in the main + process. +- The preload script must be contained in a single script, but it is possible to have + complex preload code composed with multiple modules by using a tool like + browserify, as explained below. In fact, browserify is already used by + electron to provide a node-like environment to the preload script. + +To create a browserify bundle and use it as a preload script, something like +the following should be used: + + browserify preload/index.js \ + -x electron \ + -x fs \ + --insert-global-vars=__filename,__dirname -o preload.js + +The `-x` flag should be used with any required module that is already exposed in +the preload scope, and tells browserify to use the enclosing `require` function +for it. `--insert-global-vars` will ensure that `process`, `Buffer` and +`setImmediate` are also taken from the enclosing scope(normally browserify +injects code for those). + +Currently the `require` function provided in the preload scope exposes the +following modules: + +- `child_process` +- `electron` (crashReporter, remote and ipcRenderer) +- `fs` +- `os` +- `timers` +- `url` + +More may be added as needed to expose more electron APIs in the sandbox, but any +module in the main process can already be used through +`electron.remote.require`. + +## Status + +Please use the `sandbox` option with care, as it is still an experimental +feature. We are still not aware of the security implications of exposing some +electron renderer APIs to the preload script, but here are some things to +consider before rendering untrusted content: + +- A preload script can accidentaly leak privileged APIs to untrusted code. +- Some bug in V8 engine may allow malicious code to access the renderer preload + APIs, effectively granting full access to the system through the `remote` + module. + +Since rendering untrusted content in electron is still uncharted territory, +the APIs exposed to the sandbox preload script should be considered more +unstable than the rest of electron APIs, and may have breaking changes to fix +security issues. + +One planned enhancement that should greatly increase security is to block IPC +messages from sandboxed renderers by default, allowing the main process to +explicitly define a set of messages the renderer is allowed to send. diff --git a/docs/api/screen.md b/docs/api/screen.md index 9704f88134f0..49f0d9f55c9c 100644 --- a/docs/api/screen.md +++ b/docs/api/screen.md @@ -91,10 +91,7 @@ The `screen` module has the following methods: ### `screen.getCursorScreenPoint()` -Returns `Object`: - -* `x` Integer -* `y` Integer +Returns [`Point`](structures/point.md) The current absolute position of the mouse pointer. @@ -108,9 +105,7 @@ Returns [`Display[]`](structures/display.md) - An array of displays that are cur ### `screen.getDisplayNearestPoint(point)` -* `point` Object - * `x` Integer - * `y` Integer +* `point` [Point](structures/point.md) Returns [`Display`](structures/display.md) - The display nearest the specified point. diff --git a/docs/api/session.md b/docs/api/session.md index 7c7831c8ac37..7f249372957b 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -287,7 +287,7 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => { #### `ses.setPermissionRequestHandler(handler)` * `handler` Function - * `webContents` Object - [WebContents](web-contents.md) requesting the permission. + * `webContents` [WebContents](web-contents.md) - WebContents requesting the permission. * `permission` String - Enum of 'media', 'geolocation', 'notifications', 'midiSysex', 'pointerLock', 'fullscreen', 'openExternal'. * `callback` Function @@ -388,15 +388,15 @@ The following properties are available on instances of `Session`: #### `ses.cookies` -A Cookies object for this session. +A [Cookies](cookies.md) object for this session. #### `ses.webRequest` -A WebRequest object for this session. +A [WebRequest](web-request.md) object for this session. #### `ses.protocol` -A Protocol object (an instance of [protocol](protocol.md) module) for this session. +A [Protocol](protocol.md) object for this session. ```javascript const {app, session} = require('electron') diff --git a/docs/api/structures/display.md b/docs/api/structures/display.md index d702b35a1bec..f5f5b9866ba1 100644 --- a/docs/api/structures/display.md +++ b/docs/api/structures/display.md @@ -6,13 +6,9 @@ * `scaleFactor` Number - Output device's pixel scale factor. * `touchSupport` String - Can be `available`, `unavailable`, `unknown`. * `bounds` [Rectangle](rectangle.md) -* `size` Object - * `height` Number - * `width` Number +* `size` [Size](size.md) * `workArea` [Rectangle](rectangle.md) -* `workAreaSize` Object - * `height` Number - * `width` Number +* `workAreaSize` [Size](size.md) The `Display` object represents a physical display connected to the system. A fake `Display` may exist on a headless system, or a `Display` may correspond to diff --git a/docs/api/structures/mime-typed-buffer.md b/docs/api/structures/mime-typed-buffer.md index dc1a20d28f79..08e5cd47a4a9 100644 --- a/docs/api/structures/mime-typed-buffer.md +++ b/docs/api/structures/mime-typed-buffer.md @@ -1,4 +1,4 @@ # MimeTypedBuffer Object * `mimeType` String - The mimeType of the Buffer that you are sending -* `buffer` Buffer - The actual Buffer content +* `data` Buffer - The actual Buffer content diff --git a/docs/api/structures/point.md b/docs/api/structures/point.md new file mode 100644 index 000000000000..69b87cbdf9c4 --- /dev/null +++ b/docs/api/structures/point.md @@ -0,0 +1,4 @@ +# Point Object + +* `x` Number +* `y` Number diff --git a/docs/api/structures/size.md b/docs/api/structures/size.md new file mode 100644 index 000000000000..1d9c8b1f5a12 --- /dev/null +++ b/docs/api/structures/size.md @@ -0,0 +1,4 @@ +# Size Object + +* `width` Number +* `height` Number diff --git a/docs/api/touch-bar-button.md b/docs/api/touch-bar-button.md index 177e51bcdb20..456fc207fe98 100644 --- a/docs/api/touch-bar-button.md +++ b/docs/api/touch-bar-button.md @@ -11,6 +11,7 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `backgroundColor` String (optional) - Button background color in hex format, i.e `#ABCDEF`. * `icon` [NativeImage](native-image.md) (optional) - Button icon. + * `iconPosition` String - Can be `left`, `right` or `overlay`. * `click` Function (optional) - Function to call when the button is clicked. ### Instance Properties diff --git a/docs/api/touch-bar-scrubber.md b/docs/api/touch-bar-scrubber.md index 370c98beed1d..31f65d8770cb 100644 --- a/docs/api/touch-bar-scrubber.md +++ b/docs/api/touch-bar-scrubber.md @@ -9,9 +9,9 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `options` Object * `items` [ScrubberItem[]](structures/scrubber-item.md) - An array of items to place in this scrubber * `select` Function - Called when the user taps an item that was not the last tapped item - * `selectedIndex` - The index of the item the user selected + * `selectedIndex` Integer - The index of the item the user selected * `highlight` Function - Called when the user taps any item - * `highlightedIndex` - The index of the item the user touched + * `highlightedIndex` Integer - The index of the item the user touched * `selectedStyle` String - Selected item style. Defaults to `null`. * `overlayStyle` String - Selected overlay item style. Defaults to `null`. * `showArrowButtons` Boolean - Defaults to `false`. diff --git a/docs/api/touch-bar-segmented-control.md b/docs/api/touch-bar-segmented-control.md index 5c8a0de7eba1..42be09079082 100644 --- a/docs/api/touch-bar-segmented-control.md +++ b/docs/api/touch-bar-segmented-control.md @@ -8,18 +8,23 @@ Process: [Main](../tutorial/quick-start.md#main-process) * `options` Object * `segmentStyle` String - (Optional) Style of the segments: - * `automatic` - Default - * `rounded` - * `textured-rounded` - * `round-rect` - * `textured-square` - * `capsule` - * `small-square` - * `separated` + * `automatic` - Default. The appearance of the segmented control is + automatically determined based on the type of window in which the control + is displayed and the position within the window. + * `rounded` - The control is displayed using the rounded style. + * `textured-rounded` - The control is displayed using the textured rounded + style. + * `round-rect` - The control is displayed using the round rect style. + * `textured-square` - The control is displayed using the textured square + style. + * `capsule` - The control is displayed using the capsule style + * `small-square` - The control is displayed using the small square style. + * `separated` - The segments in the control are displayed very close to each + other but not touching. * `segments` [SegmentedControlSegment[]](structures/segmented-control-segment.md) - An array of segments to place in this control * `selectedIndex` Integer (Optional) - The index of the currently selected segment, will update automatically with user interaction * `change` Function - Called when the user selects a new segment - * `selectedIndex` - The index of the segment the user selected + * `selectedIndex` Integer - The index of the segment the user selected ### Instance Properties diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md index 7eb2eb0209be..8f1b8732f8b4 100644 --- a/docs/api/touch-bar.md +++ b/docs/api/touch-bar.md @@ -4,9 +4,11 @@ Process: [Main](../tutorial/quick-start.md#main-process) -### `new TouchBar(items)` _Experimental_ +### `new TouchBar(options)` _Experimental_ -* `items` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md))[] +* `options` - Object + * `items` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarScrubber](touch-bar-scrubber.md) | [TouchBarSegmentedControl](touch-bar-segmented-control.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md))[] + * `escapeItem` ([TouchBarButton](touch-bar-button.md) | [TouchBarColorPicker](touch-bar-color-picker.md) | [TouchBarGroup](touch-bar-group.md) | [TouchBarLabel](touch-bar-label.md) | [TouchBarPopover](touch-bar-popover.md) | [TouchBarScrubber](touch-bar-scrubber.md) | [TouchBarSegmentedControl](touch-bar-segmented-control.md) | [TouchBarSlider](touch-bar-slider.md) | [TouchBarSpacer](touch-bar-spacer.md)) (optional) Creates a new touch bar with the specified items. Use `BrowserWindow.setTouchBar` to add the `TouchBar` to a window. @@ -14,6 +16,20 @@ Creates a new touch bar with the specified items. Use **Note:** The TouchBar API is currently experimental and may change or be removed in future Electron releases. +**Tip:** If you don't have a MacBook with Touch Bar, you can use +[Touch Bar Simulator](https://github.com/sindresorhus/touch-bar-simulator) +to test Touch Bar usage in your app. + +### Instance Properties + +The following properties are available on instances of `TouchBar`: + +#### `touchBar.escapeItem` + +The `TouchBarItem` that will replace the "esc" button on the touch bar when set. +Setting to `null` restores the default "esc" button. Changing this value +immediately updates the escape item in the touch bar. + ## Examples Below is an example of a simple slot machine touch bar game with a button @@ -122,3 +138,13 @@ app.once('ready', () => { window.setTouchBar(touchBar) }) ``` + +### Running the above example + +To run the example above, you'll need to (assuming you've got a terminal open in the dirtectory you want to run the example): + +1. Save the above file to your computer as `touchbar.js` +2. Install Electron via `npm install electron` +3. Run the example inside Electron: `./node_modules/.bin/electron touchbar.js` + +You should then see a new Electron window and the app running in your touch bar (or touch bar emulator). diff --git a/docs/api/tray.md b/docs/api/tray.md index 0be970298650..141b24bbbff1 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -219,9 +219,7 @@ Displays a tray balloon. #### `tray.popUpContextMenu([menu, position])` _macOS_ _Windows_ * `menu` Menu (optional) -* `position` Object (optional) - The pop up position. - * `x` Integer - * `y` Integer +* `position` [Point](structures/point.md) (optional) - The pop up position. Pops up the context menu of the tray icon. When `menu` is passed, the `menu` will be shown instead of the tray icon's context menu. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index b91f6d9edef5..98b1cbd2f241 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -336,6 +336,7 @@ Returns: * `activeMatchOrdinal` Integer - Position of the active match. * `matches` Integer - Number of Matches. * `selectionArea` Object - Coordinates of first match region. + * `finalUpdate` Boolean Emitted when a result is available for [`webContents.findInPage`] request. @@ -374,12 +375,8 @@ Returns: * `type` String * `image` NativeImage (optional) * `scale` Float (optional) - scaling factor for the custom cursor -* `size` Object (optional) - the size of the `image` - * `width` Integer - * `height` Integer -* `hotspot` Object (optional) - coordinates of the custom cursor's hotspot - * `x` Integer - x coordinate - * `y` Integer - y coordinate +* `size` [Size](structures/size.md) (optional) - the size of the `image` +* `hotspot` [Point](structures/point.md) (optional) - coordinates of the custom cursor's hotspot Emitted when the cursor's type changes. The `type` parameter can be `default`, `crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, @@ -1066,24 +1063,16 @@ app.on('ready', () => { (default: `desktop`) * `desktop` - Desktop screen type * `mobile` - Mobile screen type - * `screenSize` Object - Set the emulated screen size (screenPosition == mobile) - * `width` Integer - Set the emulated screen width - * `height` Integer - Set the emulated screen height - * `viewPosition` Object - Position the view on the screen + * `screenSize` [Size](structures/size.md) - Set the emulated screen size (screenPosition == mobile) + * `viewPosition` [Point](structures/point.md) - Position the view on the screen (screenPosition == mobile) (default: `{x: 0, y: 0}`) - * `x` Integer - Set the x axis offset from top left corner - * `y` Integer - Set the y axis offset from top left corner * `deviceScaleFactor` Integer - Set the device scale factor (if zero defaults to original device scale factor) (default: `0`) - * `viewSize` Object - Set the emulated view size (empty means no override) - * `width` Integer - Set the emulated view width - * `height` Integer - Set the emulated view height + * `viewSize` [Size](structures/size.md) - Set the emulated view size (empty means no override) * `fitToView` Boolean - Whether emulated view should be scaled down if necessary to fit into available space (default: `false`) - * `offset` Object - Offset of the emulated view inside available space (not in - fit to view mode) (default: `{x: 0, y: 0}`) - * `x` Float - Set the x axis offset from top left corner - * `y` Float - Set the y axis offset from top left corner + * `offset` [Point](structures/point.md) - Offset of the emulated view inside available space + (not in fit to view mode) (default: `{x: 0, y: 0}`) * `scale` Float - Scale of emulated view inside available space (not in fit to view mode) (default: `1`) @@ -1247,7 +1236,7 @@ one through the `'paint'` event. #### `contents.getWebRTCIPHandlingPolicy()` -* Returns `String` - Returns the WebRTC IP Handling Policy. +Returns `String` - Returns the WebRTC IP Handling Policy. #### `contents.setWebRTCIPHandlingPolicy(policy)` diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 25ae3480f938..abe8e4d91626 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -136,6 +136,9 @@ Inserts `text` to the focused element. * `callback` Function (optional) - Called after script has been executed. * `result` Any +Returns `Promise` - A promise that resolves with the result of the executed code +or is rejected if the result of the code is a rejected promise. + Evaluates `code` in page. In the browser window some HTML APIs like `requestFullScreen` can only be diff --git a/docs/api/web-request.md b/docs/api/web-request.md index 6da98d81f1ff..a271add9da26 100644 --- a/docs/api/web-request.md +++ b/docs/api/web-request.md @@ -42,6 +42,8 @@ The following methods are available on instances of `WebRequest`: #### `webRequest.onBeforeRequest([filter, ]listener)` * `filter` Object + * `urls` String[] - Array of URL patterns that will be used to filter out the + requests that do not match the URL patterns. * `listener` Function * `details` Object * `id` Integer @@ -66,6 +68,8 @@ The `callback` has to be called with an `response` object. #### `webRequest.onBeforeSendHeaders([filter, ]listener)` * `filter` Object + * `urls` String[] - Array of URL patterns that will be used to filter out the + requests that do not match the URL patterns. * `listener` Function The `listener` will be called with `listener(details, callback)` before sending @@ -90,6 +94,8 @@ The `callback` has to be called with an `response` object. #### `webRequest.onSendHeaders([filter, ]listener)` * `filter` Object + * `urls` String[] - Array of URL patterns that will be used to filter out the + requests that do not match the URL patterns. * `listener` Function * `details` Object * `id` Integer @@ -106,6 +112,8 @@ response are visible by the time this listener is fired. #### `webRequest.onHeadersReceived([filter, ]listener)` * `filter` Object + * `urls` String[] - Array of URL patterns that will be used to filter out the + requests that do not match the URL patterns. * `listener` Function The `listener` will be called with `listener(details, callback)` when HTTP @@ -134,6 +142,8 @@ The `callback` has to be called with an `response` object. #### `webRequest.onResponseStarted([filter, ]listener)` * `filter` Object + * `urls` String[] - Array of URL patterns that will be used to filter out the + requests that do not match the URL patterns. * `listener` Function * `details` Object * `id` Integer @@ -154,6 +164,8 @@ and response headers are available. #### `webRequest.onBeforeRedirect([filter, ]listener)` * `filter` Object + * `urls` String[] - Array of URL patterns that will be used to filter out the + requests that do not match the URL patterns. * `listener` Function * `details` Object * `id` String @@ -174,6 +186,8 @@ redirect is about to occur. #### `webRequest.onCompleted([filter, ]listener)` * `filter` Object + * `urls` String[] - Array of URL patterns that will be used to filter out the + requests that do not match the URL patterns. * `listener` Function * `details` Object * `id` Integer @@ -192,6 +206,8 @@ completed. #### `webRequest.onErrorOccurred([filter, ]listener)` * `filter` Object + * `urls` String[] - Array of URL patterns that will be used to filter out the + requests that do not match the URL patterns. * `listener` Function * `details` Object * `id` Integer diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index 5efc225a1d65..1da5679c18ad 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -12,7 +12,8 @@ rendered. Unlike an `iframe`, the `webview` runs in a separate process than your app. It doesn't have the same permissions as your web page and all interactions between your app and embedded content will be asynchronous. This keeps your app -safe from the embedded content. +safe from the embedded content. **Note:** Most methods called on the +webview from the host page require a syncronous call to the main process. For security purposes, `webview` can only be used in `BrowserWindow`s that have `nodeIntegration` enabled. @@ -749,6 +750,7 @@ Returns: * `activeMatchOrdinal` Integer - Position of the active match. * `matches` Integer - Number of Matches. * `selectionArea` Object - Coordinates of first match region. + * `finalUpdate` Boolean Fired when a result is available for [`webview.findInPage`](webview-tag.md#webviewtagfindinpage) request. diff --git a/docs/api/window-open.md b/docs/api/window-open.md index 56216f551a0f..41332aa6cfd2 100644 --- a/docs/api/window-open.md +++ b/docs/api/window-open.md @@ -30,6 +30,10 @@ has to be a field of `BrowserWindow`'s options. * Node integration will always be disabled in the opened `window` if it is disabled on the parent window. +* Context isolation will always be enabled in the opened `window` if it is + enabled on the parent window. +* JavaScript will always be disabled in the opened `window` if it is disabled on + the parent window. * Non-standard features (that are not handled by Chromium or Electron) given in `features` will be passed to any registered `webContent`'s `new-window` event handler in the `additionalFeatures` argument. diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 4f7335e108fd..6bfd24257677 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -10,6 +10,9 @@ Follow the guidelines below for building Electron on Windows. * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) * [Git](http://git-scm.com) +* [Debugging Tools for Windows](https://msdn.microsoft.com/en-us/library/windows/hardware/ff551063.aspx) + if you plan on creating a full distribution since `symstore.exe` is used for + creating a symbol store from `.pdb` files. If you don't currently have a Windows installation, [dev.microsoftedge.com](https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/) diff --git a/docs/development/chromium-development.md b/docs/development/chromium-development.md new file mode 100644 index 000000000000..23506ae918fa --- /dev/null +++ b/docs/development/chromium-development.md @@ -0,0 +1,14 @@ +# Chromium Development + +> A collection of resources for learning about Chromium and tracking its development + +- [chromiumdev](https://chromiumdev-slack.herokuapp.com) on Slack +- [@ChromiumDev](https://twitter.com/ChromiumDev) on Twitter +- [@googlechrome](https://twitter.com/googlechrome) on Twitter +- [Blog](https://blog.chromium.org) +- [Code Search](https://cs.chromium.org/) +- [Source Code](https://cs.chromium.org/chromium/src/) +- [Development Calendar and Release Info](https://www.chromium.org/developers/calendar) +- [Discussion Groups](http://www.chromium.org/developers/discussion-groups) + +See also [V8 Development](v8-development.md) diff --git a/docs/development/upgrading-chrome.md b/docs/development/upgrading-chrome.md index 5cb9337cd09c..6e47a8b4f521 100644 --- a/docs/development/upgrading-chrome.md +++ b/docs/development/upgrading-chrome.md @@ -45,6 +45,46 @@ Chrome/Node API changes. - 64-bit Linux - ARM Linux +## Verify ffmpeg Support + +Electron ships with a version of `ffmpeg` that includes proprietary codecs by +default. A version without these codecs is built and distributed with each +release as well. Each Chrome upgrade should verify that switching this version is +still supported. + +You can verify Electron's support for multiple `ffmpeg` builds by loading the +following page. It should work with the default `ffmpeg` library distributed +with Electron and not work with the `ffmpeg` library built without proprietary +codecs. + +```html + + + + + Proprietary Codec Check + + +

Checking if Electron is using proprietary codecs by loading video from http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4

+

+ + + + +``` + ## Links - [Chrome Release Schedule](https://www.chromium.org/developers/calendar) diff --git a/docs/development/v8-development.md b/docs/development/v8-development.md new file mode 100644 index 000000000000..76d13299ca7e --- /dev/null +++ b/docs/development/v8-development.md @@ -0,0 +1,11 @@ +# V8 Development + +> A collection of resources for learning and using V8 + +* [V8 Tracing](https://github.com/v8/v8/wiki/Tracing-V8) +* [V8 Profiler](https://github.com/v8/v8/wiki/V8-Profiler) - Profiler combinations which are useful for profiling: `--prof`, `--trace-ic`, `--trace-opt`, `--trace-deopt`, `--print-bytecode`, `--print-opt-code` +* [V8 Interpreter Design](https://docs.google.com/document/d/11T2CRex9hXxoJwbYqVQ32yIPMh0uouUZLdyrtmMoL44/edit?ts=56f27d9d#heading=h.6jz9dj3bnr8t) +* [Optimizing compiler](https://github.com/v8/v8/wiki/TurboFan) +* [V8 GDB Debugging](https://github.com/v8/v8/wiki/GDB-JIT-Interface) + +See also [Chromium Development](chromium-development.md) diff --git a/docs/faq.md b/docs/faq.md index 0079d43f4e26..abeac23f76ed 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -148,7 +148,7 @@ npm uninstall electron npm uninstall -g electron ``` -However if your are using the built-in module but still getting this error, it +However if you are using the built-in module but still getting this error, it is very likely you are using the module in the wrong process. For example `electron.app` can only be used in the main process, while `electron.webFrame` is only available in renderer processes. diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index edebd2852cfa..96e56362b2d1 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -8,6 +8,10 @@ applications can put a custom menu in the dock menu. This guide explains how to integrate your application into those desktop environments with Electron APIs. +## Notifications + +See [Notifications](notifications.md) + ## Recent documents (Windows & macOS) Windows and macOS provide easy access to a list of recent documents opened by diff --git a/docs/tutorial/electron-versioning.md b/docs/tutorial/electron-versioning.md index cae99344a657..3f6c9ef90d43 100644 --- a/docs/tutorial/electron-versioning.md +++ b/docs/tutorial/electron-versioning.md @@ -1,21 +1,55 @@ # Electron Versioning -If you are a seasoned Node developer, you are surely aware of `semver` - and -might be used to giving your dependency management systems only rough guidelines -rather than fixed version numbers. Due to the hard dependency on Node and -Chromium, Electron is in a slightly more difficult position and does not follow -semver. You should therefore always reference a specific version of Electron. +If you've been using Node and npm for a while, you are probably aware of [Semantic Versioning], or SemVer for short. It's a convention for specifying version numbers for software that helps communicate intentions to the users of your software. -Version numbers are bumped using the following rules: +## Overview of Semantic Versioning -* Major: For breaking changes in Electron's API - if you upgrade from `0.37.0` - to `1.0.0`, you will have to update your app. -* Minor: For major Chrome and minor Node upgrades; or significant Electron - changes - if you upgrade from `1.0.0` to `1.1.0`, your app is supposed to +Semantic versions are always made up of three numbers: + +``` +major.minor.patch +``` + +Semantic version numbers are bumped (incremented) using the following rules: + +* **Major** is for changes that break backwards compatibility. +* **Minor** is for new features that don't break backwards compatibility. +* **Patch** is for bug fixes and other minor changes. + +A simple mnemonic for remembering this scheme is as follows: + +``` +breaking.feature.fix +``` + +## Electron Versioning + +Due to its dependency on Node and Chromium, it is not possible for the Electron +project to adhere to a SemVer policy. **You should therefore always +reference a specific version of Electron.** + +Electron version numbers are bumped using the following rules: + +* **Major** is for breaking changes in Electron's API. If you upgrade from `0.37.0` + to `1.0.0`, you will have to make changes to your app. +* **Minor** is for major Chrome and minor Node upgrades, or significant Electron + changes. If you upgrade from `1.5.0` to `1.6.0`, your app is supposed to still work, but you might have to work around small changes. -* Patch: For new features and bug fixes - if you upgrade from `1.0.0` to - `1.0.1`, your app will continue to work as-is. +* **Patch** is for new features and bug fixes. If you upgrade from `1.6.2` to + `1.6.3`, your app will continue to work as-is. -If you are using `electron` or `electron-prebuilt`, we recommend that you set a fixed version -number (`1.1.0` instead of `^1.1.0`) to ensure that all upgrades of Electron are -a manual operation made by you, the developer. +We recommend that you set a fixed version when installing Electron from npm: + +```sh +npm install electron --save-exact --save-dev +``` + +The `--save-exact` flag will add `electron` to your `package.json` file without +using a `^` or `~`, e.g. `1.6.2` instead of `^1.6.2`. This practice ensures that +all upgrades of Electron are a manual operation made by you, the developer. + +Alternatively, you can use the `~` prefix in your SemVer range, like `~1.6.2`. +This will lock your major and minor version, but allow new patch versions to +be installed. + +[Semantic Versioning]: http://semver.org diff --git a/docs/tutorial/windows-store-guide.md b/docs/tutorial/windows-store-guide.md index 4cca798aec65..abf964318102 100644 --- a/docs/tutorial/windows-store-guide.md +++ b/docs/tutorial/windows-store-guide.md @@ -69,8 +69,7 @@ The output should look roughly like this: │   └── atom.asar ├── snapshot_blob.bin ├── squirrel.exe -├── ui_resources_200_percent.pak -└── xinput1_3.dll +└── ui_resources_200_percent.pak ``` ## Step 2: Running electron-windows-store diff --git a/electron.gyp b/electron.gyp index 15336a4e6bf3..ce3673abf114 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.6.4', + 'version%': '1.6.7', 'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c', }, 'includes': [ @@ -126,7 +126,17 @@ 'VCManifestTool': { 'EmbedManifest': 'true', 'AdditionalManifestFiles': 'atom/browser/resources/win/atom.manifest', - } + }, + 'VCLinkerTool': { + # Chrome builds with this minimum environment which makes e.g. + # GetSystemMetrics(SM_CXSIZEFRAME) return Windows XP/2003 + # compatible metrics. See: https://crbug.com/361720 + # + # The following two settings translate to a linker flag + # of /SUBSYSTEM:WINDOWS,5.02 + 'MinimumRequiredVersion': '5.02', + 'SubSystem': '2', + }, }, 'copies': [ { @@ -159,7 +169,6 @@ '<(libchromiumcontent_dir)/natives_blob.bin', '<(libchromiumcontent_dir)/snapshot_blob.bin', 'external_binaries/d3dcompiler_47.dll', - 'external_binaries/xinput1_3.dll', ], }, ], @@ -441,7 +450,15 @@ 'sandbox_args': [ './lib/sandboxed_renderer/init.js', '-r', - './lib/sandboxed_renderer/api/exports/electron.js:electron' + './lib/sandboxed_renderer/api/exports/electron.js:electron', + '-r', + './lib/sandboxed_renderer/api/exports/fs.js:fs', + '-r', + './lib/sandboxed_renderer/api/exports/os.js:os', + '-r', + './lib/sandboxed_renderer/api/exports/path.js:path', + '-r', + './lib/sandboxed_renderer/api/exports/child_process.js:child_process' ], 'isolated_args': [ 'lib/isolated_renderer/init.js', @@ -543,6 +560,8 @@ '$(SDKROOT)/System/Library/Frameworks/Carbon.framework', '$(SDKROOT)/System/Library/Frameworks/QuartzCore.framework', '$(SDKROOT)/System/Library/Frameworks/Quartz.framework', + '$(SDKROOT)/System/Library/Frameworks/Security.framework', + '$(SDKROOT)/System/Library/Frameworks/SecurityInterface.framework', ], }, 'mac_bundle': 1, diff --git a/filenames.gypi b/filenames.gypi index 3b557fcb88a4..6107ab956e60 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -13,23 +13,24 @@ 'lib/browser/api/auto-updater/auto-updater-native.js', 'lib/browser/api/auto-updater/auto-updater-win.js', 'lib/browser/api/auto-updater/squirrel-update-win.js', + 'lib/browser/api/browser-view.js', 'lib/browser/api/browser-window.js', 'lib/browser/api/content-tracing.js', 'lib/browser/api/dialog.js', 'lib/browser/api/exports/electron.js', 'lib/browser/api/global-shortcut.js', 'lib/browser/api/ipc-main.js', - 'lib/browser/api/menu.js', - 'lib/browser/api/menu-item.js', 'lib/browser/api/menu-item-roles.js', + 'lib/browser/api/menu-item.js', + 'lib/browser/api/menu.js', 'lib/browser/api/module-list.js', 'lib/browser/api/navigation-controller.js', 'lib/browser/api/net.js', 'lib/browser/api/power-monitor.js', 'lib/browser/api/power-save-blocker.js', 'lib/browser/api/protocol.js', - 'lib/browser/api/session.js', 'lib/browser/api/screen.js', + 'lib/browser/api/session.js', 'lib/browser/api/system-preferences.js', 'lib/browser/api/touch-bar.js', 'lib/browser/api/tray.js', @@ -88,6 +89,7 @@ 'default_app/index.html', 'default_app/main.js', 'default_app/package.json', + 'default_app/renderer.js', ], 'lib_sources': [ 'atom/app/atom_content_client.cc', @@ -103,6 +105,8 @@ 'atom/browser/api/atom_api_app.h', 'atom/browser/api/atom_api_auto_updater.cc', 'atom/browser/api/atom_api_auto_updater.h', + 'atom/browser/api/atom_api_browser_view.cc', + 'atom/browser/api/atom_api_browser_view.h', 'atom/browser/api/atom_api_content_tracing.cc', 'atom/browser/api/atom_api_cookies.cc', 'atom/browser/api/atom_api_cookies.h', @@ -110,27 +114,27 @@ 'atom/browser/api/atom_api_debugger.h', 'atom/browser/api/atom_api_desktop_capturer.cc', 'atom/browser/api/atom_api_desktop_capturer.h', + 'atom/browser/api/atom_api_dialog.cc', 'atom/browser/api/atom_api_download_item.cc', 'atom/browser/api/atom_api_download_item.h', - 'atom/browser/api/atom_api_dialog.cc', 'atom/browser/api/atom_api_global_shortcut.cc', 'atom/browser/api/atom_api_global_shortcut.h', 'atom/browser/api/atom_api_menu.cc', 'atom/browser/api/atom_api_menu.h', - 'atom/browser/api/atom_api_menu_views.cc', - 'atom/browser/api/atom_api_menu_views.h', 'atom/browser/api/atom_api_menu_mac.h', 'atom/browser/api/atom_api_menu_mac.mm', + 'atom/browser/api/atom_api_menu_views.cc', + 'atom/browser/api/atom_api_menu_views.h', 'atom/browser/api/atom_api_net.cc', 'atom/browser/api/atom_api_net.h', 'atom/browser/api/atom_api_power_monitor.cc', 'atom/browser/api/atom_api_power_monitor.h', 'atom/browser/api/atom_api_power_save_blocker.cc', 'atom/browser/api/atom_api_power_save_blocker.h', - 'atom/browser/api/atom_api_render_process_preferences.cc', - 'atom/browser/api/atom_api_render_process_preferences.h', 'atom/browser/api/atom_api_protocol.cc', 'atom/browser/api/atom_api_protocol.h', + 'atom/browser/api/atom_api_render_process_preferences.cc', + 'atom/browser/api/atom_api_render_process_preferences.h', 'atom/browser/api/atom_api_screen.cc', 'atom/browser/api/atom_api_screen.h', 'atom/browser/api/atom_api_session.cc', @@ -216,6 +220,12 @@ 'atom/browser/mac/atom_application_delegate.mm', 'atom/browser/mac/dict_util.h', 'atom/browser/mac/dict_util.mm', + 'atom/browser/native_browser_view.cc', + 'atom/browser/native_browser_view.h', + 'atom/browser/native_browser_view_mac.h', + 'atom/browser/native_browser_view_mac.mm', + 'atom/browser/native_browser_view_views.h', + 'atom/browser/native_browser_view_views.cc', 'atom/browser/native_window.cc', 'atom/browser/native_window.h', 'atom/browser/native_window_views_win.cc', @@ -279,6 +289,8 @@ 'atom/browser/ui/accelerator_util_views.cc', 'atom/browser/ui/atom_menu_model.cc', 'atom/browser/ui/atom_menu_model.h', + 'atom/browser/ui/certificate_trust.h', + 'atom/browser/ui/certificate_trust_mac.mm', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', 'atom/browser/ui/cocoa/atom_touch_bar.h', @@ -311,8 +323,6 @@ 'atom/browser/ui/views/menu_bar.h', 'atom/browser/ui/views/menu_delegate.cc', 'atom/browser/ui/views/menu_delegate.h', - 'atom/browser/ui/views/menu_layout.cc', - 'atom/browser/ui/views/menu_layout.h', 'atom/browser/ui/views/menu_model_adapter.cc', 'atom/browser/ui/views/menu_model_adapter.h', 'atom/browser/ui/views/native_frame_view.cc', @@ -466,6 +476,8 @@ 'atom/renderer/api/atom_api_spell_check_client.h', 'atom/renderer/api/atom_api_web_frame.cc', 'atom/renderer/api/atom_api_web_frame.h', + 'atom/renderer/atom_render_frame_observer.cc', + 'atom/renderer/atom_render_frame_observer.h', 'atom/renderer/atom_render_view_observer.cc', 'atom/renderer/atom_render_view_observer.h', 'atom/renderer/atom_renderer_client.cc', @@ -480,6 +492,8 @@ 'atom/renderer/node_array_buffer_bridge.h', 'atom/renderer/preferences_manager.cc', 'atom/renderer/preferences_manager.h', + 'atom/renderer/renderer_client_base.cc', + 'atom/renderer/renderer_client_base.h', 'atom/renderer/web_worker_observer.cc', 'atom/renderer/web_worker_observer.h', 'atom/utility/atom_content_utility_client.cc', diff --git a/lib/browser/api/app.js b/lib/browser/api/app.js index c5865f2242b8..d1ca60280e93 100644 --- a/lib/browser/api/app.js +++ b/lib/browser/api/app.js @@ -12,11 +12,7 @@ const {EventEmitter} = require('events') Object.setPrototypeOf(App.prototype, EventEmitter.prototype) -let appPath = null - Object.assign(app, { - getAppPath () { return appPath }, - setAppPath (path) { appPath = path }, setApplicationMenu (menu) { return Menu.setApplicationMenu(menu) }, diff --git a/lib/browser/api/browser-view.js b/lib/browser/api/browser-view.js new file mode 100644 index 000000000000..60023fef92bb --- /dev/null +++ b/lib/browser/api/browser-view.js @@ -0,0 +1,8 @@ +'use strict' + +const {EventEmitter} = require('events') +const {BrowserView} = process.atomBinding('browser_view') + +Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype) + +module.exports = BrowserView diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 4b3f70139f0d..49a134f39415 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -76,13 +76,9 @@ BrowserWindow.prototype._init = function () { // Change window title to page title. this.webContents.on('page-title-updated', (event, title) => { - // The page-title-updated event is not emitted immediately (see #3645), so - // when the callback is called the BrowserWindow might have been closed. - if (this.isDestroyed()) return - // Route the event to BrowserWindow. this.emit('page-title-updated', event, title) - if (!event.defaultPrevented) this.setTitle(title) + if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title) }) // Sometimes the webContents doesn't get focus when window is shown, so we diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 834a6a6d7d8d..964461804769 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -280,6 +280,27 @@ module.exports = { showErrorBox: function (...args) { return binding.showErrorBox(...args) + }, + + showCertificateTrustDialog: function (...args) { + let [window, options, callback] = parseArgs(...args) + + if (options == null || typeof options !== 'object') { + throw new TypeError('options must be an object') + } + + let {certificate, message} = options + if (certificate == null || typeof certificate !== 'object') { + throw new TypeError('certificate must be an object') + } + + if (message == null) { + message = '' + } else if (typeof message !== 'string') { + throw new TypeError('message must be a string') + } + + return binding.showCertificateTrustDialog(window, certificate, message, callback) } } diff --git a/lib/browser/api/menu-item-roles.js b/lib/browser/api/menu-item-roles.js index 8a78670267ae..af0d35b4a56a 100644 --- a/lib/browser/api/menu-item-roles.js +++ b/lib/browser/api/menu-item-roles.js @@ -154,6 +154,68 @@ const roles = { webContents.setZoomLevel(zoomLevel - 0.5) }) } + }, + // Edit submenu (should fit both Mac & Windows) + editMenu: { + label: 'Edit', + submenu: [ + { + role: 'undo' + }, + { + role: 'redo' + }, + { + type: 'separator' + }, + { + role: 'cut' + }, + { + role: 'copy' + }, + { + role: 'paste' + }, + + process.platform === 'darwin' ? { + role: 'pasteandmatchstyle' + } : null, + + { + role: 'delete' + }, + + process.platform === 'win32' ? { + type: 'separator' + } : null, + + { + role: 'selectall' + } + ] + }, + + // Window submenu should be used for Mac only + windowMenu: { + label: 'Window', + submenu: [ + { + role: 'minimize' + }, + { + role: 'close' + }, + + process.platform === 'darwin' ? { + type: 'separator' + } : null, + + process.platform === 'darwin' ? { + role: 'front' + } : null + + ] } } @@ -176,6 +238,19 @@ exports.getDefaultAccelerator = (role) => { if (roles.hasOwnProperty(role)) return roles[role].accelerator } +exports.getDefaultSubmenu = (role) => { + if (!roles.hasOwnProperty(role)) return + + let {submenu} = roles[role] + + // remove null items from within the submenu + if (Array.isArray(submenu)) { + submenu = submenu.filter((item) => item != null) + } + + return submenu +} + exports.execute = (role, focusedWindow, focusedWebContents) => { if (!canExecuteRole(role)) return false diff --git a/lib/browser/api/menu-item.js b/lib/browser/api/menu-item.js index 98b8e9980e28..e95226d3b364 100644 --- a/lib/browser/api/menu-item.js +++ b/lib/browser/api/menu-item.js @@ -11,7 +11,7 @@ const MenuItem = function (options) { for (let key in options) { if (!(key in this)) this[key] = options[key] } - + this.submenu = this.submenu || roles.getDefaultSubmenu(this.role) if (this.submenu != null && this.submenu.constructor !== Menu) { this.submenu = Menu.buildFromTemplate(this.submenu) } diff --git a/lib/browser/api/module-list.js b/lib/browser/api/module-list.js index 3274f0b6d4fb..64b2829064b6 100644 --- a/lib/browser/api/module-list.js +++ b/lib/browser/api/module-list.js @@ -2,6 +2,7 @@ module.exports = [ {name: 'app', file: 'app'}, {name: 'autoUpdater', file: 'auto-updater'}, + {name: 'BrowserView', file: 'browser-view'}, {name: 'BrowserWindow', file: 'browser-window'}, {name: 'contentTracing', file: 'content-tracing'}, {name: 'dialog', file: 'dialog'}, diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 10d903919e9d..d32f6946eee5 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -156,9 +156,15 @@ class ClientRequest extends EventEmitter { urlStr = url.format(urlObj) } + const redirectPolicy = options.redirect || 'follow' + if (!['follow', 'error', 'manual'].includes(redirectPolicy)) { + throw new Error('redirect mode should be one of follow, error or manual') + } + let urlRequestOptions = { method: method, - url: urlStr + url: urlStr, + redirect: redirectPolicy } if (options.session) { if (options.session instanceof Session) { @@ -240,7 +246,7 @@ class ClientRequest extends EventEmitter { if (typeof name !== 'string') { throw new TypeError('`name` should be a string in setHeader(name, value).') } - if (value === undefined) { + if (value == null) { throw new Error('`value` required in setHeader("' + name + '", value).') } if (!this.urlRequest.notStarted) { @@ -249,11 +255,11 @@ class ClientRequest extends EventEmitter { const key = name.toLowerCase() this.extraHeaders[key] = value - this.urlRequest.setExtraHeader(name, value) + this.urlRequest.setExtraHeader(name, value.toString()) } getHeader (name) { - if (arguments.length < 1) { + if (name == null) { throw new Error('`name` is required for getHeader(name).') } @@ -266,7 +272,7 @@ class ClientRequest extends EventEmitter { } removeHeader (name) { - if (arguments.length < 1) { + if (name == null) { throw new Error('`name` is required for removeHeader(name).') } @@ -339,6 +345,10 @@ class ClientRequest extends EventEmitter { return this._write(data, encoding, callback, true) } + followRedirect () { + this.urlRequest.followRedirect() + } + abort () { this.urlRequest.cancel() } diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js index 74951edbac6d..4bb129449eb9 100644 --- a/lib/browser/api/touch-bar.js +++ b/lib/browser/api/touch-bar.js @@ -20,22 +20,37 @@ class TouchBar extends EventEmitter { touchBar._addToWindow(window) } - constructor (items) { + constructor (options) { super() + if (options == null) { + throw new Error('Must specify options object as first argument') + } + + let {items, escapeItem} = options + + // FIXME Support array as first argument, remove in 2.0 + if (Array.isArray(options)) { + items = options + escapeItem = null + } + if (!Array.isArray(items)) { - throw new Error('Must specify items array as first argument') + items = [] + } + + this.changeListener = (item) => { + this.emit('change', item.id, item.type) } this.windowListeners = {} this.items = {} this.ordereredItems = [] + this.escapeItem = escapeItem const registerItem = (item) => { this.items[item.id] = item - item.on('change', () => { - this.emit('change', item.id, item.type) - }) + item.on('change', this.changeListener) if (item.child instanceof TouchBar) { item.child.ordereredItems.forEach(registerItem) } @@ -49,6 +64,24 @@ class TouchBar extends EventEmitter { }) } + set escapeItem (item) { + if (item != null && !(item instanceof TouchBarItem)) { + throw new Error('Escape item must be an instance of TouchBarItem') + } + if (this.escapeItem != null) { + this.escapeItem.removeListener('change', this.changeListener) + } + this._escapeItem = item + if (this.escapeItem != null) { + this.escapeItem.on('change', this.changeListener) + } + this.emit('escape-item-change', item) + } + + get escapeItem () { + return this._escapeItem + } + _addToWindow (window) { const {id} = window @@ -62,8 +95,16 @@ class TouchBar extends EventEmitter { } this.on('change', changeListener) + const escapeItemListener = (item) => { + window._setEscapeTouchBarItem(item != null ? item : {}) + } + this.on('escape-item-change', escapeItemListener) + const interactionListener = (event, itemID, details) => { - const item = this.items[itemID] + let item = this.items[itemID] + if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) { + item = this.escapeItem + } if (item != null && item.onInteraction != null) { item.onInteraction(details) } @@ -72,6 +113,7 @@ class TouchBar extends EventEmitter { const removeListeners = () => { this.removeListener('change', changeListener) + this.removeListener('escape-item-change', escapeItemListener) window.removeListener('-touch-bar-interaction', interactionListener) window.removeListener('closed', removeListeners) window._touchBar = null @@ -81,6 +123,7 @@ class TouchBar extends EventEmitter { this.windowListeners[id] = removeListeners window._setTouchBarItems(this.ordereredItems) + escapeItemListener(this.escapeItem) } _removeFromWindow (window) { @@ -104,7 +147,7 @@ class TouchBarItem extends EventEmitter { }, set: function (value) { this[privateName] = value - this.emit('change') + this.emit('change', this) }, enumerable: true }) @@ -116,10 +159,11 @@ TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { super() if (config == null) config = {} this.type = 'button' - const {click, icon, label, backgroundColor} = config + const {click, icon, iconPosition, label, backgroundColor} = config this._addLiveProperty('label', label) this._addLiveProperty('backgroundColor', backgroundColor) this._addLiveProperty('icon', icon) + this._addLiveProperty('iconPosition', iconPosition) if (typeof click === 'function') { this.onInteraction = () => { config.click() @@ -180,6 +224,10 @@ TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { if (!(this.child instanceof TouchBar)) { this.child = new TouchBar(this.child) } + this.child.ordereredItems.forEach((item) => { + item._popover = item._popover || [] + if (!item._popover.includes(this.id)) item._popover.push(this.id) + }) } } diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 6af96e175715..c49c1be8ba64 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -268,13 +268,6 @@ WebContents.prototype._init = function () { this.reload() }) - // Delays the page-title-updated event to next tick. - this.on('-page-title-updated', function (...args) { - setImmediate(() => { - this.emit('page-title-updated', ...args) - }) - }) - app.emit('web-contents-created', {}, this) } diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index e5bfa7412386..ecf4093dcf06 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.js @@ -5,7 +5,7 @@ const {isSameOrigin} = process.atomBinding('v8_util') const parseFeaturesString = require('../common/parse-features-string') const hasProp = {}.hasOwnProperty -const frameToGuest = {} +const frameToGuest = new Map() // Copy attribute of |parent| to |child| if it is not defined in |child|. const mergeOptions = function (child, parent, visited) { @@ -48,11 +48,16 @@ const mergeBrowserWindowOptions = function (embedder, options) { options.webPreferences.nodeIntegration = false } - // Enable context isolation on child window if enable on parent window + // Enable context isolation on child window if enabled on parent window if (embedder.getWebPreferences().contextIsolation === true) { options.webPreferences.contextIsolation = true } + // Disable JavaScript on child window if disabled on parent window + if (embedder.getWebPreferences().javascript === false) { + options.webPreferences.javascript = false + } + // Sets correct openerId here to give correct options to 'new-window' event handler options.webPreferences.openerId = embedder.id @@ -87,10 +92,10 @@ const setupGuest = function (embedder, frameName, guest, options) { guest.once('closed', closedByUser) } if (frameName) { - frameToGuest[frameName] = guest + frameToGuest.set(frameName, guest) guest.frameName = frameName guest.once('closed', function () { - delete frameToGuest[frameName] + frameToGuest.delete(frameName) }) } return guestId @@ -98,7 +103,7 @@ const setupGuest = function (embedder, frameName, guest, options) { // Create a new guest created by |embedder| with |options|. const createGuest = function (embedder, url, frameName, options, postData) { - let guest = frameToGuest[frameName] + let guest = frameToGuest.get(frameName) if (frameName && (guest != null)) { guest.loadURL(url) return guest.webContents.id @@ -186,7 +191,7 @@ ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, const options = {} const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'] - const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload'] + const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload', 'javascript', 'contextIsolation'] const disposition = 'new-window' // Used to store additional features @@ -197,6 +202,10 @@ ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, if (value === undefined) { additionalFeatures.push(key) } else { + // Don't allow webPreferences to be set since it must be an object + // that cannot be directly overridden + if (key === 'webPreferences') return + if (webPreferences.includes(key)) { if (options.webPreferences == null) { options.webPreferences = {} @@ -300,7 +309,7 @@ ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, // The W3C does not seem to have word on how postMessage should work when the // origins do not match, so we do not do |canAccessWindow| check here since // postMessage across origins is useful and not harmful. - if (guestContents.getURL().indexOf(targetOrigin) === 0 || targetOrigin === '*') { + if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) { const sourceId = event.sender.id guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) } diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 13d6f882c2e1..8d543f2d7d30 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -360,15 +360,16 @@ ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, id, method, args) { } }) -ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, value) { +ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) { try { + args = unwrapArgs(event.sender, args) let obj = objectsRegistry.get(id) if (obj == null) { throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`) } - obj[name] = value + obj[name] = args[0] event.returnValue = null } catch (error) { event.returnValue = exceptionToMeta(error) diff --git a/lib/common/api/crash-reporter.js b/lib/common/api/crash-reporter.js index 658622e8f5a7..7a54e24fbc2a 100644 --- a/lib/common/api/crash-reporter.js +++ b/lib/common/api/crash-reporter.js @@ -56,7 +56,7 @@ class CrashReporter { const env = { ELECTRON_INTERNAL_CRASH_SERVICE: 1 } - spawn(process.execPath, args, { + this._crashServiceProcess = spawn(process.execPath, args, { env: env, detached: true }) diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index b3d3d2ab17cc..5e790133d383 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -139,7 +139,13 @@ const setObjectMembers = function (ref, object, metaId, members) { // Only set setter when it is writable. if (member.writable) { descriptor.set = function (value) { - ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_SET', metaId, member.name, value) + const args = wrapArgs([value]) + const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_SET', metaId, member.name, args) + // Meta will be non-null when a setter error occurred so parse it + // to a value so it gets re-thrown. + if (meta != null) { + metaToValue(meta) + } return value } } diff --git a/lib/renderer/init.js b/lib/renderer/init.js index 38441c9ec143..9b7ea9dabdb6 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -56,6 +56,7 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (ev let nodeIntegration = 'false' let preloadScript = null let isBackgroundPage = false +let appPath = null for (let arg of process.argv) { if (arg.indexOf('--guest-instance-id=') === 0) { // This is a guest web view. @@ -69,13 +70,15 @@ for (let arg of process.argv) { preloadScript = arg.substr(arg.indexOf('=') + 1) } else if (arg === '--background-page') { isBackgroundPage = true + } else if (arg.indexOf('--app-path=') === 0) { + appPath = arg.substr(arg.indexOf('=') + 1) } } if (window.location.protocol === 'chrome-devtools:') { // Override some inspector APIs. require('./inspector') - nodeIntegration = 'true' + nodeIntegration = 'false' } else if (window.location.protocol === 'chrome-extension:') { // Add implementations of chrome API. require('./chrome-api').injectTo(window.location.hostname, isBackgroundPage, window) @@ -116,6 +119,11 @@ if (nodeIntegration === 'true') { } else { global.__filename = __filename global.__dirname = __dirname + + if (appPath) { + // Search for module under the app directory + module.paths = module.paths.concat(Module._nodeModulePaths(appPath)) + } } // Redirect window.onerror to uncaughtException. diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index 21f0741a22aa..99f7aef54237 100644 --- a/lib/renderer/window-setup.js +++ b/lib/renderer/window-setup.js @@ -32,6 +32,13 @@ const resolveURL = function (url) { return a.href } +// Use this method to ensure values expected as strings in the main process +// are convertible to strings in the renderer process. This ensures exceptions +// converting values to strings are thrown in this process. +const toString = (value) => { + return value != null ? `${value}` : value +} + const windowProxies = {} const getOrCreateProxy = (ipcRenderer, guestId) => { @@ -82,7 +89,7 @@ function BrowserWindowProxy (ipcRenderer, guestId) { } this.postMessage = (message, targetOrigin) => { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', guestId, message, targetOrigin, window.location.origin) + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', guestId, message, toString(targetOrigin), window.location.origin) } this.eval = (...args) => { @@ -112,7 +119,7 @@ module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => { if (url != null && url !== '') { url = resolveURL(url) } - const guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, features) + const guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features)) if (guestId != null) { return getOrCreateProxy(ipcRenderer, guestId) } else { @@ -121,11 +128,11 @@ module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => { } window.alert = function (message, title) { - ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_ALERT', message, title) + ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_ALERT', toString(message), toString(title)) } window.confirm = function (message, title) { - return ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CONFIRM', message, title) + return ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CONFIRM', toString(message), toString(title)) } // But we do not support prompt(). @@ -157,7 +164,7 @@ module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => { } window.history.go = function (offset) { - sendHistoryOperation(ipcRenderer, 'goToOffset', offset) + sendHistoryOperation(ipcRenderer, 'goToOffset', +offset) } defineProperty(window.history, 'length', { diff --git a/lib/sandboxed_renderer/api/exports/child_process.js b/lib/sandboxed_renderer/api/exports/child_process.js new file mode 100644 index 000000000000..ff39e96a120e --- /dev/null +++ b/lib/sandboxed_renderer/api/exports/child_process.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.require('child_process') diff --git a/lib/sandboxed_renderer/api/exports/electron.js b/lib/sandboxed_renderer/api/exports/electron.js index 2a1d34123116..02f23be3966d 100644 --- a/lib/sandboxed_renderer/api/exports/electron.js +++ b/lib/sandboxed_renderer/api/exports/electron.js @@ -11,6 +11,12 @@ Object.defineProperties(exports, { return require('../../../renderer/api/remote') } }, + crashReporter: { + enumerable: true, + get: function () { + return require('../../../common/api/crash-reporter') + } + }, CallbacksRegistry: { get: function () { return require('../../../common/api/callbacks-registry') diff --git a/lib/sandboxed_renderer/api/exports/fs.js b/lib/sandboxed_renderer/api/exports/fs.js new file mode 100644 index 000000000000..7342908e59a9 --- /dev/null +++ b/lib/sandboxed_renderer/api/exports/fs.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.require('fs') diff --git a/lib/sandboxed_renderer/api/exports/os.js b/lib/sandboxed_renderer/api/exports/os.js new file mode 100644 index 000000000000..ecd0d38a63a6 --- /dev/null +++ b/lib/sandboxed_renderer/api/exports/os.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.require('os') diff --git a/lib/sandboxed_renderer/api/exports/path.js b/lib/sandboxed_renderer/api/exports/path.js new file mode 100644 index 000000000000..f2b2f2a77fd4 --- /dev/null +++ b/lib/sandboxed_renderer/api/exports/path.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.require('path') diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 7f2d252865a8..5194f9515448 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -21,23 +21,25 @@ for (let prop of Object.keys(events.EventEmitter.prototype)) { Object.setPrototypeOf(process, events.EventEmitter.prototype) const electron = require('electron') +const fs = require('fs') const preloadModules = new Map([ - ['electron', electron] + ['child_process', require('child_process')], + ['electron', electron], + ['fs', fs], + ['os', require('os')], + ['path', require('path')], + ['url', require('url')], + ['timers', require('timers')] ]) -const extraModules = [ - 'fs' -] -for (let extraModule of extraModules) { - preloadModules.set(extraModule, electron.remote.require(extraModule)) -} - -// Fetch the preload script using the "fs" module proxy. -let preloadSrc = preloadModules.get('fs').readFileSync(preloadPath).toString() +const preloadSrc = fs.readFileSync(preloadPath).toString() // Pass different process object to the preload script(which should not have // access to things like `process.atomBinding`). const preloadProcess = new events.EventEmitter() +preloadProcess.crash = () => binding.crash() +process.platform = preloadProcess.platform = electron.remote.process.platform +process.execPath = preloadProcess.execPath = electron.remote.process.execPath process.on('exit', () => preloadProcess.emit('exit')) // This is the `require` function that will be visible to the preload script @@ -67,12 +69,13 @@ function preloadRequire (module) { // and any `require('electron')` calls in `preload.js` will work as expected // since browserify won't try to include `electron` in the bundle, falling back // to the `preloadRequire` function above. -let preloadWrapperSrc = `(function(require, process, Buffer, global) { +const preloadWrapperSrc = `(function(require, process, Buffer, global, setImmediate) { ${preloadSrc} })` // eval in window scope: // http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2 const geval = eval -let preloadFn = geval(preloadWrapperSrc) -preloadFn(preloadRequire, preloadProcess, Buffer, global) +const preloadFn = geval(preloadWrapperSrc) +const {setImmediate} = require('timers') +preloadFn(preloadRequire, preloadProcess, Buffer, global, setImmediate) diff --git a/package.json b/package.json index d321d7831f05..bfb35f29689a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electron", - "version": "1.6.4", + "version": "1.6.7", "devDependencies": { "asar": "^0.11.0", "browserify": "^13.1.0", diff --git a/script/cibuild b/script/cibuild index 92dec9db2dee..419404fca8a5 100755 --- a/script/cibuild +++ b/script/cibuild @@ -86,11 +86,10 @@ def main(): run_script('create-dist.py') run_script('upload.py') else: - if PLATFORM == 'win32': - os.environ['OUTPUT_TO_FILE'] = 'output.log' run_script('build.py', ['-c', 'D']) if PLATFORM == 'win32' or target_arch == 'x64': run_script('test.py', ['--ci']) + run_script('verify-ffmpeg.py') def run_script(script, args=[]): diff --git a/script/create-dist.py b/script/create-dist.py index 35a109cfa3f5..27032400f3d3 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import argparse import glob import os import re @@ -42,7 +43,6 @@ TARGET_BINARIES = { 'content_resources_200_percent.pak', 'ui_resources_200_percent.pak', 'views_resources_200_percent.pak', - 'xinput1_3.dll', 'natives_blob.bin', 'snapshot_blob.bin', ], @@ -87,7 +87,9 @@ def main(): copy_chrome_binary('mksnapshot') copy_license() - if PLATFORM != 'win32': + args = parse_args() + + if PLATFORM != 'win32' and not args.no_api_docs: create_api_json_schema() if PLATFORM == 'linux': @@ -242,5 +244,13 @@ def create_symbols_zip(): make_zip(os.path.join(DIST_DIR, pdb_name), pdbs + licenses, []) +def parse_args(): + parser = argparse.ArgumentParser(description='Create Electron Distribution') + parser.add_argument('--no_api_docs', + action='store_true', + help='Skip generating the Electron API Documentation!') + return parser.parse_args() + + if __name__ == '__main__': sys.exit(main()) diff --git a/script/lib/config.py b/script/lib/config.py index fc87d083750c..5818571089ff 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -9,7 +9,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' LIBCHROMIUMCONTENT_COMMIT = os.getenv('LIBCHROMIUMCONTENT_COMMIT') or \ - '44448acf6a21024b9adb7140ffef6402a509f8bf' + '4a0e32606e52c12c50c2e3a0973d015d8cdff494' PLATFORM = { 'cygwin': 'win32', diff --git a/script/verify-ffmpeg.py b/script/verify-ffmpeg.py new file mode 100755 index 000000000000..9d9dba067030 --- /dev/null +++ b/script/verify-ffmpeg.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +import os +import shutil +import subprocess +import sys + +from lib.util import electron_gyp, rm_rf + + +SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +FFMPEG_LIBCC_PATH = os.path.join(SOURCE_ROOT, 'vendor', 'brightray', 'vendor', + 'download', 'libchromiumcontent', 'ffmpeg') + +PROJECT_NAME = electron_gyp()['project_name%'] +PRODUCT_NAME = electron_gyp()['product_name%'] + + +def main(): + os.chdir(SOURCE_ROOT) + + if len(sys.argv) == 2 and sys.argv[1] == '-R': + config = 'R' + else: + config = 'D' + + app_path = create_app_copy(config) + + if sys.platform == 'darwin': + electron = os.path.join(app_path, 'Contents', 'MacOS', PRODUCT_NAME) + ffmpeg_name = 'libffmpeg.dylib' + ffmpeg_app_path = os.path.join(app_path, 'Contents', 'Frameworks', + '{0} Framework.framework'.format(PROJECT_NAME), + 'Libraries') + elif sys.platform == 'win32': + electron = os.path.join(app_path, '{0}.exe'.format(PROJECT_NAME)) + ffmpeg_app_path = app_path + ffmpeg_name = 'ffmpeg.dll' + else: + electron = os.path.join(app_path, PROJECT_NAME) + ffmpeg_app_path = app_path + ffmpeg_name = 'libffmpeg.so' + + # Copy ffmpeg without proprietary codecs into app + shutil.copy(os.path.join(FFMPEG_LIBCC_PATH, ffmpeg_name), ffmpeg_app_path) + + returncode = 0 + try: + test_path = os.path.join('spec', 'fixtures', 'no-proprietary-codecs.js') + subprocess.check_call([electron, test_path] + sys.argv[1:]) + except subprocess.CalledProcessError as e: + returncode = e.returncode + except KeyboardInterrupt: + returncode = 0 + + return returncode + + +# Create copy of app to install ffmpeg library without proprietary codecs into +def create_app_copy(config): + initial_app_path = os.path.join(SOURCE_ROOT, 'out', config) + app_path = os.path.join(SOURCE_ROOT, 'out', config + '-no-proprietary-codecs') + + if sys.platform == 'darwin': + app_name = '{0}.app'.format(PRODUCT_NAME) + initial_app_path = os.path.join(initial_app_path, app_name) + app_path = os.path.join(app_path, app_name) + + rm_rf(app_path) + shutil.copytree(initial_app_path, app_path, symlinks=True) + return app_path + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index 301d6ae4bf47..9a9b4334bceb 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -137,6 +137,16 @@ describe('app module', function () { done() }) }) + + it('closes all windows', function (done) { + var appPath = path.join(__dirname, 'fixtures', 'api', 'exit-closes-all-windows-app') + var electronPath = remote.getGlobal('process').execPath + appProcess = ChildProcess.spawn(electronPath, [appPath]) + appProcess.on('close', function (code) { + assert.equal(code, 123) + done() + }) + }) }) describe('app.relaunch', function () { diff --git a/spec/api-auto-updater-spec.js b/spec/api-auto-updater-spec.js index 0716c2245df8..df77822e456d 100644 --- a/spec/api-auto-updater-spec.js +++ b/spec/api-auto-updater-spec.js @@ -1,6 +1,6 @@ const assert = require('assert') -const autoUpdater = require('electron').remote.autoUpdater -const ipcRenderer = require('electron').ipcRenderer +const {autoUpdater} = require('electron').remote +const {ipcRenderer} = require('electron') // Skip autoUpdater tests in MAS build. if (!process.mas) { @@ -64,5 +64,25 @@ if (!process.mas) { autoUpdater.quitAndInstall() }) }) + + describe('error event', function () { + it('serializes correctly over the remote module', function (done) { + if (process.platform === 'linux') { + return done() + } + + autoUpdater.once('error', function (error) { + assert.equal(error instanceof Error, true) + assert.deepEqual(Object.getOwnPropertyNames(error), ['stack', 'message', 'name']) + done() + }) + + autoUpdater.setFeedURL('') + + if (process.platform === 'win32') { + autoUpdater.checkForUpdates() + } + }) + }) }) } diff --git a/spec/api-browser-view-spec.js b/spec/api-browser-view-spec.js new file mode 100644 index 000000000000..3ccb9502c237 --- /dev/null +++ b/spec/api-browser-view-spec.js @@ -0,0 +1,92 @@ +'use strict' + +const assert = require('assert') +const {closeWindow} = require('./window-helpers') + +const {remote} = require('electron') +const {BrowserView, BrowserWindow} = remote + +describe('View module', function () { + var w = null + var view = null + + beforeEach(function () { + w = new BrowserWindow({ + show: false, + width: 400, + height: 400, + webPreferences: { + backgroundThrottling: false + } + }) + }) + + afterEach(function () { + if (view) { + view.destroy() + view = null + } + + return closeWindow(w).then(function () { w = null }) + }) + + describe('BrowserView.setBackgroundColor()', function () { + it('does not throw for valid args', function () { + view = new BrowserView() + view.setBackgroundColor('#000') + }) + + it('throws for invalid args', function () { + view = new BrowserView() + assert.throws(function () { + view.setBackgroundColor(null) + }, /conversion failure/) + }) + }) + + describe('BrowserView.setAutoResize()', function () { + it('does not throw for valid args', function () { + view = new BrowserView() + view.setAutoResize({}) + view.setAutoResize({ width: true, height: false }) + }) + + it('throws for invalid args', function () { + view = new BrowserView() + assert.throws(function () { + view.setAutoResize(null) + }, /conversion failure/) + }) + }) + + describe('BrowserView.setBounds()', function () { + it('does not throw for valid args', function () { + view = new BrowserView() + view.setBounds({ x: 0, y: 0, width: 1, height: 1 }) + }) + + it('throws for invalid args', function () { + view = new BrowserView() + assert.throws(function () { + view.setBounds(null) + }, /conversion failure/) + assert.throws(function () { + view.setBounds({}) + }, /conversion failure/) + }) + }) + + describe('BrowserWindow.setBrowserView()', function () { + it('does not throw for valid args', function () { + view = new BrowserView() + w.setBrowserView(view) + }) + + it('does not throw if called multiple times with same view', function () { + view = new BrowserView() + w.setBrowserView(view) + w.setBrowserView(view) + w.setBrowserView(view) + }) + }) +}) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index b5985e4b7406..34db4a6ae79a 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -86,6 +86,42 @@ describe('BrowserWindow module', function () { }) describe('BrowserWindow.close()', function () { + let server + + before(function (done) { + server = http.createServer((request, response) => { + switch (request.url) { + case '/404': + response.statusCode = '404' + response.end() + break + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end('hello') + break + case '/title': + response.statusCode = '200' + response.end('Hello') + break + default: + done('unsupported endpoint') + } + }).listen(0, '127.0.0.1', () => { + server.url = 'http://127.0.0.1:' + server.address().port + done() + }) + }) + + after(function () { + server.close() + server = null + }) + it('should emit unload handler', function (done) { w.webContents.on('did-finish-load', function () { w.close() @@ -109,6 +145,38 @@ describe('BrowserWindow module', function () { }) w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html')) }) + + it('should not crash when invoked synchronously inside navigation observer', function (done) { + const events = [ + { name: 'did-start-loading', url: `${server.url}/200` }, + { name: 'did-get-redirect-request', url: `${server.url}/301` }, + { name: 'did-get-response-details', url: `${server.url}/200` }, + { name: 'dom-ready', url: `${server.url}/200` }, + { name: 'page-title-updated', url: `${server.url}/title` }, + { name: 'did-stop-loading', url: `${server.url}/200` }, + { name: 'did-finish-load', url: `${server.url}/200` }, + { name: 'did-frame-finish-load', url: `${server.url}/200` }, + { name: 'did-fail-load', url: `${server.url}/404` } + ] + const responseEvent = 'window-webContents-destroyed' + + function* genNavigationEvent () { + let eventOptions = null + while ((eventOptions = events.shift()) && events.length) { + let w = new BrowserWindow({show: false}) + eventOptions.id = w.id + eventOptions.responseEvent = responseEvent + ipcRenderer.send('test-webcontents-navigation-observer', eventOptions) + yield 1 + } + } + + let gen = genNavigationEvent() + ipcRenderer.on(responseEvent, function () { + if (!gen.next().value) done() + }) + gen.next() + }) }) describe('window.close()', function () { @@ -692,7 +760,7 @@ describe('BrowserWindow module', function () { }) }) - describe('"title-bar-style" option', function () { + describe('"titleBarStyle" option', function () { if (process.platform !== 'darwin') { return } @@ -772,6 +840,20 @@ describe('BrowserWindow module', function () { }) }) + describe('"tabbingIdentifier" option', function () { + it('can be set on a window', function () { + w.destroy() + w = new BrowserWindow({ + tabbingIdentifier: 'group1' + }) + w.destroy() + w = new BrowserWindow({ + tabbingIdentifier: 'group2', + frame: false + }) + }) + }) + describe('"web-preferences" option', function () { afterEach(function () { ipcMain.removeAllListeners('answer') @@ -1177,6 +1259,54 @@ describe('BrowserWindow module', function () { }) }) + describe('sheet-begin event', function () { + if (process.platform !== 'darwin') { + return + } + + let sheet = null + + afterEach(function () { + return closeWindow(sheet, {assertSingleWindow: false}).then(function () { sheet = null }) + }) + + it('emits when window opens a sheet', function (done) { + w.show() + w.once('sheet-begin', function () { + sheet.close() + done() + }) + sheet = new BrowserWindow({ + modal: true, + parent: w + }) + }) + }) + + describe('sheet-end event', function () { + if (process.platform !== 'darwin') { + return + } + + let sheet = null + + afterEach(function () { + return closeWindow(sheet, {assertSingleWindow: false}).then(function () { sheet = null }) + }) + + it('emits when window has closed a sheet', function (done) { + w.show() + sheet = new BrowserWindow({ + modal: true, + parent: w + }) + w.once('sheet-end', function () { + done() + }) + sheet.close() + }) + }) + describe('beginFrameSubscription method', function () { // This test is too slow, only test it on CI. if (!isCI) return @@ -1460,13 +1590,19 @@ describe('BrowserWindow module', function () { // Only implemented on macOS. if (process.platform !== 'darwin') return - it('can be changed with setKiosk method', function () { + it('can be changed with setKiosk method', function (done) { w.destroy() w = new BrowserWindow() w.setKiosk(true) assert.equal(w.isKiosk(), true) - w.setKiosk(false) - assert.equal(w.isKiosk(), false) + + w.once('enter-full-screen', () => { + w.setKiosk(false) + assert.equal(w.isKiosk(), false) + }) + w.once('leave-full-screen', () => { + done() + }) }) }) diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index c269cfe1324b..92020dbea623 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -1,5 +1,6 @@ const assert = require('assert') const childProcess = require('child_process') +const fs = require('fs') const http = require('http') const multiparty = require('multiparty') const path = require('path') @@ -11,85 +12,202 @@ const {remote} = require('electron') const {app, BrowserWindow, crashReporter} = remote.require('electron') describe('crashReporter module', function () { - var fixtures = path.resolve(__dirname, 'fixtures') - var w = null + if (process.mas) { + return + } + var originalTempDirectory = null var tempDirectory = null - beforeEach(function () { - w = new BrowserWindow({ - show: false - }) + before(function () { tempDirectory = temp.mkdirSync('electronCrashReporterSpec-') originalTempDirectory = app.getPath('temp') app.setPath('temp', tempDirectory) }) - afterEach(function () { + after(function () { app.setPath('temp', originalTempDirectory) - return closeWindow(w).then(function () { w = null }) }) - if (process.mas) { - return + var fixtures = path.resolve(__dirname, 'fixtures') + const generateSpecs = (description, browserWindowOpts) => { + describe(description, function () { + var w = null + var stopServer = null + + beforeEach(function () { + stopServer = null + w = new BrowserWindow(Object.assign({ + show: false + }, browserWindowOpts)) + }) + + afterEach(function () { + return closeWindow(w).then(function () { w = null }) + }) + + afterEach(function () { + stopCrashService() + }) + + afterEach(function (done) { + if (stopServer != null) { + stopServer(done) + } else { + done() + } + }) + + it('should send minidump when renderer crashes', function (done) { + if (process.env.APPVEYOR === 'True') return done() + if (process.env.TRAVIS === 'true') return done() + + this.timeout(120000) + + stopServer = startServer({ + callback (port) { + const crashUrl = url.format({ + protocol: 'file', + pathname: path.join(fixtures, 'api', 'crash.html'), + search: '?port=' + port + }) + w.loadURL(crashUrl) + }, + processType: 'renderer', + done: done + }) + }) + + it('should send minidump when node processes crash', function (done) { + if (process.env.APPVEYOR === 'True') return done() + if (process.env.TRAVIS === 'true') return done() + + this.timeout(120000) + + stopServer = startServer({ + callback (port) { + const crashesDir = path.join(app.getPath('temp'), `${process.platform === 'win32' ? 'Zombies' : app.getName()} Crashes`) + const version = app.getVersion() + const crashPath = path.join(fixtures, 'module', 'crash.js') + + if (process.platform === 'win32') { + const crashServiceProcess = childProcess.spawn(process.execPath, [ + `--reporter-url=http://127.0.0.1:${port}`, + '--application-name=Zombies', + `--crashes-directory=${crashesDir}` + ], { + env: { + ELECTRON_INTERNAL_CRASH_SERVICE: 1 + }, + detached: true + }) + remote.process.crashServicePid = crashServiceProcess.pid + } + + childProcess.fork(crashPath, [port, version, crashesDir], {silent: true}) + }, + processType: 'browser', + done: done + }) + }) + + it('should not send minidump if uploadToServer is false', function (done) { + this.timeout(120000) + + let dumpFile + let crashesDir = crashReporter.getCrashesDirectory() + const existingDumpFiles = new Set() + if (process.platform === 'darwin') { + // crashpad puts the dump files in the "completed" subdirectory + crashesDir = path.join(crashesDir, 'completed') + crashReporter.setUploadToServer(false) + } + const testDone = (uploaded) => { + if (uploaded) { + return done(new Error('Uploaded crash report')) + } + if (process.platform === 'darwin') { + crashReporter.setUploadToServer(true) + } + assert(fs.existsSync(dumpFile)) + done() + } + + let pollInterval + const pollDumpFile = () => { + fs.readdir(crashesDir, (err, files) => { + if (err) { + return + } + const dumps = files.filter((file) => /\.dmp$/.test(file) && !existingDumpFiles.has(file)) + if (!dumps.length) { + return + } + assert.equal(1, dumps.length) + dumpFile = path.join(crashesDir, dumps[0]) + clearInterval(pollInterval) + // dump file should not be deleted when not uploading, so we wait + // 1s and assert it still exists in `testDone` + setTimeout(testDone, 1000) + }) + } + + remote.ipcMain.once('list-existing-dumps', (event) => { + fs.readdir(crashesDir, (err, files) => { + if (!err) { + for (const file of files) { + if (/\.dmp$/.test(file)) { + existingDumpFiles.add(file) + } + } + } + event.returnValue = null // allow the renderer to crash + pollInterval = setInterval(pollDumpFile, 100) + }) + }) + + stopServer = startServer({ + callback (port) { + const crashUrl = url.format({ + protocol: 'file', + pathname: path.join(fixtures, 'api', 'crash.html'), + search: `?port=${port}&skipUpload=1` + }) + w.loadURL(crashUrl) + }, + processType: 'renderer', + done: testDone.bind(null, true) + }) + }) + + it('should send minidump with updated extra parameters', function (done) { + if (process.env.APPVEYOR === 'True') return done() + if (process.env.TRAVIS === 'true') return done() + + this.timeout(120000) + + stopServer = startServer({ + callback (port) { + const crashUrl = url.format({ + protocol: 'file', + pathname: path.join(fixtures, 'api', 'crash-restart.html'), + search: '?port=' + port + }) + w.loadURL(crashUrl) + }, + processType: 'renderer', + done: done + }) + }) + }) } - it('should send minidump when renderer crashes', function (done) { - if (process.env.APPVEYOR === 'True') return done() - if (process.env.TRAVIS === 'true') return done() - - this.timeout(120000) - - startServer({ - callback (port) { - const crashUrl = url.format({ - protocol: 'file', - pathname: path.join(fixtures, 'api', 'crash.html'), - search: '?port=' + port - }) - w.loadURL(crashUrl) - }, - processType: 'renderer', - done: done - }) - }) - - it('should send minidump when node processes crash', function (done) { - if (process.env.APPVEYOR === 'True') return done() - if (process.env.TRAVIS === 'true') return done() - - this.timeout(120000) - - startServer({ - callback (port) { - const crashesDir = path.join(app.getPath('temp'), `${app.getName()} Crashes`) - const version = app.getVersion() - const crashPath = path.join(fixtures, 'module', 'crash.js') - childProcess.fork(crashPath, [port, version, crashesDir], {silent: true}) - }, - processType: 'browser', - done: done - }) - }) - - it('should send minidump with updated extra parameters', function (done) { - if (process.env.APPVEYOR === 'True') return done() - if (process.env.TRAVIS === 'true') return done() - - this.timeout(10000) - - startServer({ - callback (port) { - const crashUrl = url.format({ - protocol: 'file', - pathname: path.join(fixtures, 'api', 'crash-restart.html'), - search: '?port=' + port - }) - w.loadURL(crashUrl) - }, - processType: 'renderer', - done: done - }) + generateSpecs('without sandbox', {}) + generateSpecs('with sandbox', { + webPreferences: { + sandbox: true, + preload: path.join(fixtures, 'module', 'preload-sandbox.js') + } }) describe('.start(options)', function () { @@ -163,7 +281,6 @@ const waitForCrashReport = () => { const startServer = ({callback, processType, done}) => { var called = false var server = http.createServer((req, res) => { - server.close() var form = new multiparty.Form() form.parse(req, (error, fields) => { if (error) throw error @@ -192,6 +309,15 @@ const startServer = ({callback, processType, done}) => { }) }) }) + + const activeConnections = new Set() + server.on('connection', (connection) => { + activeConnections.add(connection) + connection.once('close', () => { + activeConnections.delete(connection) + }) + }) + let {port} = remote.process server.listen(port, '127.0.0.1', () => { port = server.address().port @@ -204,4 +330,27 @@ const startServer = ({callback, processType, done}) => { } callback(port) }) + + return function stopServer (done) { + for (const connection of activeConnections) { + connection.destroy() + } + server.close(function () { + done() + }) + } +} + +const stopCrashService = () => { + const {crashServicePid} = remote.process + if (crashServicePid) { + remote.process.crashServicePid = 0 + try { + process.kill(crashServicePid) + } catch (error) { + if (error.code !== 'ESRCH') { + throw error + } + } + } } diff --git a/spec/api-debugger-spec.js b/spec/api-debugger-spec.js index f8597c03409b..83b114f72380 100644 --- a/spec/api-debugger-spec.js +++ b/spec/api-debugger-spec.js @@ -1,4 +1,5 @@ const assert = require('assert') +const http = require('http') const path = require('path') const {closeWindow} = require('./window-helpers') const BrowserWindow = require('electron').remote.BrowserWindow @@ -70,6 +71,15 @@ describe('debugger module', function () { }) describe('debugger.sendCommand', function () { + let server + + afterEach(function () { + if (server != null) { + server.close() + server = null + } + }) + it('retuns response', function (done) { w.webContents.loadURL('about:blank') try { @@ -125,5 +135,33 @@ describe('debugger module', function () { done() }) }) + + it('handles invalid unicode characters in message', function (done) { + try { + w.webContents.debugger.attach() + } catch (err) { + done('unexpected error : ' + err) + } + + w.webContents.debugger.on('message', (event, method, params) => { + if (method === 'Network.loadingFinished') { + w.webContents.debugger.sendCommand('Network.getResponseBody', { + requestId: params.requestId + }, () => { + done() + }) + } + }) + + server = http.createServer((req, res) => { + res.setHeader('Content-Type', 'text/plain; charset=utf-8') + res.end('\uFFFF') + }) + + server.listen(0, '127.0.0.1', () => { + w.webContents.debugger.sendCommand('Network.enable') + w.loadURL(`http://127.0.0.1:${server.address().port}`) + }) + }) }) }) diff --git a/spec/api-dialog-spec.js b/spec/api-dialog-spec.js index 6b245c2b65f7..601e28ca53ca 100644 --- a/spec/api-dialog-spec.js +++ b/spec/api-dialog-spec.js @@ -93,4 +93,20 @@ describe('dialog module', () => { }, /Error processing argument at index 1/) }) }) + + describe('showCertificateTrustDialog', () => { + it('throws errors when the options are invalid', () => { + assert.throws(() => { + dialog.showCertificateTrustDialog() + }, /options must be an object/) + + assert.throws(() => { + dialog.showCertificateTrustDialog({}) + }, /certificate must be an object/) + + assert.throws(() => { + dialog.showCertificateTrustDialog({certificate: {}, message: false}) + }, /message must be a string/) + }) + }) }) diff --git a/spec/api-ipc-spec.js b/spec/api-ipc-spec.js index 47f2eef21685..fb4278d180fb 100644 --- a/spec/api-ipc-spec.js +++ b/spec/api-ipc-spec.js @@ -175,8 +175,14 @@ describe('ipc module', function () { it('can change its properties', function () { var property = remote.require(path.join(fixtures, 'module', 'property.js')) assert.equal(property.property, 1127) + + property.property = null + assert.equal(property.property, null) + property.property = undefined + assert.equal(property.property, undefined) property.property = 1007 assert.equal(property.property, 1007) + assert.equal(property.getFunctionProperty(), 'foo-browser') property.func.property = 'bar' assert.equal(property.getFunctionProperty(), 'bar-browser') @@ -187,6 +193,26 @@ describe('ipc module', function () { property.property = 1127 }) + it('rethrows errors getting/setting properties', function () { + const foo = remote.require(path.join(fixtures, 'module', 'error-properties.js')) + + assert.throws(function () { + foo.bar + }, /getting error/) + + assert.throws(function () { + foo.bar = 'test' + }, /setting error/) + }) + + it('can set a remote property with a remote object', function () { + const foo = remote.require(path.join(fixtures, 'module', 'remote-object-set.js')) + + assert.doesNotThrow(function () { + foo.bar = remote.getCurrentWindow() + }) + }) + it('can construct an object from its member', function () { var call = remote.require(path.join(fixtures, 'module', 'call.js')) var obj = new call.constructor() diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js index 176f1eafae52..9b1f3de36906 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -455,6 +455,57 @@ describe('menu module', function () { }) }) + describe('MenuItem editMenu', function () { + it('includes a default submenu layout when submenu is empty', function () { + var item = new MenuItem({role: 'editMenu'}) + assert.equal(item.label, 'Edit') + assert.equal(item.submenu.items[0].role, 'undo') + assert.equal(item.submenu.items[1].role, 'redo') + assert.equal(item.submenu.items[2].type, 'separator') + assert.equal(item.submenu.items[3].role, 'cut') + assert.equal(item.submenu.items[4].role, 'copy') + assert.equal(item.submenu.items[5].role, 'paste') + + if (process.platform === 'darwin') { + assert.equal(item.submenu.items[6].role, 'pasteandmatchstyle') + assert.equal(item.submenu.items[7].role, 'delete') + assert.equal(item.submenu.items[8].role, 'selectall') + } + + if (process.platform === 'win32') { + assert.equal(item.submenu.items[6].role, 'delete') + assert.equal(item.submenu.items[7].type, 'separator') + assert.equal(item.submenu.items[8].role, 'selectall') + } + }) + + it('overrides default layout when submenu is specified', function () { + var item = new MenuItem({role: 'editMenu', submenu: [{role: 'close'}]}) + assert.equal(item.label, 'Edit') + assert.equal(item.submenu.items[0].role, 'close') + }) + }) + + describe('MenuItem windowMenu', function () { + it('includes a default submenu layout when submenu is empty', function () { + var item = new MenuItem({role: 'windowMenu'}) + assert.equal(item.label, 'Window') + assert.equal(item.submenu.items[0].role, 'minimize') + assert.equal(item.submenu.items[1].role, 'close') + + if (process.platform === 'darwin') { + assert.equal(item.submenu.items[2].type, 'separator') + assert.equal(item.submenu.items[3].role, 'front') + } + }) + + it('overrides default layout when submenu is specified', function () { + var item = new MenuItem({role: 'windowMenu', submenu: [{role: 'copy'}]}) + assert.equal(item.label, 'Window') + assert.equal(item.submenu.items[0].role, 'copy') + }) + }) + describe('MenuItem with custom properties in constructor', function () { it('preserves the custom properties', function () { var template = [{ diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 49fe2edadcce..a5c3df821056 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -26,22 +26,34 @@ const kOneKiloByte = 1024 const kOneMegaByte = kOneKiloByte * kOneKiloByte describe('net module', function () { - describe('HTTP basics', function () { - let server - beforeEach(function (done) { - server = http.createServer() - server.listen(0, '127.0.0.1', function () { - server.url = 'http://127.0.0.1:' + server.address().port - done() + let server + const connections = new Set() + + beforeEach(function (done) { + server = http.createServer() + server.listen(0, '127.0.0.1', function () { + server.url = `http://127.0.0.1:${server.address().port}` + done() + }) + server.on('connection', (connection) => { + connections.add(connection) + connection.once('close', () => { + connections.delete(connection) }) }) + }) - afterEach(function () { - server.close(function () { - }) + afterEach(function (done) { + for (const connection of connections) { + connection.destroy() + } + server.close(function () { server = null + done() }) + }) + describe('HTTP basics', function () { it('should be able to issue a basic GET request', function (done) { const requestUrl = '/requestUrl' server.on('request', function (request, response) { @@ -224,19 +236,7 @@ describe('net module', function () { }) describe('ClientRequest API', function () { - let server - beforeEach(function (done) { - server = http.createServer() - server.listen(0, '127.0.0.1', function () { - server.url = 'http://127.0.0.1:' + server.address().port - done() - }) - }) - afterEach(function () { - server.close(function () { - }) - server = null session.defaultSession.webRequest.onBeforeRequest(null) }) @@ -364,6 +364,49 @@ describe('net module', function () { urlRequest.end() }) + it('should be able to set a non-string object as a header value', function (done) { + const requestUrl = '/requestUrl' + const customHeaderName = 'Some-Integer-Value' + const customHeaderValue = 900 + server.on('request', function (request, response) { + switch (request.url) { + case requestUrl: + assert.equal(request.headers[customHeaderName.toLowerCase()], + customHeaderValue.toString()) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + break + default: + assert.equal(request.url, requestUrl) + } + }) + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${requestUrl}` + }) + urlRequest.on('response', function (response) { + const statusCode = response.statusCode + assert.equal(statusCode, 200) + response.pause() + response.on('end', function () { + done() + }) + response.resume() + }) + urlRequest.setHeader(customHeaderName, customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName), + customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName.toLowerCase()), + customHeaderValue) + urlRequest.write('') + assert.equal(urlRequest.getHeader(customHeaderName), + customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName.toLowerCase()), + customHeaderValue) + urlRequest.end() + }) + it('should not be able to set a custom HTTP request header after first write', function (done) { const requestUrl = '/requestUrl' const customHeaderName = 'Some-Custom-Header-Name' @@ -906,6 +949,217 @@ describe('net module', function () { urlRequest.end() }) + it('should throw if given an invalid redirect mode', function () { + const requestUrl = '/requestUrl' + const options = { + url: `${server.url}${requestUrl}`, + redirect: 'custom' + } + assert.throws(function () { + net.request(options) + }, 'redirect mode should be one of follow, error or manual') + }) + + it('should throw when calling getHeader without a name', function () { + assert.throws(function () { + net.request({url: `${server.url}/requestUrl`}).getHeader() + }, /`name` is required for getHeader\(name\)\./) + + assert.throws(function () { + net.request({url: `${server.url}/requestUrl`}).getHeader(null) + }, /`name` is required for getHeader\(name\)\./) + }) + + it('should throw when calling removeHeader without a name', function () { + assert.throws(function () { + net.request({url: `${server.url}/requestUrl`}).removeHeader() + }, /`name` is required for removeHeader\(name\)\./) + + assert.throws(function () { + net.request({url: `${server.url}/requestUrl`}).removeHeader(null) + }, /`name` is required for removeHeader\(name\)\./) + }) + + it('should follow redirect when no redirect mode is provided', function (done) { + const requestUrl = '/301' + server.on('request', function (request, response) { + switch (request.url) { + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}` + }) + urlRequest.on('response', function (response) { + assert.equal(response.statusCode, 200) + done() + }) + urlRequest.end() + }) + + it('should follow redirect chain when no redirect mode is provided', function (done) { + const requestUrl = '/redirectChain' + server.on('request', function (request, response) { + switch (request.url) { + case '/redirectChain': + response.statusCode = '301' + response.setHeader('Location', '/301') + response.end() + break + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}` + }) + urlRequest.on('response', function (response) { + assert.equal(response.statusCode, 200) + done() + }) + urlRequest.end() + }) + + it('should not follow redirect when mode is error', function (done) { + const requestUrl = '/301' + server.on('request', function (request, response) { + switch (request.url) { + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}`, + redirect: 'error' + }) + urlRequest.on('error', function (error) { + assert.equal(error.message, 'Request cannot follow redirect with the current redirect mode') + }) + urlRequest.on('close', function () { + done() + }) + urlRequest.end() + }) + + it('should allow follow redirect when mode is manual', function (done) { + const requestUrl = '/redirectChain' + let redirectCount = 0 + server.on('request', function (request, response) { + switch (request.url) { + case '/redirectChain': + response.statusCode = '301' + response.setHeader('Location', '/301') + response.end() + break + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}`, + redirect: 'manual' + }) + urlRequest.on('response', function (response) { + assert.equal(response.statusCode, 200) + assert.equal(redirectCount, 2) + done() + }) + urlRequest.on('redirect', function (status, method, url) { + if (url === `${server.url}/301` || url === `${server.url}/200`) { + redirectCount += 1 + urlRequest.followRedirect() + } + }) + urlRequest.end() + }) + + it('should allow cancelling redirect when mode is manual', function (done) { + const requestUrl = '/redirectChain' + let redirectCount = 0 + server.on('request', function (request, response) { + switch (request.url) { + case '/redirectChain': + response.statusCode = '301' + response.setHeader('Location', '/redirect/1') + response.end() + break + case '/redirect/1': + response.statusCode = '200' + response.setHeader('Location', '/redirect/2') + response.end() + break + case '/redirect/2': + response.statusCode = '200' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + url: `${server.url}${requestUrl}`, + redirect: 'manual' + }) + urlRequest.on('response', function (response) { + assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function (chunk) { + }) + response.on('end', function () { + urlRequest.abort() + }) + response.resume() + }) + urlRequest.on('close', function () { + assert.equal(redirectCount, 1) + done() + }) + urlRequest.on('redirect', function (status, method, url) { + if (url === `${server.url}/redirect/1`) { + redirectCount += 1 + urlRequest.followRedirect() + } + }) + urlRequest.end() + }) + it('should throw if given an invalid session option', function (done) { const requestUrl = '/requestUrl' try { @@ -1109,21 +1363,8 @@ describe('net module', function () { urlRequest.end() }) }) + describe('IncomingMessage API', function () { - let server - beforeEach(function (done) { - server = http.createServer() - server.listen(0, '127.0.0.1', function () { - server.url = 'http://127.0.0.1:' + server.address().port - done() - }) - }) - - afterEach(function () { - server.close() - server = null - }) - it('response object should implement the IncomingMessage API', function (done) { const requestUrl = '/requestUrl' const customHeaderName = 'Some-Custom-Header-Name' @@ -1290,21 +1531,8 @@ describe('net module', function () { urlRequest.end() }) }) + describe('Stability and performance', function (done) { - let server - beforeEach(function (done) { - server = http.createServer() - server.listen(0, '127.0.0.1', function () { - server.url = 'http://127.0.0.1:' + server.address().port - done() - }) - }) - - afterEach(function () { - server.close() - server = null - }) - it('should free unreferenced, never-started request objects without crash', function (done) { const requestUrl = '/requestUrl' ipcRenderer.once('api-net-spec-done', function () { @@ -1320,6 +1548,7 @@ describe('net module', function () { }) `) }) + it('should not collect on-going requests without crash', function (done) { const requestUrl = '/requestUrl' server.on('request', function (request, response) { @@ -1361,6 +1590,7 @@ describe('net module', function () { urlRequest.end() `) }) + it('should collect unreferenced, ended requests without crash', function (done) { const requestUrl = '/requestUrl' server.on('request', function (request, response) { diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index f5a4cbf56c2a..d361c10d947e 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -219,6 +219,21 @@ describe('session module', function () { if (error) return done(error) }) }) + + describe('ses.cookies.flushStore(callback)', function () { + it('flushes the cookies to disk and invokes the callback when done', function (done) { + session.defaultSession.cookies.set({ + url: url, + name: 'foo', + value: 'bar' + }, (error) => { + if (error) return done(error) + session.defaultSession.cookies.flushStore(() => { + done() + }) + }) + }) + }) }) describe('ses.clearStorageData(options)', function () { @@ -226,7 +241,7 @@ describe('session module', function () { it('clears localstorage data', function (done) { ipcMain.on('count', function (event, count) { ipcMain.removeAllListeners('count') - assert(!count) + assert.equal(count, 0) done() }) w.loadURL('file://' + path.join(fixtures, 'api', 'localstorage.html')) diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js index a8e90502f55d..6177e3cb85fa 100644 --- a/spec/api-touch-bar-spec.js +++ b/spec/api-touch-bar-spec.js @@ -1,4 +1,5 @@ const assert = require('assert') +const path = require('path') const {BrowserWindow, TouchBar} = require('electron').remote const {closeWindow} = require('./window-helpers') @@ -6,20 +7,32 @@ const {TouchBarButton, TouchBarColorPicker, TouchBarGroup} = TouchBar const {TouchBarLabel, TouchBarPopover, TouchBarScrubber, TouchBarSegmentedControl, TouchBarSlider, TouchBarSpacer} = TouchBar describe('TouchBar module', function () { - it('throws an error when created without an items array', function () { + it('throws an error when created without an options object', function () { assert.throws(() => { const touchBar = new TouchBar() touchBar.toString() - }, /Must specify items array as first argument/) + }, /Must specify options object as first argument/) }) it('throws an error when created with invalid items', function () { assert.throws(() => { - const touchBar = new TouchBar([1, true, {}, []]) + const touchBar = new TouchBar({items: [1, true, {}, []]}) touchBar.toString() }, /Each item must be an instance of TouchBarItem/) }) + it('throws an error when an invalid escape item is set', function () { + assert.throws(() => { + const touchBar = new TouchBar({items: [], escapeItem: 'esc'}) + touchBar.toString() + }, /Escape item must be an instance of TouchBarItem/) + + assert.throws(() => { + const touchBar = new TouchBar({items: []}) + touchBar.escapeItem = 'esc' + }, /Escape item must be an instance of TouchBarItem/) + }) + describe('BrowserWindow behavior', function () { let window @@ -36,6 +49,11 @@ describe('TouchBar module', function () { const label = new TouchBarLabel({label: 'bar'}) const touchBar = new TouchBar([ new TouchBarButton({label: 'foo', backgroundColor: '#F00', click: () => {}}), + new TouchBarButton({ + icon: path.join(__dirname, 'fixtures', 'assets', 'logo.png'), + iconPosition: 'right', + click: () => {} + }), new TouchBarColorPicker({selectedColor: '#F00', change: () => {}}), new TouchBarGroup({items: new TouchBar([new TouchBarLabel({label: 'hello'})])}), label, @@ -55,10 +73,40 @@ describe('TouchBar module', function () { showArrowButtons: true }) ]) + const escapeButton = new TouchBarButton({ + label: 'foo' + }) window.setTouchBar(touchBar) + touchBar.escapeItem = escapeButton label.label = 'baz' + escapeButton.label = 'hello' window.setTouchBar() window.setTouchBar(new TouchBar([new TouchBarLabel({label: 'two'})])) + touchBar.escapeItem = null + }) + + it('calls the callback on the items when a window interaction event fires', function (done) { + const button = new TouchBarButton({ + label: 'bar', + click: () => { + done() + } + }) + const touchBar = new TouchBar({items: [button]}) + window.setTouchBar(touchBar) + window.emit('-touch-bar-interaction', {}, button.id) + }) + + it('calls the callback on the escape item when a window interaction event fires', function (done) { + const button = new TouchBarButton({ + label: 'bar', + click: () => { + done() + } + }) + const touchBar = new TouchBar({escapeItem: button}) + window.setTouchBar(touchBar) + window.emit('-touch-bar-interaction', {}, button.id) }) }) }) diff --git a/spec/api-web-contents-spec.js b/spec/api-web-contents-spec.js index f081b3900944..7eac9d3e0e96 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -542,4 +542,70 @@ describe('webContents module', function () { }) }) }) + + describe('destroy()', () => { + let server + + before(function (done) { + server = http.createServer((request, response) => { + switch (request.url) { + case '/404': + response.statusCode = '404' + response.end() + break + case '/301': + response.statusCode = '301' + response.setHeader('Location', '/200') + response.end() + break + case '/200': + response.statusCode = '200' + response.end('hello') + break + default: + done('unsupported endpoint') + } + }).listen(0, '127.0.0.1', () => { + server.url = 'http://127.0.0.1:' + server.address().port + done() + }) + }) + + after(function () { + server.close() + server = null + }) + + it('should not crash when invoked synchronously inside navigation observer', (done) => { + const events = [ + { name: 'did-start-loading', url: `${server.url}/200` }, + { name: 'did-get-redirect-request', url: `${server.url}/301` }, + { name: 'did-get-response-details', url: `${server.url}/200` }, + { name: 'dom-ready', url: `${server.url}/200` }, + { name: 'did-stop-loading', url: `${server.url}/200` }, + { name: 'did-finish-load', url: `${server.url}/200` }, + // FIXME: Multiple Emit calls inside an observer assume that object + // will be alive till end of the observer. Synchronous `destroy` api + // violates this contract and crashes. + // { name: 'did-frame-finish-load', url: `${server.url}/200` }, + { name: 'did-fail-load', url: `${server.url}/404` } + ] + const responseEvent = 'webcontents-destroyed' + + function* genNavigationEvent () { + let eventOptions = null + while ((eventOptions = events.shift()) && events.length) { + eventOptions.responseEvent = responseEvent + ipcRenderer.send('test-webcontents-navigation-observer', eventOptions) + yield 1 + } + } + + let gen = genNavigationEvent() + ipcRenderer.on(responseEvent, () => { + if (!gen.next().value) done() + }) + gen.next() + }) + }) }) diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index a54c6744f7c8..1302fbbc4331 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -1,4 +1,5 @@ const assert = require('assert') +const fs = require('fs') const http = require('http') const path = require('path') const ws = require('ws') @@ -13,6 +14,7 @@ const isCI = remote.getGlobal('isCi') describe('chromium feature', function () { var fixtures = path.resolve(__dirname, 'fixtures') var listener = null + let w = null afterEach(function () { if (listener != null) { @@ -21,6 +23,10 @@ describe('chromium feature', function () { listener = null }) + afterEach(function () { + return closeWindow(w).then(function () { w = null }) + }) + describe('heap snapshot', function () { it('does not crash', function () { if (process.env.TRAVIS === 'true') return @@ -44,11 +50,6 @@ describe('chromium feature', function () { describe('document.hidden', function () { var url = 'file://' + fixtures + '/pages/document-hidden.html' - var w = null - - afterEach(function () { - return closeWindow(w).then(function () { w = null }) - }) it('is set correctly when window is not shown', function (done) { w = new BrowserWindow({ @@ -90,13 +91,7 @@ describe('chromium feature', function () { }) describe('navigator.mediaDevices', function () { - if (process.env.TRAVIS === 'true') { - return - } - if (isCI && process.platform === 'linux') { - return - } - if (isCI && process.platform === 'win32') { + if (isCI) { return } @@ -107,7 +102,7 @@ describe('chromium feature', function () { if (labelFound) { done() } else { - done('No device labels found: ' + JSON.stringify(labels)) + done(new Error(`No device labels found: ${JSON.stringify(labels)}`)) } }).catch(done) }) @@ -119,7 +114,7 @@ describe('chromium feature', function () { } const deviceIds = [] const ses = session.fromPartition('persist:media-device-id') - let w = new BrowserWindow({ + w = new BrowserWindow({ show: false, webPreferences: { session: ses @@ -155,11 +150,6 @@ describe('chromium feature', function () { describe('navigator.serviceWorker', function () { var url = 'file://' + fixtures + '/pages/service-worker/index.html' - var w = null - - afterEach(function () { - return closeWindow(w).then(function () { w = null }) - }) it('should register for file scheme', function (done) { w = new BrowserWindow({ @@ -188,12 +178,6 @@ describe('chromium feature', function () { return } - let w = null - - afterEach(() => { - return closeWindow(w).then(function () { w = null }) - }) - it('returns a BrowserWindowProxy object', function () { var b = window.open('about:blank', '', 'show=no') assert.equal(b.closed, false) @@ -246,6 +230,45 @@ describe('chromium feature', function () { b = window.open(windowUrl, '', 'nodeIntegration=no,show=no') }) + it('disables node integration when it is disabled on the parent window for chrome devtools URLs', function (done) { + var b + app.once('web-contents-created', (event, contents) => { + contents.once('did-finish-load', () => { + contents.executeJavaScript('typeof process').then((typeofProcessGlobal) => { + assert.equal(typeofProcessGlobal, 'undefined') + b.close() + done() + }).catch(done) + }) + }) + b = window.open('chrome-devtools://devtools/bundled/inspector.html', '', 'nodeIntegration=no,show=no') + }) + + it('disables JavaScript when it is disabled on the parent window', function (done) { + var b + app.once('web-contents-created', (event, contents) => { + contents.once('did-finish-load', () => { + app.once('browser-window-created', (event, window) => { + const preferences = window.webContents.getWebPreferences() + assert.equal(preferences.javascript, false) + window.destroy() + b.close() + done() + }) + // Click link on page + contents.sendInputEvent({type: 'mouseDown', clickCount: 1, x: 1, y: 1}) + contents.sendInputEvent({type: 'mouseUp', clickCount: 1, x: 1, y: 1}) + }) + }) + + var windowUrl = require('url').format({ + pathname: `${fixtures}/pages/window-no-javascript.html`, + protocol: 'file', + slashes: true + }) + b = window.open(windowUrl, '', 'javascript=no,show=no') + }) + it('does not override child options', function (done) { var b, size size = { @@ -339,15 +362,48 @@ describe('chromium feature', function () { }) b = window.open() }) + + it('throws an exception when the arguments cannot be converted to strings', function () { + assert.throws(function () { + window.open('', {toString: null}) + }, /Cannot convert object to primitive value/) + + assert.throws(function () { + window.open('', '', {toString: 3}) + }, /Cannot convert object to primitive value/) + }) + + it('sets the window title to the specified frameName', function (done) { + let b + app.once('browser-window-created', (event, createdWindow) => { + assert.equal(createdWindow.getTitle(), 'hello') + b.close() + done() + }) + b = window.open('', 'hello') + }) + + it('does not throw an exception when the frameName is a built-in object property', function (done) { + let b + app.once('browser-window-created', (event, createdWindow) => { + assert.equal(createdWindow.getTitle(), '__proto__') + b.close() + done() + }) + b = window.open('', '__proto__') + }) + + it('does not throw an exception when the features include webPreferences', function () { + let b + assert.doesNotThrow(function () { + b = window.open('', '', 'webPreferences=') + }) + b.close() + }) }) describe('window.opener', function () { let url = 'file://' + fixtures + '/pages/window-opener.html' - let w = null - - afterEach(function () { - return closeWindow(w).then(function () { w = null }) - }) it('is null for main window', function (done) { w = new BrowserWindow({ @@ -521,6 +577,14 @@ describe('chromium feature', function () { }) b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no') }) + + it('throws an exception when the targetOrigin cannot be converted to a string', function () { + var b = window.open('') + assert.throws(function () { + b.postMessage('test', {toString: null}) + }, /Cannot convert object to primitive value/) + b.close() + }) }) describe('window.opener.postMessage', function () { @@ -555,6 +619,39 @@ describe('chromium feature', function () { }) document.body.appendChild(webview) }) + + describe('targetOrigin argument', function () { + let serverURL + let server + + beforeEach(function (done) { + server = http.createServer(function (req, res) { + res.writeHead(200) + const filePath = path.join(fixtures, 'pages', 'window-opener-targetOrigin.html') + res.end(fs.readFileSync(filePath, 'utf8')) + }) + server.listen(0, '127.0.0.1', function () { + serverURL = `http://127.0.0.1:${server.address().port}` + done() + }) + }) + + afterEach(function () { + server.close() + }) + + it('delivers messages that match the origin', function (done) { + let b + listener = function (event) { + window.removeEventListener('message', listener) + b.close() + assert.equal(event.data, 'deliver') + done() + } + window.addEventListener('message', listener) + b = window.open(serverURL, '', 'show=no') + }) + }) }) describe('creating a Uint8Array under browser side', function () { @@ -849,7 +946,6 @@ describe('chromium feature', function () { }) describe('PDF Viewer', function () { - let w = null const pdfSource = url.format({ pathname: path.join(fixtures, 'assets', 'cat.pdf').replace(/\\/g, '/'), protocol: 'file', @@ -865,10 +961,6 @@ describe('chromium feature', function () { }) }) - afterEach(function () { - return closeWindow(w).then(function () { w = null }) - }) - it('opens when loading a pdf resource as top level navigation', function (done) { ipcMain.once('pdf-loaded', function (event, success) { if (success) done() @@ -907,4 +999,36 @@ describe('chromium feature', function () { }) }) }) + + describe('window.alert(message, title)', function () { + it('throws an exception when the arguments cannot be converted to strings', function () { + assert.throws(function () { + window.alert({toString: null}) + }, /Cannot convert object to primitive value/) + + assert.throws(function () { + window.alert('message', {toString: 3}) + }, /Cannot convert object to primitive value/) + }) + }) + + describe('window.confirm(message, title)', function () { + it('throws an exception when the arguments cannot be converted to strings', function () { + assert.throws(function () { + window.confirm({toString: null}, 'title') + }, /Cannot convert object to primitive value/) + + assert.throws(function () { + window.confirm('message', {toString: 3}) + }, /Cannot convert object to primitive value/) + }) + }) + + describe('window.history.go(offset)', function () { + it('throws an exception when the argumnet cannot be converted to a string', function () { + assert.throws(function () { + window.history.go({toString: null}) + }, /Cannot convert object to primitive value/) + }) + }) }) diff --git a/spec/fixtures/api/crash-restart.html b/spec/fixtures/api/crash-restart.html index 2f55c539bbd8..22f3b45b5c82 100644 --- a/spec/fixtures/api/crash-restart.html +++ b/spec/fixtures/api/crash-restart.html @@ -3,7 +3,7 @@ diff --git a/spec/fixtures/api/exit-closes-all-windows-app/main.js b/spec/fixtures/api/exit-closes-all-windows-app/main.js new file mode 100644 index 000000000000..c97d8d1f195b --- /dev/null +++ b/spec/fixtures/api/exit-closes-all-windows-app/main.js @@ -0,0 +1,19 @@ +const {app, BrowserWindow} = require('electron') + +const windows = [] + +function createWindow (id) { + const window = new BrowserWindow({show: false}) + window.loadURL(`data:,window${id}`) + windows.push(window) +} + +app.once('ready', () => { + for (let i = 1; i <= 5; i++) { + createWindow(i) + } + + setImmediate(function () { + app.exit(123) + }) +}) diff --git a/spec/fixtures/api/exit-closes-all-windows-app/package.json b/spec/fixtures/api/exit-closes-all-windows-app/package.json new file mode 100644 index 000000000000..ae52532315a3 --- /dev/null +++ b/spec/fixtures/api/exit-closes-all-windows-app/package.json @@ -0,0 +1,4 @@ +{ + "name": "electron-exit-closes-all-windows", + "main": "main.js" +} diff --git a/spec/fixtures/module/error-properties.js b/spec/fixtures/module/error-properties.js new file mode 100644 index 000000000000..c3a1e3b3a7f6 --- /dev/null +++ b/spec/fixtures/module/error-properties.js @@ -0,0 +1,11 @@ +class Foo { + set bar (value) { + throw new Error('setting error') + } + + get bar () { + throw new Error('getting error') + } +} + +module.exports = new Foo() diff --git a/spec/fixtures/module/preload-sandbox.js b/spec/fixtures/module/preload-sandbox.js index 39a8704e1329..15d6e06a862c 100644 --- a/spec/fixtures/module/preload-sandbox.js +++ b/spec/fixtures/module/preload-sandbox.js @@ -1,6 +1,8 @@ (function () { + const {setImmediate} = require('timers') const {ipcRenderer} = require('electron') window.ipcRenderer = ipcRenderer + window.setImmediate = setImmediate if (location.protocol === 'file:') { window.test = 'preload' window.require = require diff --git a/spec/fixtures/module/remote-object-set.js b/spec/fixtures/module/remote-object-set.js new file mode 100644 index 000000000000..5fefbf5cf36a --- /dev/null +++ b/spec/fixtures/module/remote-object-set.js @@ -0,0 +1,11 @@ +const {BrowserWindow} = require('electron') + +class Foo { + set bar (value) { + if (!(value instanceof BrowserWindow)) { + throw new Error('setting error') + } + } +} + +module.exports = new Foo() diff --git a/spec/fixtures/no-proprietary-codecs.js b/spec/fixtures/no-proprietary-codecs.js new file mode 100644 index 000000000000..23a7d815a924 --- /dev/null +++ b/spec/fixtures/no-proprietary-codecs.js @@ -0,0 +1,47 @@ +// Verifies that Electron cannot play a video that uses proprietary codecs +// +// This application should be run with the ffmpeg that does not include +// proprietary codecs to ensure Electron uses it instead of the version +// that does include proprietary codecs. + +const {app, BrowserWindow, ipcMain} = require('electron') +const path = require('path') +const url = require('url') + +const MEDIA_ERR_SRC_NOT_SUPPORTED = 4 +const FIVE_MINUTES = 5 * 60 * 1000 + +let window + +app.once('ready', () => { + window = new BrowserWindow({ + show: false + }) + + window.loadURL(url.format({ + protocol: 'file', + slashed: true, + pathname: path.resolve(__dirname, 'asar', 'video.asar', 'index.html') + })) + + ipcMain.on('asar-video', (event, message, error) => { + if (message === 'ended') { + console.log('Video played, proprietary codecs are included') + app.exit(1) + return + } + + if (message === 'error' && error === MEDIA_ERR_SRC_NOT_SUPPORTED) { + app.exit(0) + return + } + + console.log(`Unexpected response from page: ${message} ${error}`) + app.exit(1) + }) + + setTimeout(() => { + console.log('No IPC message after 5 minutes') + app.exit(1) + }, FIVE_MINUTES) +}) diff --git a/spec/fixtures/pages/window-no-javascript.html b/spec/fixtures/pages/window-no-javascript.html new file mode 100644 index 000000000000..9c38c9e0bba4 --- /dev/null +++ b/spec/fixtures/pages/window-no-javascript.html @@ -0,0 +1,12 @@ + + + + +CLICK + + diff --git a/spec/fixtures/pages/window-opener-targetOrigin.html b/spec/fixtures/pages/window-opener-targetOrigin.html new file mode 100644 index 000000000000..aa7e48ea0e86 --- /dev/null +++ b/spec/fixtures/pages/window-opener-targetOrigin.html @@ -0,0 +1,24 @@ + + + + + diff --git a/spec/modules-spec.js b/spec/modules-spec.js index 5f8271752711..ba87ed3466a8 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -1,60 +1,63 @@ const assert = require('assert') const Module = require('module') const path = require('path') -const temp = require('temp') +const {remote} = require('electron') +const {BrowserWindow} = remote +const {closeWindow} = require('./window-helpers') -describe('third-party module', function () { +describe('modules support', function () { var fixtures = path.join(__dirname, 'fixtures') - temp.track() - if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) { - describe('runas', function () { - it('can be required in renderer', function () { - require('runas') + describe('third-party module', function () { + if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) { + describe('runas', function () { + it('can be required in renderer', function () { + require('runas') + }) + + it('can be required in node binary', function (done) { + var runas = path.join(fixtures, 'module', 'runas.js') + var child = require('child_process').fork(runas) + child.on('message', function (msg) { + assert.equal(msg, 'ok') + done() + }) + }) }) - it('can be required in node binary', function (done) { - var runas = path.join(fixtures, 'module', 'runas.js') - var child = require('child_process').fork(runas) - child.on('message', function (msg) { - assert.equal(msg, 'ok') - done() + describe('ffi', function () { + if (process.platform === 'win32') return + + it('does not crash', function () { + var ffi = require('ffi') + var libm = ffi.Library('libm', { + ceil: ['double', ['double']] + }) + assert.equal(libm.ceil(1.5), 2) + }) + }) + } + + describe('q', function () { + var Q = require('q') + describe('Q.when', function () { + it('emits the fullfil callback', function (done) { + Q(true).then(function (val) { + assert.equal(val, true) + done() + }) }) }) }) - describe('ffi', function () { - if (process.platform === 'win32') return - - it('does not crash', function () { - var ffi = require('ffi') - var libm = ffi.Library('libm', { - ceil: ['double', ['double']] + describe('coffee-script', function () { + it('can be registered and used to require .coffee files', function () { + assert.doesNotThrow(function () { + require('coffee-script').register() }) - assert.equal(libm.ceil(1.5), 2) + assert.strictEqual(require('./fixtures/module/test.coffee'), true) }) }) - } - - describe('q', function () { - var Q = require('q') - describe('Q.when', function () { - it('emits the fullfil callback', function (done) { - Q(true).then(function (val) { - assert.equal(val, true) - done() - }) - }) - }) - }) - - describe('coffee-script', function () { - it('can be registered and used to require .coffee files', function () { - assert.doesNotThrow(function () { - require('coffee-script').register() - }) - assert.strictEqual(require('./fixtures/module/test.coffee'), true) - }) }) describe('global variables', function () { @@ -76,56 +79,79 @@ describe('third-party module', function () { }) }) }) -}) -describe('Module._nodeModulePaths', function () { - describe('when the path is inside the resources path', function () { - it('does not include paths outside of the resources path', function () { - let modulePath = process.resourcesPath - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'node_modules') - ]) + describe('Module._nodeModulePaths', function () { + describe('when the path is inside the resources path', function () { + it('does not include paths outside of the resources path', function () { + let modulePath = process.resourcesPath + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'node_modules') + ]) - modulePath = process.resourcesPath + '-foo' - let nodeModulePaths = Module._nodeModulePaths(modulePath) - assert(nodeModulePaths.includes(path.join(modulePath, 'node_modules'))) - assert(nodeModulePaths.includes(path.join(modulePath, '..', 'node_modules'))) + modulePath = process.resourcesPath + '-foo' + let nodeModulePaths = Module._nodeModulePaths(modulePath) + assert(nodeModulePaths.includes(path.join(modulePath, 'node_modules'))) + assert(nodeModulePaths.includes(path.join(modulePath, '..', 'node_modules'))) - modulePath = path.join(process.resourcesPath, 'foo') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'foo', 'node_modules'), - path.join(process.resourcesPath, 'node_modules') - ]) + modulePath = path.join(process.resourcesPath, 'foo') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'foo', 'node_modules'), + path.join(process.resourcesPath, 'node_modules') + ]) - modulePath = path.join(process.resourcesPath, 'node_modules', 'foo') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), - path.join(process.resourcesPath, 'node_modules') - ]) + modulePath = path.join(process.resourcesPath, 'node_modules', 'foo') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), + path.join(process.resourcesPath, 'node_modules') + ]) - modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'bar') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'node_modules', 'foo', 'bar', 'node_modules'), - path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), - path.join(process.resourcesPath, 'node_modules') - ]) + modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'bar') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'node_modules', 'foo', 'bar', 'node_modules'), + path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), + path.join(process.resourcesPath, 'node_modules') + ]) - modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar', 'node_modules'), - path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), - path.join(process.resourcesPath, 'node_modules') - ]) + modulePath = path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules', 'bar', 'node_modules'), + path.join(process.resourcesPath, 'node_modules', 'foo', 'node_modules'), + path.join(process.resourcesPath, 'node_modules') + ]) + }) + }) + + describe('when the path is outside the resources path', function () { + it('includes paths outside of the resources path', function () { + let modulePath = path.resolve('/foo') + assert.deepEqual(Module._nodeModulePaths(modulePath), [ + path.join(modulePath, 'node_modules'), + path.resolve('/node_modules') + ]) + }) }) }) - describe('when the path is outside the resources path', function () { - it('includes paths outside of the resources path', function () { - let modulePath = path.resolve('/foo') - assert.deepEqual(Module._nodeModulePaths(modulePath), [ - path.join(modulePath, 'node_modules'), - path.resolve('/node_modules') - ]) + describe('require', () => { + describe('when loaded URL is not file: protocol', () => { + let w + + beforeEach(() => { + w = new BrowserWindow({ + show: false + }) + }) + + afterEach(async () => { + await closeWindow(w) + w = null + }) + + it('searches for module under app directory', async () => { + w.loadURL('about:blank') + const result = await w.webContents.executeJavaScript('typeof require("q").when') + assert.equal(result, 'function') + }) }) }) }) diff --git a/spec/node-spec.js b/spec/node-spec.js index 98db1efeb08e..678b8e9490fd 100644 --- a/spec/node-spec.js +++ b/spec/node-spec.js @@ -85,12 +85,22 @@ describe('node feature', function () { child.stdout.on('data', (chunk) => { data += String(chunk) }) - child.on('exit', (code) => { + child.on('close', (code) => { assert.equal(code, 0) assert.equal(data, 'pipes stdio') done() }) }) + + it('works when sending a message to a process forked with the --eval argument', function (done) { + const source = "process.on('message', (message) => { process.send(message) })" + const forked = ChildProcess.fork('--eval', [source]) + forked.once('message', (message) => { + assert.equal(message, 'hello') + done() + }) + forked.send('hello') + }) }) describe('child_process.spawn', function () { diff --git a/spec/static/main.js b/spec/static/main.js index 1ac489ecae8b..ba31b61ae07d 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -2,20 +2,15 @@ process.throwDeprecation = true const electron = require('electron') -const app = electron.app -const crashReporter = electron.crashReporter -const ipcMain = electron.ipcMain -const dialog = electron.dialog -const BrowserWindow = electron.BrowserWindow -const protocol = electron.protocol -const webContents = electron.webContents -const v8 = require('v8') +const {app, BrowserWindow, crashReporter, dialog, ipcMain, protocol, webContents} = electron + +const {Coverage} = require('electabul') -const Coverage = require('electabul').Coverage const fs = require('fs') const path = require('path') const url = require('url') const util = require('util') +const v8 = require('v8') var argv = require('yargs') .boolean('ci') @@ -24,7 +19,10 @@ var argv = require('yargs') .argv var window = null -process.port = 0 // will be used by crash-reporter spec. + + // will be used by crash-reporter spec. +process.port = 0 +process.crashServicePid = 0 v8.setFlagsFromString('--expose_gc') app.commandLine.appendSwitch('js-flags', '--expose_gc') @@ -100,6 +98,12 @@ app.on('window-all-closed', function () { app.quit() }) +app.on('web-contents-created', (event, contents) => { + contents.on('crashed', (event, killed) => { + console.log(`webContents ${contents.id} crashed: ${contents.getURL()} (killed=${killed})`) + }) +}) + app.on('ready', function () { // Test if using protocol module would crash. electron.protocol.registerStringProtocol('test-if-crashes', function () {}) @@ -329,6 +333,32 @@ ipcMain.on('navigate-with-pending-entry', (event, id) => { }) }) +ipcMain.on('crash-service-pid', (event, pid) => { + process.crashServicePid = pid + event.returnValue = null +}) + +ipcMain.on('test-webcontents-navigation-observer', (event, options) => { + let contents = null + let destroy = () => {} + if (options.id) { + const w = BrowserWindow.fromId(options.id) + contents = w.webContents + destroy = () => w.close() + } else { + contents = webContents.create() + destroy = () => contents.destroy() + } + + contents.once(options.name, () => destroy()) + + contents.once('destroyed', () => { + event.sender.send(options.responseEvent) + }) + + contents.loadURL(options.url) +}) + // Suspend listeners until the next event and then restore them const suspendListeners = (emitter, eventName, callback) => { const listeners = emitter.listeners(eventName) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 2f5cb8068c4d..4ea9369d666f 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -121,6 +121,9 @@ describe(' tag', function () { }) it('loads node symbols after POST navigation when set', function (done) { + // FIXME Figure out why this is timing out on AppVeyor + if (process.env.APPVEYOR === 'True') return done() + webview.addEventListener('console-message', function (e) { assert.equal(e.message, 'function object object') done() diff --git a/vendor/brightray b/vendor/brightray index d0144ba2c902..909c49265493 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit d0144ba2c9024d65cdf260abf5c03c742afff3fd +Subproject commit 909c49265493bd095c27cefd999567be2107899a diff --git a/vendor/node b/vendor/node index a6663598aa78..3fe90cfcf54d 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit a6663598aa78832e7955cb93c51a098eac787abb +Subproject commit 3fe90cfcf54dd946980e59daf550a7cdb2317c8f