diff --git a/.gitmodules b/.gitmodules index 5bc253bad5..0cabff390f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "vendor/boto"] path = vendor/boto url = https://github.com/boto/boto.git +[submodule "vendor/pdf_viewer"] + path = vendor/pdf_viewer + url = https://github.com/electron/pdf-viewer.git diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 7552ddadc2..82803b0936 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -20,4 +20,16 @@ Thanks for opening an issue! A few things to keep in mind: ### How to reproduce - + diff --git a/README.md b/README.md index 7d6bfe7433..f32fbfab92 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![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/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/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: 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) +: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 [Chromium](http://www.chromium.org) and is used by the [Atom -editor](https://github.com/atom/atom) and many other [apps](http://electron.atom.io/apps). +editor](https://github.com/atom/atom) and many other [apps](https://electron.atom.io/apps). Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important announcements. @@ -54,6 +54,7 @@ contains documents describing how to build and contribute to Electron. - [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) @@ -71,10 +72,11 @@ locations: forums - `#atom-shell` channel on Freenode - [`Atom`](http://atom-slack.herokuapp.com/) channel on Slack +- [`electron-ru`](https://telegram.me/electron_ru) *(Russian)* - [`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`](http://electron-tr.herokuapp.com) *(Turkish)* - [`electron-id`](https://electron-id.slack.com) *(Indonesia)* Check out [awesome-electron](https://github.com/sindresorhus/awesome-electron) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..ff2f101842 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Reporting Security Issues + +The Electron team and community take security bugs in Electron seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. + +To report a security issue, email [electron@github.com](mailto:electron@github.com) and include the word "SECURITY" in the subject line. + +The Electron team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. + +Report security bugs in third-party modules to the person or team maintaining the module. You can also report a vulnerability through the [Node Security Project](https://nodesecurity.io/report). diff --git a/atom/app/atom_content_client.cc b/atom/app/atom_content_client.cc index 760a42732b..8e2a6c5730 100644 --- a/atom/app/atom_content_client.cc +++ b/atom/app/atom_content_client.cc @@ -7,6 +7,7 @@ #include #include +#include "atom/common/atom_constants.h" #include "atom/common/atom_version.h" #include "atom/common/chrome_version.h" #include "atom/common/options_switches.h" @@ -18,6 +19,7 @@ #include "content/public/common/content_constants.h" #include "content/public/common/pepper_plugin_info.h" #include "content/public/common/user_agent.h" +#include "pdf/pdf.h" #include "ppapi/shared_impl/ppapi_permissions.h" #include "third_party/widevine/cdm/stub/widevine_cdm_version.h" #include "ui/base/l10n/l10n_util.h" @@ -42,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()) @@ -108,6 +110,25 @@ content::PepperPluginInfo CreateWidevineCdmInfo(const base::FilePath& path, } #endif +void ComputeBuiltInPlugins(std::vector* plugins) { + content::PepperPluginInfo pdf_info; + pdf_info.is_internal = true; + pdf_info.is_out_of_process = true; + pdf_info.name = "Chromium PDF Viewer"; + pdf_info.description = "Portable Document Format"; + pdf_info.path = base::FilePath::FromUTF8Unsafe(kPdfPluginPath); + content::WebPluginMimeType pdf_mime_type(kPdfPluginMimeType, "pdf", + "Portable Document Format"); + pdf_info.mime_types.push_back(pdf_mime_type); + pdf_info.internal_entry_points.get_interface = chrome_pdf::PPP_GetInterface; + pdf_info.internal_entry_points.initialize_module = + chrome_pdf::PPP_InitializeModule; + pdf_info.internal_entry_points.shutdown_module = + chrome_pdf::PPP_ShutdownModule; + pdf_info.permissions = ppapi::PERMISSION_PRIVATE | ppapi::PERMISSION_DEV; + plugins->push_back(pdf_info); +} + void ConvertStringWithSeparatorToVector(std::vector* vec, const char* separator, const char* cmd_switch) { @@ -190,6 +211,7 @@ void AtomContentClient::AddPepperPlugins( #if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) AddWidevineCdmFromCommandLine(plugins); #endif + ComputeBuiltInPlugins(plugins); } void AtomContentClient::AddServiceWorkerSchemes( diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index e4015e57be..1490b89013 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -102,6 +102,9 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { #if defined(OS_WIN) // Ignore invalid parameter errors. _set_invalid_parameter_handler(InvalidParameterHandler); + // Disable the ActiveVerifier, which is used by Chrome to track possible + // bugs, but no use in Electron. + base::win::DisableHandleVerifier(); #endif return brightray::MainDelegate::BasicStartupComplete(exit_code); diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index caaee9d25c..1024bb0c6c 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -30,14 +30,16 @@ #include "base/path_service.h" #include "base/strings/string_util.h" #include "brightray/browser/brightray_paths.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/icon_manager.h" #include "chrome/common/chrome_paths.h" #include "content/public/browser/browser_accessibility_state.h" +#include "content/public/browser/browser_child_process_host.h" #include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/gpu_data_manager.h" #include "content/public/browser/render_frame_host.h" #include "content/public/common/content_switches.h" #include "media/audio/audio_manager.h" -#include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/ssl/ssl_cert_request_info.h" #include "ui/base/l10n/l10n_util.h" @@ -335,6 +337,26 @@ namespace api { namespace { +class AppIdProcessIterator : public base::ProcessIterator { + public: + AppIdProcessIterator() : base::ProcessIterator(nullptr) {} + + protected: + bool IncludeEntry() override { + return (entry().parent_pid() == base::GetCurrentProcId() || + entry().pid() == base::GetCurrentProcId()); + } +}; + +IconLoader::IconSize GetIconSizeByString(const std::string& size) { + if (size == "small") { + return IconLoader::IconSize::SMALL; + } else if (size == "large") { + return IconLoader::IconSize::LARGE; + } + return IconLoader::IconSize::NORMAL; +} + // Return the path constant from string. int GetPathConstant(const std::string& name) { if (name == "appData") @@ -416,7 +438,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()); } @@ -462,6 +484,21 @@ int ImportIntoCertStore( } #endif +void OnIconDataAvailable(v8::Isolate* isolate, + const App::FileIconCallback& callback, + gfx::Image* icon) { + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + + if (icon && !icon->IsEmpty()) { + callback.Run(v8::Null(isolate), *icon); + } else { + v8::Local error_message = + v8::String::NewFromUtf8(isolate, "Failed to get file icon."); + callback.Run(v8::Exception::Error(error_message), gfx::Image()); + } +} + } // namespace App::App(v8::Isolate* isolate) { @@ -494,7 +531,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(); } @@ -629,6 +666,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; @@ -669,7 +714,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; @@ -690,7 +735,7 @@ bool App::MakeSingleInstance( } void App::ReleaseSingleInstance() { - if (process_singleton_.get()) { + if (process_singleton_) { process_singleton_->Cleanup(); process_singleton_.reset(); } @@ -841,6 +886,84 @@ JumpListResult App::SetJumpList(v8::Local val, } #endif // defined(OS_WIN) +void App::GetFileIcon(const base::FilePath& path, + mate::Arguments* args) { + mate::Dictionary options; + IconLoader::IconSize icon_size; + FileIconCallback callback; + + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + + base::FilePath normalized_path = path.NormalizePathSeparators(); + + if (!args->GetNext(&options)) { + icon_size = IconLoader::IconSize::NORMAL; + } else { + std::string icon_size_string; + options.Get("size", &icon_size_string); + icon_size = GetIconSizeByString(icon_size_string); + } + + if (!args->GetNext(&callback)) { + args->ThrowError("Missing required callback function"); + return; + } + + auto icon_manager = g_browser_process->GetIconManager(); + gfx::Image* icon = + icon_manager->LookupIconFromFilepath(normalized_path, icon_size); + if (icon) { + callback.Run(v8::Null(isolate()), *icon); + } else { + icon_manager->LoadIcon( + normalized_path, icon_size, + base::Bind(&OnIconDataAvailable, isolate(), callback), + &cancelable_task_tracker_); + } +} + +std::vector App::GetAppMemoryInfo(v8::Isolate* isolate) { + AppIdProcessIterator process_iterator; + auto process_entry = process_iterator.NextProcessEntry(); + std::vector result; + + while (process_entry != nullptr) { + int64_t pid = process_entry->pid(); + auto process = base::Process::OpenWithExtraPrivileges(pid); + +#if defined(OS_MACOSX) + std::unique_ptr metrics( + base::ProcessMetrics::CreateProcessMetrics( + process.Handle(), content::BrowserChildProcessHost::GetPortProvider())); +#else + std::unique_ptr metrics( + base::ProcessMetrics::CreateProcessMetrics(process.Handle())); +#endif + + mate::Dictionary pid_dict = mate::Dictionary::CreateEmpty(isolate); + mate::Dictionary memory_dict = mate::Dictionary::CreateEmpty(isolate); + + memory_dict.Set("workingSetSize", + static_cast(metrics->GetWorkingSetSize() >> 10)); + memory_dict.Set("peakWorkingSetSize", + static_cast(metrics->GetPeakWorkingSetSize() >> 10)); + + size_t private_bytes, shared_bytes; + if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes)) { + memory_dict.Set("privateBytes", static_cast(private_bytes >> 10)); + memory_dict.Set("sharedBytes", static_cast(shared_bytes >> 10)); + } + + pid_dict.Set("memory", memory_dict); + pid_dict.Set("pid", pid); + result.push_back(pid_dict); + process_entry = process_iterator.NextProcessEntry(); + } + + return result; +} + // static mate::Handle App::Create(v8::Isolate* isolate) { return mate::CreateHandle(isolate, new App(isolate)); @@ -896,6 +1019,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) @@ -909,7 +1034,9 @@ void App::BuildPrototype( .SetMethod("isAccessibilitySupportEnabled", &App::IsAccessibilitySupportEnabled) .SetMethod("disableHardwareAcceleration", - &App::DisableHardwareAcceleration); + &App::DisableHardwareAcceleration) + .SetMethod("getFileIcon", &App::GetFileIcon) + .SetMethod("getAppMemoryInfo", &App::GetAppMemoryInfo); } } // namespace api diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index c7e62927c5..9543e4b74b 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -13,8 +13,12 @@ #include "atom/browser/browser.h" #include "atom/browser/browser_observer.h" #include "atom/common/native_mate_converters/callback.h" +#include "base/process/process_iterator.h" +#include "base/task/cancelable_task_tracker.h" +#include "chrome/browser/icon_manager.h" #include "chrome/browser/process_singleton.h" #include "content/public/browser/gpu_data_manager_observer.h" +#include "native_mate/dictionary.h" #include "native_mate/handle.h" #include "net/base/completion_callback.h" @@ -43,6 +47,9 @@ class App : public AtomBrowserClient::Delegate, public BrowserObserver, public content::GpuDataManagerObserver { public: + using FileIconCallback = base::Callback, + const gfx::Image&)>; + static mate::Handle Create(v8::Isolate* isolate); static void BuildPrototype(v8::Isolate* isolate, @@ -65,6 +72,8 @@ class App : public AtomBrowserClient::Delegate, std::unique_ptr model); #endif + base::FilePath GetAppPath() const; + protected: explicit App(v8::Isolate* isolate); ~App() override; @@ -110,6 +119,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, @@ -129,6 +140,10 @@ class App : public AtomBrowserClient::Delegate, void ImportCertificate(const base::DictionaryValue& options, const net::CompletionCallback& callback); #endif + void GetFileIcon(const base::FilePath& path, + mate::Arguments* args); + + std::vector GetAppMemoryInfo(v8::Isolate* isolate); #if defined(OS_WIN) // Get the current Jump List settings. @@ -144,6 +159,11 @@ class App : public AtomBrowserClient::Delegate, std::unique_ptr certificate_manager_model_; #endif + // 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 67079abf28..c23e488f64 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 0000000000..d37d2df41c --- /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 0000000000..7531cfcc4a --- /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 ddb6910b50..2ce9e12f6c 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 3a7a98fbaf..d20dab8394 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 90999a4f60..0758231852 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 5d853e2d59..efa8750eb9 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" @@ -35,6 +37,27 @@ struct Converter { } }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + file_dialog::DialogSettings* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + dict.Get("window", &(out->parent_window)); + dict.Get("title", &(out->title)); + dict.Get("message", &(out->message)); + dict.Get("buttonLabel", &(out->button_label)); + dict.Get("nameFieldLabel", &(out->name_field_label)); + dict.Get("defaultPath", &(out->default_path)); + dict.Get("filters", &(out->filters)); + dict.Get("properties", &(out->properties)); + dict.Get("showsTagField", &(out->shows_tag_field)); + return true; + } +}; + } // namespace mate namespace { @@ -47,6 +70,8 @@ void ShowMessageBox(int type, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, atom::NativeWindow* window, mate::Arguments* args) { @@ -55,56 +80,44 @@ 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, 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); } } -void ShowOpenDialog(const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const file_dialog::Filters& filters, - int properties, - atom::NativeWindow* window, +void ShowOpenDialog(const file_dialog::DialogSettings& settings, mate::Arguments* args) { v8::Local peek = args->PeekNext(); file_dialog::OpenDialogCallback callback; if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - file_dialog::ShowOpenDialog(window, title, button_label, default_path, - filters, properties, callback); + file_dialog::ShowOpenDialog(settings, callback); } else { std::vector paths; - if (file_dialog::ShowOpenDialog(window, title, button_label, default_path, - filters, properties, &paths)) + if (file_dialog::ShowOpenDialog(settings, &paths)) args->Return(paths); } } -void ShowSaveDialog(const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const file_dialog::Filters& filters, - atom::NativeWindow* window, +void ShowSaveDialog(const file_dialog::DialogSettings& settings, mate::Arguments* args) { v8::Local peek = args->PeekNext(); file_dialog::SaveDialogCallback callback; if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - file_dialog::ShowSaveDialog(window, title, button_label, default_path, - filters, callback); + file_dialog::ShowSaveDialog(settings, callback); } else { base::FilePath path; - if (file_dialog::ShowSaveDialog(window, title, button_label, default_path, - filters, &path)) + if (file_dialog::ShowSaveDialog(settings, &path)) args->Return(path); } } @@ -116,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) || defined(OS_WIN) + dict.SetMethod("showCertificateTrustDialog", + &certificate_trust::ShowCertificateTrust); +#endif } } // namespace diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc index ccb99b6f60..3e5932cad0 100644 --- a/atom/browser/api/atom_api_download_item.cc +++ b/atom/browser/api/atom_api_download_item.cc @@ -78,7 +78,6 @@ DownloadItem::~DownloadItem() { void DownloadItem::OnDownloadUpdated(content::DownloadItem* item) { if (download_item_->IsDone()) { Emit("done", item->GetState()); - // Destroy the item once item is downloaded. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, GetDestroyClosure()); @@ -111,7 +110,6 @@ bool DownloadItem::CanResume() const { void DownloadItem::Cancel() { download_item_->Cancel(true); - download_item_->Remove(); } int64_t DownloadItem::GetReceivedBytes() const { diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 627ce601f5..da208b3659 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -176,7 +176,8 @@ void Menu::BuildPrototype(v8::Isolate* isolate, .SetMethod("isItemCheckedAt", &Menu::IsItemCheckedAt) .SetMethod("isEnabledAt", &Menu::IsEnabledAt) .SetMethod("isVisibleAt", &Menu::IsVisibleAt) - .SetMethod("popupAt", &Menu::PopupAt); + .SetMethod("popupAt", &Menu::PopupAt) + .SetMethod("closePopupAt", &Menu::ClosePopupAt); } } // namespace api diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 97b6360049..df50640b85 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -53,9 +53,9 @@ class Menu : public mate::TrackableObject, void ExecuteCommand(int command_id, int event_flags) override; void MenuWillShow(ui::SimpleMenuModel* source) override; - virtual void PopupAt(Window* window, - int x = -1, int y = -1, - int positioning_item = 0) = 0; + virtual void PopupAt( + Window* window, int x, int y, int positioning_item, bool async) = 0; + virtual void ClosePopupAt(int32_t window_id) = 0; std::unique_ptr model_; Menu* parent_; diff --git a/atom/browser/api/atom_api_menu_mac.h b/atom/browser/api/atom_api_menu_mac.h index d318b7bdc0..bc70e5b5ae 100644 --- a/atom/browser/api/atom_api_menu_mac.h +++ b/atom/browser/api/atom_api_menu_mac.h @@ -7,10 +7,13 @@ #include "atom/browser/api/atom_api_menu.h" +#include #include #import "atom/browser/ui/cocoa/atom_menu_controller.h" +using base::scoped_nsobject; + namespace atom { namespace api { @@ -19,15 +22,25 @@ class MenuMac : public Menu { protected: MenuMac(v8::Isolate* isolate, v8::Local wrapper); - void PopupAt(Window* window, int x, int y, int positioning_item) override; - - base::scoped_nsobject menu_controller_; + void PopupAt( + Window* window, int x, int y, int positioning_item, bool async) override; + void PopupOnUI(const base::WeakPtr& native_window, + int32_t window_id, int x, int y, int positioning_item, + bool async); + void ClosePopupAt(int32_t window_id) override; private: friend class Menu; static void SendActionToFirstResponder(const std::string& action); + scoped_nsobject menu_controller_; + + // window ID -> open context menu + std::map> popup_controllers_; + + base::WeakPtrFactory weak_factory_; + DISALLOW_COPY_AND_ASSIGN(MenuMac); }; diff --git a/atom/browser/api/atom_api_menu_mac.mm b/atom/browser/api/atom_api_menu_mac.mm index 9e901d1098..9741202dd7 100644 --- a/atom/browser/api/atom_api_menu_mac.mm +++ b/atom/browser/api/atom_api_menu_mac.mm @@ -6,35 +6,58 @@ #include "atom/browser/native_window.h" #include "atom/browser/unresponsive_suppressor.h" +#include "base/mac/scoped_sending_event.h" #include "base/message_loop/message_loop.h" #include "base/strings/sys_string_conversions.h" #include "brightray/browser/inspectable_web_contents.h" #include "brightray/browser/inspectable_web_contents_view.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" #include "atom/common/node_includes.h" +using content::BrowserThread; + namespace atom { namespace api { MenuMac::MenuMac(v8::Isolate* isolate, v8::Local wrapper) - : Menu(isolate, wrapper) { + : Menu(isolate, wrapper), + weak_factory_(this) { } -void MenuMac::PopupAt(Window* window, int x, int y, int positioning_item) { +void MenuMac::PopupAt( + Window* window, int x, int y, int positioning_item, bool async) { NativeWindow* native_window = window->window(); if (!native_window) return; + + auto popup = base::Bind(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(), + native_window->GetWeakPtr(), window->ID(), x, y, + positioning_item, async); + if (async) + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, popup); + else + popup.Run(); +} + +void MenuMac::PopupOnUI(const base::WeakPtr& native_window, + int32_t window_id, int x, int y, int positioning_item, + bool async) { + if (!native_window) + return; brightray::InspectableWebContents* web_contents = native_window->inspectable_web_contents(); if (!web_contents) return; - base::scoped_nsobject menu_controller( - [[AtomMenuController alloc] initWithModel:model_.get() + auto close_callback = base::Bind(&MenuMac::ClosePopupAt, + weak_factory_.GetWeakPtr(), window_id); + popup_controllers_[window_id] = base::scoped_nsobject( + [[AtomMenuController alloc] initWithModel:model() useDefaultAccelerator:NO]); - NSMenu* menu = [menu_controller menu]; + NSMenu* menu = [popup_controllers_[window_id] menu]; NSView* view = web_contents->GetView()->GetNativeView(); // Which menu item to show. @@ -69,11 +92,33 @@ void MenuMac::PopupAt(Window* window, int x, int y, int positioning_item) { if (rightmostMenuPoint > screenRight) position.x = position.x - [menu size].width; - // Don't emit unresponsive event when showing menu. - atom::UnresponsiveSuppressor suppressor; - // Show the menu. - [menu popUpMenuPositioningItem:item atLocation:position inView:view]; + if (async) { + [popup_controllers_[window_id] setCloseCallback:close_callback]; + // Make sure events can be pumped while the menu is up. + base::MessageLoop::ScopedNestableTaskAllower allow( + base::MessageLoop::current()); + + // One of the events that could be pumped is |window.close()|. + // User-initiated event-tracking loops protect against this by + // setting flags in -[CrApplication sendEvent:], but since + // web-content menus are initiated by IPC message the setup has to + // be done manually. + base::mac::ScopedSendingEvent sendingEventScoper; + + // Don't emit unresponsive event when showing menu. + atom::UnresponsiveSuppressor suppressor; + [menu popUpMenuPositioningItem:item atLocation:position inView:view]; + } else { + // Don't emit unresponsive event when showing menu. + atom::UnresponsiveSuppressor suppressor; + [menu popUpMenuPositioningItem:item atLocation:position inView:view]; + close_callback.Run(); + } +} + +void MenuMac::ClosePopupAt(int32_t window_id) { + popup_controllers_.erase(window_id); } // static diff --git a/atom/browser/api/atom_api_menu_views.cc b/atom/browser/api/atom_api_menu_views.cc index 8a72247c9d..59fdac3949 100644 --- a/atom/browser/api/atom_api_menu_views.cc +++ b/atom/browser/api/atom_api_menu_views.cc @@ -8,17 +8,20 @@ #include "atom/browser/unresponsive_suppressor.h" #include "content/public/browser/render_widget_host_view.h" #include "ui/display/screen.h" -#include "ui/views/controls/menu/menu_runner.h" + +using views::MenuRunner; namespace atom { namespace api { MenuViews::MenuViews(v8::Isolate* isolate, v8::Local wrapper) - : Menu(isolate, wrapper) { + : Menu(isolate, wrapper), + weak_factory_(this) { } -void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { +void MenuViews::PopupAt( + Window* window, int x, int y, int positioning_item, bool async) { NativeWindow* native_window = static_cast(window->window()); if (!native_window) return; @@ -38,14 +41,20 @@ void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { location = gfx::Point(origin.x() + x, origin.y() + y); } + int flags = MenuRunner::CONTEXT_MENU | MenuRunner::HAS_MNEMONICS; + if (async) + flags |= MenuRunner::ASYNC; + // Don't emit unresponsive event when showing menu. atom::UnresponsiveSuppressor suppressor; // Show the menu. - views::MenuRunner menu_runner( - model(), - views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); - ignore_result(menu_runner.RunMenuAt( + int32_t window_id = window->ID(); + auto close_callback = base::Bind( + &MenuViews::ClosePopupAt, weak_factory_.GetWeakPtr(), window_id); + menu_runners_[window_id] = std::unique_ptr(new MenuRunner( + model(), flags, close_callback)); + ignore_result(menu_runners_[window_id]->RunMenuAt( static_cast(window->window())->widget(), NULL, gfx::Rect(location, gfx::Size()), @@ -53,6 +62,10 @@ void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { ui::MENU_SOURCE_MOUSE)); } +void MenuViews::ClosePopupAt(int32_t window_id) { + menu_runners_.erase(window_id); +} + // static mate::WrappableBase* Menu::New(mate::Arguments* args) { return new MenuViews(args->isolate(), args->GetThis()); diff --git a/atom/browser/api/atom_api_menu_views.h b/atom/browser/api/atom_api_menu_views.h index 1e7abd1372..a974f75bc7 100644 --- a/atom/browser/api/atom_api_menu_views.h +++ b/atom/browser/api/atom_api_menu_views.h @@ -5,8 +5,12 @@ #ifndef ATOM_BROWSER_API_ATOM_API_MENU_VIEWS_H_ #define ATOM_BROWSER_API_ATOM_API_MENU_VIEWS_H_ +#include + #include "atom/browser/api/atom_api_menu.h" +#include "base/memory/weak_ptr.h" #include "ui/display/screen.h" +#include "ui/views/controls/menu/menu_runner.h" namespace atom { @@ -17,9 +21,16 @@ class MenuViews : public Menu { MenuViews(v8::Isolate* isolate, v8::Local wrapper); protected: - void PopupAt(Window* window, int x, int y, int positioning_item) override; + void PopupAt( + Window* window, int x, int y, int positioning_item, bool async) override; + void ClosePopupAt(int32_t window_id) override; private: + // window ID -> open context menu + std::map> menu_runners_; + + base::WeakPtrFactory weak_factory_; + DISALLOW_COPY_AND_ASSIGN(MenuViews); }; diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 45bb9c2cf3..83d103a631 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -204,6 +204,18 @@ struct Converter { } }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + atom::VerifyRequestParams val) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("hostname", val.hostname); + dict.Set("certificate", val.certificate); + dict.Set("verificationResult", val.default_result); + return dict.GetHandle(); + } +}; + } // namespace mate namespace atom { @@ -221,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 = @@ -738,7 +750,7 @@ void Session::BuildPrototype(v8::Isolate* isolate, .SetMethod("setDownloadPath", &Session::SetDownloadPath) .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) - .SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc) + .SetMethod("_setCertificateVerifyProc", &Session::SetCertVerifyProc) .SetMethod("setPermissionRequestHandler", &Session::SetPermissionRequestHandler) .SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 967ae50a7e..d3607e7283 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 c92ac01961..372ac98ac6 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 83180f86e0..5f7b23f1d0 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -22,6 +22,7 @@ #include "atom/browser/ui/drag_util.h" #include "atom/browser/web_contents_permission_helper.h" #include "atom/browser/web_contents_preferences.h" +#include "atom/browser/web_contents_zoom_controller.h" #include "atom/browser/web_view_guest_delegate.h" #include "atom/common/api/api_messages.h" #include "atom/common/api/event_emitter_caller.h" @@ -46,6 +47,7 @@ #include "chrome/browser/printing/print_preview_message_handler.h" #include "chrome/browser/printing/print_view_manager_basic.h" #include "chrome/browser/ssl/security_state_tab_helper.h" +#include "content/browser/frame_host/navigation_entry_impl.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/view_messages.h" @@ -54,6 +56,9 @@ #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" #include "content/public/browser/plugin_service.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" @@ -201,6 +206,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; @@ -215,10 +221,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 { @@ -253,12 +261,28 @@ 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)); } +// Set the background color of RenderWidgetHostView. +void SetBackgroundColor(content::WebContents* web_contents) { + const auto view = web_contents->GetRenderWidgetHostView(); + if (view) { + WebContentsPreferences* web_preferences = + WebContentsPreferences::FromWebContents(web_contents); + std::string color_name; + if (web_preferences->web_preferences()->GetString(options::kBackgroundColor, + &color_name)) { + view->SetBackgroundColor(ParseHexColor(color_name)); + } else { + view->SetBackgroundColor(SK_ColorTRANSPARENT); + } + } +} + } // namespace WebContents::WebContents(v8::Isolate* isolate, @@ -266,11 +290,11 @@ WebContents::WebContents(v8::Isolate* isolate, Type type) : content::WebContentsObserver(web_contents), embedder_(nullptr), + zoom_controller_(nullptr), type_(type), request_id_(0), background_throttling_(true), enable_devtools_(true) { - if (type == REMOTE) { web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); Init(isolate); @@ -283,9 +307,9 @@ WebContents::WebContents(v8::Isolate* isolate, } } -WebContents::WebContents(v8::Isolate* isolate, - const mate::Dictionary& options) +WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) : embedder_(nullptr), + zoom_controller_(nullptr), type_(BROWSER_WINDOW), request_id_(0), background_throttling_(true), @@ -303,6 +327,8 @@ WebContents::WebContents(v8::Isolate* isolate, 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; @@ -355,7 +381,7 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, content::WebContents *web_contents, mate::Handle session, const mate::Dictionary& options) { - Observe(web_contents); + content::WebContentsObserver::Observe(web_contents); InitWithWebContents(web_contents, session->browser_context()); managed_web_contents()->GetView()->SetDelegate(this); @@ -363,10 +389,16 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, // Save the preferences in C++. new WebContentsPreferences(web_contents, options); - // Intialize permission helper. + // Initialize permission helper. WebContentsPermissionHelper::CreateForWebContents(web_contents); - // Intialize security state client. + // Initialize security state client. SecurityStateTabHelper::CreateForWebContents(web_contents); + // Initialize zoom controller. + WebContentsZoomController::CreateForWebContents(web_contents); + zoom_controller_ = WebContentsZoomController::FromWebContents(web_contents); + double zoom_factor; + if (options.Get(options::kZoomFactor, &zoom_factor)) + zoom_controller_->SetDefaultZoomFactor(zoom_factor); web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); @@ -385,6 +417,11 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, SetOwnerWindow(owner_window); } + const content::NavigationController* controller = + &web_contents->GetController(); + registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, + content::Source(controller)); + Init(isolate); AttachAsUserData(web_contents); } @@ -397,14 +434,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, @@ -454,7 +508,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 */); } } @@ -744,6 +798,30 @@ void WebContents::DidGetRedirectForResourceRequest( details.headers.get()); } +void WebContents::DidStartNavigation( + content::NavigationHandle* navigation_handle) { + if (!navigation_handle->IsInMainFrame() || navigation_handle->IsSamePage()) + return; + + if (deferred_load_url_.id) { + auto web_contents = navigation_handle->GetWebContents(); + auto& controller = web_contents->GetController(); + int id = controller.GetPendingEntry()->GetUniqueID(); + if (id == deferred_load_url_.id) { + if (!deferred_load_url_.params.url.is_empty()) { + auto params = deferred_load_url_.params; + deferred_load_url_.id = 0; + deferred_load_url_.params = + content::NavigationController::LoadURLParams(GURL()); + controller.LoadURLWithParams(params); + SetBackgroundColor(web_contents); + } else { + deferred_load_url_.id = 0; + } + } + } +} + void WebContents::DidFinishNavigation( content::NavigationHandle* navigation_handle) { bool is_main_frame = navigation_handle->IsInMainFrame(); @@ -769,10 +847,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( @@ -788,6 +864,32 @@ void WebContents::DidUpdateFaviconURL( Emit("page-favicon-updated", unique_urls); } +void WebContents::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case content::NOTIFICATION_NAV_ENTRY_PENDING: { + content::NavigationEntry* entry = + content::Details(details).ptr(); + content::NavigationEntryImpl* entry_impl = + static_cast(entry); + // In NavigatorImpl::DidStartMainFrameNavigation when there is no + // browser side pending entry available it creates a new one based + // on existing pending entry, hence we track the unique id here + // instead in WebContents::LoadURL with controller.GetPendingEntry() + // TODO(deepak1556): Remove once we have + // https://codereview.chromium.org/2661743002. + if (entry_impl->frame_tree_node_id() == -1) { + deferred_load_url_.id = entry->GetUniqueID(); + } + break; + } + default: + NOTREACHED(); + break; + } +} + void WebContents::DevToolsReloadPage() { Emit("devtools-reload-page"); } @@ -830,6 +932,10 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(AtomViewHostMsg_Message, OnRendererMessage) IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_Message_Sync, OnRendererMessageSync) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_SetTemporaryZoomLevel, + OnSetTemporaryZoomLevel) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AtomViewHostMsg_GetZoomLevel, + OnGetZoomLevel) IPC_MESSAGE_HANDLER_CODE(ViewHostMsg_SetCursor, OnCursorChange, handled = false) IPC_MESSAGE_UNHANDLED(handled = false) @@ -851,10 +957,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(); @@ -925,26 +1027,25 @@ void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { params.load_type = content::NavigationController::LOAD_TYPE_HTTP_POST; } + GURL base_url_for_data_url; + if (options.Get("baseURLForDataURL", &base_url_for_data_url)) { + params.base_url_for_data_url = base_url_for_data_url; + params.load_type = content::NavigationController::LOAD_TYPE_DATA; + } + params.transition_type = ui::PAGE_TRANSITION_TYPED; params.should_clear_history_list = true; params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE; - web_contents()->GetController().LoadURLWithParams(params); - // Set the background color of RenderWidgetHostView. + if (deferred_load_url_.id) { + deferred_load_url_.params = params; + return; + } + + web_contents()->GetController().LoadURLWithParams(params); // We have to call it right after LoadURL because the RenderViewHost is only // created after loading a page. - const auto view = web_contents()->GetRenderWidgetHostView(); - if (view) { - WebContentsPreferences* web_preferences = - WebContentsPreferences::FromWebContents(web_contents()); - std::string color_name; - if (web_preferences->web_preferences()->GetString(options::kBackgroundColor, - &color_name)) { - view->SetBackgroundColor(ParseHexColor(color_name)); - } else { - view->SetBackgroundColor(SK_ColorTRANSPARENT); - } - } + SetBackgroundColor(web_contents()); } void WebContents::DownloadURL(const GURL& url) { @@ -1000,6 +1101,23 @@ void WebContents::GoToOffset(int offset) { web_contents()->GetController().GoToOffset(offset); } +const std::string WebContents::GetWebRTCIPHandlingPolicy() const { + return web_contents()-> + GetMutableRendererPrefs()->webrtc_ip_handling_policy; +} + +void WebContents::SetWebRTCIPHandlingPolicy( + const std::string& webrtc_ip_handling_policy) { + if (GetWebRTCIPHandlingPolicy() == webrtc_ip_handling_policy) + return; + web_contents()->GetMutableRendererPrefs()->webrtc_ip_handling_policy = + webrtc_ip_handling_policy; + + content::RenderViewHost* host = web_contents()->GetRenderViewHost(); + if (host) + host->SyncRendererPrefs(); +} + bool WebContents::IsCrashed() const { return web_contents()->IsCrashed(); } @@ -1517,13 +1635,47 @@ int WebContents::GetFrameRate() const { } void WebContents::Invalidate() { - if (!IsOffScreen()) - return; - - auto* osr_rwhv = static_cast( + if (IsOffScreen()) { + auto* osr_rwhv = static_cast( web_contents()->GetRenderWidgetHostView()); - if (osr_rwhv) - osr_rwhv->Invalidate(); + if (osr_rwhv) + osr_rwhv->Invalidate(); + } else { + const auto window = owner_window(); + if (window) + window->Invalidate(); + } +} + +void WebContents::SetZoomLevel(double level) { + zoom_controller_->SetZoomLevel(level); +} + +double WebContents::GetZoomLevel() { + return zoom_controller_->GetZoomLevel(); +} + +void WebContents::SetZoomFactor(double factor) { + auto level = content::ZoomFactorToZoomLevel(factor); + SetZoomLevel(level); +} + +double WebContents::GetZoomFactor() { + auto level = GetZoomLevel(); + return content::ZoomLevelToZoomFactor(level); +} + +void WebContents::OnSetTemporaryZoomLevel(double level, + IPC::Message* reply_msg) { + zoom_controller_->SetTemporaryZoomLevel(level); + double new_level = zoom_controller_->GetZoomLevel(); + AtomViewHostMsg_SetTemporaryZoomLevel::WriteReplyParams(reply_msg, new_level); + Send(reply_msg); +} + +void WebContents::OnGetZoomLevel(IPC::Message* reply_msg) { + AtomViewHostMsg_GetZoomLevel::WriteReplyParams(reply_msg, GetZoomLevel()); + Send(reply_msg); } v8::Local WebContents::GetWebPreferences(v8::Isolate* isolate) { @@ -1654,6 +1806,10 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("setFrameRate", &WebContents::SetFrameRate) .SetMethod("getFrameRate", &WebContents::GetFrameRate) .SetMethod("invalidate", &WebContents::Invalidate) + .SetMethod("setZoomLevel", &WebContents::SetZoomLevel) + .SetMethod("_getZoomLevel", &WebContents::GetZoomLevel) + .SetMethod("setZoomFactor", &WebContents::SetZoomFactor) + .SetMethod("_getZoomFactor", &WebContents::GetZoomFactor) .SetMethod("getType", &WebContents::GetType) .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) @@ -1671,6 +1827,10 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("copyImageAt", &WebContents::CopyImageAt) .SetMethod("capturePage", &WebContents::CapturePage) .SetMethod("setEmbedder", &WebContents::SetEmbedder) + .SetMethod("setWebRTCIPHandlingPolicy", + &WebContents::SetWebRTCIPHandlingPolicy) + .SetMethod("getWebRTCIPHandlingPolicy", + &WebContents::GetWebRTCIPHandlingPolicy) .SetProperty("id", &WebContents::ID) .SetProperty("session", &WebContents::Session) .SetProperty("hostWebContents", &WebContents::HostWebContents) diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 7c11d65ad0..2e5a89a276 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -13,6 +13,8 @@ #include "atom/browser/api/trackable_object.h" #include "atom/browser/common_web_contents_delegate.h" #include "content/common/cursors/webcursor.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/common/favicon_url.h" #include "native_mate/handle.h" @@ -40,20 +42,23 @@ namespace atom { struct SetSizeParams; class AtomBrowserContext; +class WebContentsZoomController; class WebViewGuestDelegate; namespace api { class WebContents : public mate::TrackableObject, public CommonWebContentsDelegate, - public content::WebContentsObserver { + public content::WebContentsObserver, + public content::NotificationObserver { 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) @@ -73,6 +78,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; @@ -89,6 +97,8 @@ class WebContents : public mate::TrackableObject, void GoBack(); void GoForward(); void GoToOffset(int offset); + const std::string GetWebRTCIPHandlingPolicy() const; + void SetWebRTCIPHandlingPolicy(const std::string& webrtc_ip_handling_policy); bool IsCrashed() const; void SetUserAgent(const std::string& user_agent, mate::Arguments* args); std::string GetUserAgent(); @@ -176,6 +186,12 @@ class WebContents : public mate::TrackableObject, int GetFrameRate() const; void Invalidate(); + // Methods for zoom handling. + void SetZoomLevel(double level); + double GetZoomLevel(); + void SetZoomFactor(double factor); + double GetZoomFactor(); + // Callback triggered on permission response. void OnEnterFullscreenModeForTab(content::WebContents* source, const GURL& origin, @@ -202,6 +218,8 @@ class WebContents : public mate::TrackableObject, v8::Local DevToolsWebContents(v8::Isolate* isolate); v8::Local Debugger(v8::Isolate* isolate); + WebContentsZoomController* GetZoomController() { return zoom_controller_; } + protected: WebContents(v8::Isolate* isolate, content::WebContents* web_contents, @@ -301,6 +319,8 @@ class WebContents : public mate::TrackableObject, const content::ResourceRequestDetails& details) override; void DidGetRedirectForResourceRequest( const content::ResourceRedirectDetails& details) override; + void DidStartNavigation( + content::NavigationHandle* navigation_handle) override; void DidFinishNavigation( content::NavigationHandle* navigation_handle) override; bool OnMessageReceived(const IPC::Message& message) override; @@ -318,6 +338,11 @@ class WebContents : public mate::TrackableObject, const MediaPlayerId& id) override; void DidChangeThemeColor(SkColor theme_color) override; + // content::NotificationObserver: + void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) override; + // brightray::InspectableWebContentsDelegate: void DevToolsReloadPage() override; @@ -327,6 +352,13 @@ class WebContents : public mate::TrackableObject, void DevToolsClosed() override; private: + struct LoadURLParams { + LoadURLParams() : params(GURL()), id(0) {} + + content::NavigationController::LoadURLParams params; + int id; + }; + AtomBrowserContext* GetBrowserContext() const; uint32_t GetNextRequestId() { @@ -345,6 +377,14 @@ class WebContents : public mate::TrackableObject, const base::ListValue& args, IPC::Message* message); + // Called when received a synchronous message from renderer to + // set temporary zoom level. + void OnSetTemporaryZoomLevel(double level, IPC::Message* reply_msg); + + // Called when received a synchronous message from renderer to + // get the zoom level. + void OnGetZoomLevel(IPC::Message* reply_msg); + v8::Global session_; v8::Global devtools_web_contents_; v8::Global debugger_; @@ -354,6 +394,9 @@ class WebContents : public mate::TrackableObject, // The host webcontents that may contain this webcontents. WebContents* embedder_; + // The zoom controller for this webContents. + WebContentsZoomController* zoom_controller_; + // The type of current WebContents. Type type_; @@ -366,6 +409,11 @@ class WebContents : public mate::TrackableObject, // Whether to enable devtools. bool enable_devtools_; + // Container to hold url parms for deferred load when + // there is a pending navigation entry. + LoadURLParams deferred_load_url_; + content::NotificationRegistrar registrar_; + DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/atom/browser/api/atom_api_web_view_manager.cc b/atom/browser/api/atom_api_web_view_manager.cc index 1586c3a10d..961cb03220 100644 --- a/atom/browser/api/atom_api_web_view_manager.cc +++ b/atom/browser/api/atom_api_web_view_manager.cc @@ -3,10 +3,12 @@ // found in the LICENSE file. #include "atom/browser/web_contents_preferences.h" +#include "atom/browser/web_contents_zoom_controller.h" #include "atom/browser/web_view_manager.h" #include "atom/common/native_mate_converters/content_converter.h" #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_includes.h" +#include "atom/common/options_switches.h" #include "content/public/browser/browser_context.h" #include "native_mate/dictionary.h" @@ -24,6 +26,12 @@ void AddGuest(int guest_instance_id, manager->AddGuest(guest_instance_id, element_instance_id, embedder, guest_web_contents); + double zoom_factor; + if (options.GetDouble(atom::options::kZoomFactor, &zoom_factor)) { + atom::WebContentsZoomController::FromWebContents(guest_web_contents) + ->SetDefaultZoomFactor(zoom_factor); + } + WebContentsPreferences::FromWebContents(guest_web_contents)->Merge(options); } diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 2f97a88faa..6862915f9c 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"); } @@ -282,6 +295,11 @@ void Window::OnExecuteWindowsCommand(const std::string& command_name) { Emit("app-command", command_name); } +void Window::OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) { + Emit("-touch-bar-interaction", item_id, details); +} + #if defined(OS_WIN) void Window::OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) { if (IsWindowMessageHooked(message)) { @@ -811,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(); } @@ -840,15 +877,28 @@ void Window::SetVibrancy(mate::Arguments* args) { window_->SetVibrancy(type); } +void Window::SetTouchBar(const std::vector& items) { + window_->SetTouchBar(items); +} + +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() { @@ -893,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) @@ -960,6 +1012,9 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setAutoHideCursor", &Window::SetAutoHideCursor) #endif .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 9908feedbc..75f0328ba6 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -16,6 +16,7 @@ #include "atom/common/api/atom_api_native_image.h" #include "atom/common/key_weak_map.h" #include "native_mate/handle.h" +#include "native_mate/persistent_dictionary.h" #include "ui/gfx/image/image.h" class GURL; @@ -51,6 +52,8 @@ class Window : public mate::TrackableObject, NativeWindow* window() const { return window_.get(); } + int32_t ID() const; + protected: Window(v8::Isolate* isolate, v8::Local wrapper, const mate::Dictionary& options); @@ -60,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; @@ -76,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; @@ -83,6 +89,8 @@ class Window : public mate::TrackableObject, void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnExecuteWindowsCommand(const std::string& command_name) override; + void OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) override; #if defined(OS_WIN) void OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) override; @@ -175,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(); @@ -201,8 +211,10 @@ class Window : public mate::TrackableObject, void SetAutoHideCursor(bool auto_hide); void SetVibrancy(mate::Arguments* args); + void SetTouchBar(const std::vector& items); + void RefreshTouchBarItem(const std::string& item_id); + void SetEscapeTouchBarItem(const mate::PersistentDictionary& item); - int32_t ID() const; v8::Local WebContents(v8::Isolate* isolate); // Remove this window from parent window's |child_windows_|. @@ -213,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 aef54dfa0e..6a5597ca77 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 07884e58d6..820ceddce4 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 d0bbf4ad53..77c3212e2e 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 c2a7d5edd0..70573d6eee 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/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index e386e678a4..d3f8237d5e 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -8,11 +8,13 @@ #include "atom/browser/atom_access_token_store.h" #include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_context.h" +#include "atom/browser/atom_web_ui_controller_factory.h" #include "atom/browser/bridge_task_runner.h" #include "atom/browser/browser.h" #include "atom/browser/javascript_environment.h" #include "atom/browser/node_debugger.h" #include "atom/common/api/atom_bindings.h" +#include "atom/common/asar/asar_util.h" #include "atom/common/node_bindings.h" #include "atom/common/node_includes.h" #include "base/command_line.h" @@ -59,8 +61,8 @@ AtomBrowserMainParts::AtomBrowserMainParts() : fake_browser_process_(new BrowserProcess), exit_code_(nullptr), browser_(new Browser), - node_bindings_(NodeBindings::Create(true)), - atom_bindings_(new AtomBindings), + node_bindings_(NodeBindings::Create(NodeBindings::BROWSER)), + atom_bindings_(new AtomBindings(uv_default_loop())), gc_timer_(true, true) { DCHECK(!self_) << "Cannot have two AtomBrowserMainParts"; self_ = this; @@ -70,6 +72,7 @@ AtomBrowserMainParts::AtomBrowserMainParts() } AtomBrowserMainParts::~AtomBrowserMainParts() { + asar::ClearArchives(); // Leak the JavascriptEnvironment on exit. // This is to work around the bug that V8 would be waiting for background // tasks to finish on exit, while somehow it waits forever in Electron, more @@ -132,6 +135,7 @@ void AtomBrowserMainParts::PostEarlyInitialization() { // Create the global environment. node::Environment* env = node_bindings_->CreateEnvironment(js_env_->context()); + node_env_.reset(new NodeEnvironment(env)); // Make sure node can get correct environment when debugging. if (node_debugger_->IsRunning()) @@ -165,6 +169,9 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { base::Bind(&v8::Isolate::LowMemoryNotification, base::Unretained(js_env_->isolate()))); + content::WebUIControllerFactory::RegisterFactory( + AtomWebUIControllerFactory::GetInstance()); + brightray::BrowserMainParts::PreMainMessageLoopRun(); bridge_task_runner_->MessageLoopIsReady(); bridge_task_runner_ = nullptr; diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index 0d8619f686..2ba7d341f4 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -22,6 +22,7 @@ class Browser; class JavascriptEnvironment; class NodeBindings; class NodeDebugger; +class NodeEnvironment; class BridgeTaskRunner; class AtomBrowserMainParts : public brightray::BrowserMainParts { @@ -81,6 +82,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { std::unique_ptr js_env_; std::unique_ptr node_bindings_; std::unique_ptr atom_bindings_; + std::unique_ptr node_env_; std::unique_ptr node_debugger_; base::Timer gc_timer_; diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index 0213216697..ccbc43416a 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -90,10 +90,11 @@ void AtomDownloadManagerDelegate::OnDownloadPathGenerated( base::FilePath path; GetItemSavePath(item, &path); // Show save dialog if save path was not set already on item - if (path.empty() && file_dialog::ShowSaveDialog(window, item->GetURL().spec(), - "", default_path, - file_dialog::Filters(), - &path)) { + file_dialog::DialogSettings settings; + settings.parent_window = window; + settings.title = item->GetURL().spec(); + settings.default_path = default_path; + if (path.empty() && file_dialog::ShowSaveDialog(settings, &path)) { // Remember the last selected download directory. AtomBrowserContext* browser_context = static_cast( download_manager_->GetBrowserContext()); diff --git a/atom/browser/atom_javascript_dialog_manager.cc b/atom/browser/atom_javascript_dialog_manager.cc index 24197915e6..f874a99db5 100644 --- a/atom/browser/atom_javascript_dialog_manager.cc +++ b/atom/browser/atom_javascript_dialog_manager.cc @@ -38,14 +38,9 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog( } atom::ShowMessageBox(NativeWindow::FromWebContents(web_contents), - atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE, - buttons, - -1, - 0, - atom::MessageBoxOptions::MESSAGE_BOX_NONE, - "", - base::UTF16ToUTF8(message_text), - "", + atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE, buttons, -1, + 0, atom::MessageBoxOptions::MESSAGE_BOX_NONE, "", + base::UTF16ToUTF8(message_text), "", "", false, gfx::ImageSkia(), base::Bind(&OnMessageBoxCallback, callback)); } @@ -66,7 +61,9 @@ void AtomJavaScriptDialogManager::CancelDialogs( // static void AtomJavaScriptDialogManager::OnMessageBoxCallback( - const DialogClosedCallback& callback, int code) { + const DialogClosedCallback& callback, + int code, + bool checkbox_checked) { callback.Run(code == 0, base::string16()); } diff --git a/atom/browser/atom_javascript_dialog_manager.h b/atom/browser/atom_javascript_dialog_manager.h index e5bb6114bc..01cc76248c 100644 --- a/atom/browser/atom_javascript_dialog_manager.h +++ b/atom/browser/atom_javascript_dialog_manager.h @@ -32,7 +32,8 @@ class AtomJavaScriptDialogManager : public content::JavaScriptDialogManager { private: static void OnMessageBoxCallback(const DialogClosedCallback& callback, - int code); + int code, + bool checkbox_checked); }; } // namespace atom diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.cc b/atom/browser/atom_resource_dispatcher_host_delegate.cc index 78589112c5..abd2fb53f1 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.cc +++ b/atom/browser/atom_resource_dispatcher_host_delegate.cc @@ -6,11 +6,15 @@ #include "atom/browser/login_handler.h" #include "atom/browser/web_contents_permission_helper.h" +#include "atom/common/atom_constants.h" #include "atom/common/platform_util.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/stream_info.h" #include "net/base/escape.h" #include "net/ssl/client_cert_store.h" +#include "net/url_request/url_request.h" #include "url/gurl.h" #if defined(USE_NSS_CERTS) @@ -57,6 +61,23 @@ void HandleExternalProtocolInUI( permission_helper->RequestOpenExternalPermission(callback, has_user_gesture); } +void OnPdfResourceIntercepted( + const GURL& original_url, + const content::ResourceRequestInfo::WebContentsGetter& + web_contents_getter) { + content::WebContents* web_contents = web_contents_getter.Run(); + if (!web_contents) + return; + + // The URL passes the original pdf resource url, that will be requested + // by the webui page. + // chrome://pdf-viewer/index.html?src=https://somepage/123.pdf + content::NavigationController::LoadURLParams params( + GURL(base::StringPrintf("%sindex.html?%s=%s", kPdfViewerUIOrigin, + kPdfPluginSrc, original_url.spec().c_str()))); + web_contents->GetController().LoadURLWithParams(params); +} + } // namespace AtomResourceDispatcherHostDelegate::AtomResourceDispatcherHostDelegate() { @@ -95,4 +116,23 @@ AtomResourceDispatcherHostDelegate::CreateClientCertStore( #endif } +bool AtomResourceDispatcherHostDelegate::ShouldInterceptResourceAsStream( + net::URLRequest* request, + const base::FilePath& plugin_path, + const std::string& mime_type, + GURL* origin, + std::string* payload) { + const content::ResourceRequestInfo* info = + content::ResourceRequestInfo::ForRequest(request); + if (mime_type == "application/pdf" && info->IsMainFrame()) { + *origin = GURL(kPdfViewerUIOrigin); + content::BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&OnPdfResourceIntercepted, request->url(), + info->GetWebContentsGetterForRequest())); + return true; + } + return false; +} + } // namespace atom diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.h b/atom/browser/atom_resource_dispatcher_host_delegate.h index 681fec6f6f..eda744db51 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.h +++ b/atom/browser/atom_resource_dispatcher_host_delegate.h @@ -5,6 +5,8 @@ #ifndef ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_ #define ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_ +#include + #include "content/public/browser/resource_dispatcher_host_delegate.h" namespace atom { @@ -22,6 +24,14 @@ class AtomResourceDispatcherHostDelegate net::URLRequest* request) override; std::unique_ptr CreateClientCertStore( content::ResourceContext* resource_context) override; + bool ShouldInterceptResourceAsStream(net::URLRequest* request, + const base::FilePath& plugin_path, + const std::string& mime_type, + GURL* origin, + std::string* payload) override; + + private: + DISALLOW_COPY_AND_ASSIGN(AtomResourceDispatcherHostDelegate); }; } // namespace atom diff --git a/atom/browser/atom_web_ui_controller_factory.cc b/atom/browser/atom_web_ui_controller_factory.cc new file mode 100644 index 0000000000..a73eed19a1 --- /dev/null +++ b/atom/browser/atom_web_ui_controller_factory.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/atom_web_ui_controller_factory.h" + +#include + +#include "atom/browser/ui/webui/pdf_viewer_ui.h" +#include "atom/common/atom_constants.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "content/public/browser/web_contents.h" + +namespace atom { + +// static +AtomWebUIControllerFactory* AtomWebUIControllerFactory::GetInstance() { + return base::Singleton::get(); +} + +AtomWebUIControllerFactory::AtomWebUIControllerFactory() {} + +AtomWebUIControllerFactory::~AtomWebUIControllerFactory() {} + +content::WebUI::TypeID AtomWebUIControllerFactory::GetWebUIType( + content::BrowserContext* browser_context, + const GURL& url) const { + if (url.host() == kPdfViewerUIHost) { + return const_cast(this); + } + + return content::WebUI::kNoWebUI; +} + +bool AtomWebUIControllerFactory::UseWebUIForURL( + content::BrowserContext* browser_context, + const GURL& url) const { + return GetWebUIType(browser_context, url) != content::WebUI::kNoWebUI; +} + +bool AtomWebUIControllerFactory::UseWebUIBindingsForURL( + content::BrowserContext* browser_context, + const GURL& url) const { + return UseWebUIForURL(browser_context, url); +} + +content::WebUIController* +AtomWebUIControllerFactory::CreateWebUIControllerForURL(content::WebUI* web_ui, + const GURL& url) const { + if (url.host() == kPdfViewerUIHost) { + base::StringPairs toplevel_params; + base::SplitStringIntoKeyValuePairs(url.query(), '=', '&', &toplevel_params); + std::string stream_id, src; + for (const auto& param : toplevel_params) { + if (param.first == kPdfPluginSrc) { + src = param.second; + } + } + auto browser_context = web_ui->GetWebContents()->GetBrowserContext(); + return new PdfViewerUI(browser_context, web_ui, src); + } + return nullptr; +} + +} // namespace atom diff --git a/atom/browser/atom_web_ui_controller_factory.h b/atom/browser/atom_web_ui_controller_factory.h new file mode 100644 index 0000000000..41819daf8c --- /dev/null +++ b/atom/browser/atom_web_ui_controller_factory.h @@ -0,0 +1,40 @@ +// 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_ATOM_WEB_UI_CONTROLLER_FACTORY_H_ +#define ATOM_BROWSER_ATOM_WEB_UI_CONTROLLER_FACTORY_H_ + +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "content/public/browser/web_ui_controller_factory.h" + +namespace atom { + +class AtomWebUIControllerFactory : public content::WebUIControllerFactory { + public: + static AtomWebUIControllerFactory* GetInstance(); + + AtomWebUIControllerFactory(); + virtual ~AtomWebUIControllerFactory(); + + // content::WebUIControllerFactory: + content::WebUI::TypeID GetWebUIType(content::BrowserContext* browser_context, + const GURL& url) const override; + bool UseWebUIForURL(content::BrowserContext* browser_context, + const GURL& url) const override; + bool UseWebUIBindingsForURL(content::BrowserContext* browser_context, + const GURL& url) const override; + content::WebUIController* CreateWebUIControllerForURL( + content::WebUI* web_ui, + const GURL& url) const override; + + private: + friend struct base::DefaultSingletonTraits; + + DISALLOW_COPY_AND_ASSIGN(AtomWebUIControllerFactory); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_WEB_UI_CONTROLLER_FACTORY_H_ diff --git a/atom/browser/auto_updater_mac.mm b/atom/browser/auto_updater_mac.mm index a6102f3e73..3802fef162 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 9b7423cd7d..b2900a326f 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 28103a99c7..78cac65f7e 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 6abfcf5c34..f569040a21 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 c318cf8507..38a0a003d9 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 85990bbc67..ac0f713c88 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 ed0fd5e4ba..282fa92de6 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 { @@ -294,10 +304,11 @@ void CommonWebContentsDelegate::DevToolsSaveToFile( if (it != saved_files_.end() && !save_as) { path = it->second; } else { - file_dialog::Filters filters; - base::FilePath default_path(base::FilePath::FromUTF8Unsafe(url)); - if (!file_dialog::ShowSaveDialog(owner_window(), url, "", default_path, - filters, &path)) { + file_dialog::DialogSettings settings; + settings.parent_window = owner_window(); + settings.title = url; + settings.default_path = base::FilePath::FromUTF8Unsafe(url); + if (!file_dialog::ShowSaveDialog(settings, &path)) { base::StringValue url_value(url); web_contents_->CallClientFunction( "DevToolsAPI.canceledSaveURL", &url_value, nullptr, nullptr); @@ -337,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); @@ -358,12 +369,11 @@ void CommonWebContentsDelegate::DevToolsAddFileSystem( const base::FilePath& file_system_path) { base::FilePath path = file_system_path; if (path.empty()) { - file_dialog::Filters filters; - base::FilePath default_path; std::vector paths; - int flag = file_dialog::FILE_DIALOG_OPEN_DIRECTORY; - if (!file_dialog::ShowOpenDialog(owner_window(), "", "", default_path, - filters, flag, &paths)) + file_dialog::DialogSettings settings; + settings.parent_window = owner_window(); + settings.properties = file_dialog::FILE_DIALOG_OPEN_DIRECTORY; + if (!file_dialog::ShowOpenDialog(settings, &paths)) return; path = paths[0]; diff --git a/atom/browser/common_web_contents_delegate.h b/atom/browser/common_web_contents_delegate.h index c08f8d246c..d1d2631496 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/javascript_environment.cc b/atom/browser/javascript_environment.cc index 3cdd2c771e..b3e01c1c30 100644 --- a/atom/browser/javascript_environment.cc +++ b/atom/browser/javascript_environment.cc @@ -12,6 +12,8 @@ #include "gin/array_buffer.h" #include "gin/v8_initializer.h" +#include "atom/common/node_includes.h" + namespace atom { JavascriptEnvironment::JavascriptEnvironment() @@ -46,4 +48,11 @@ bool JavascriptEnvironment::Initialize() { return true; } +NodeEnvironment::NodeEnvironment(node::Environment* env) : env_(env) { +} + +NodeEnvironment::~NodeEnvironment() { + node::FreeEnvironment(env_); +} + } // namespace atom diff --git a/atom/browser/javascript_environment.h b/atom/browser/javascript_environment.h index 1f4d2f4534..43a7295f90 100644 --- a/atom/browser/javascript_environment.h +++ b/atom/browser/javascript_environment.h @@ -8,8 +8,13 @@ #include "base/macros.h" #include "gin/public/isolate_holder.h" +namespace node { +class Environment; +} + namespace atom { +// Manage the V8 isolate and context automatically. class JavascriptEnvironment { public: JavascriptEnvironment(); @@ -37,6 +42,18 @@ class JavascriptEnvironment { DISALLOW_COPY_AND_ASSIGN(JavascriptEnvironment); }; +// Manage the Node Environment automatically. +class NodeEnvironment { + public: + explicit NodeEnvironment(node::Environment* env); + ~NodeEnvironment(); + + private: + node::Environment* env_; + + DISALLOW_COPY_AND_ASSIGN(NodeEnvironment); +}; + } // namespace atom #endif // ATOM_BROWSER_JAVASCRIPT_ENVIRONMENT_H_ diff --git a/atom/browser/loader/layered_resource_handler.cc b/atom/browser/loader/layered_resource_handler.cc new file mode 100644 index 0000000000..8b41b1ad63 --- /dev/null +++ b/atom/browser/loader/layered_resource_handler.cc @@ -0,0 +1,26 @@ +// 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/loader/layered_resource_handler.h" + +namespace atom { + +LayeredResourceHandler::LayeredResourceHandler( + net::URLRequest* request, + std::unique_ptr next_handler, + Delegate* delegate) + : content::LayeredResourceHandler(request, std::move(next_handler)), + delegate_(delegate) {} + +LayeredResourceHandler::~LayeredResourceHandler() {} + +bool LayeredResourceHandler::OnResponseStarted( + content::ResourceResponse* response, + bool* defer) { + if (delegate_) + delegate_->OnResponseStarted(response); + return next_handler_->OnResponseStarted(response, defer); +} + +} // namespace atom diff --git a/atom/browser/loader/layered_resource_handler.h b/atom/browser/loader/layered_resource_handler.h new file mode 100644 index 0000000000..db3a974eea --- /dev/null +++ b/atom/browser/loader/layered_resource_handler.h @@ -0,0 +1,40 @@ +// 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_LOADER_LAYERED_RESOURCE_HANDLER_H_ +#define ATOM_BROWSER_LOADER_LAYERED_RESOURCE_HANDLER_H_ + +#include "content/browser/loader/layered_resource_handler.h" + +namespace atom { + +// Resource handler that notifies on various stages of a resource request. +class LayeredResourceHandler : public content::LayeredResourceHandler { + public: + class Delegate { + public: + Delegate() {} + virtual ~Delegate() {} + + virtual void OnResponseStarted(content::ResourceResponse* response) = 0; + }; + + LayeredResourceHandler(net::URLRequest* request, + std::unique_ptr next_handler, + Delegate* delegate); + ~LayeredResourceHandler() override; + + // content::LayeredResourceHandler: + bool OnResponseStarted(content::ResourceResponse* response, + bool* defer) override; + + private: + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(LayeredResourceHandler); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_LOADER_LAYERED_RESOURCE_HANDLER_H_ diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index 9e245f9907..4c6a938fba 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 0000000000..949e5e9ec9 --- /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 0000000000..4216cc1e34 --- /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 0000000000..4e7aa429ce --- /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 0000000000..2ce2adc1f4 --- /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 0000000000..08a8123bca --- /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 0000000000..5dcda13447 --- /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 1ef70f2c73..9e2c11aec4 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; } @@ -340,6 +339,17 @@ void NativeWindow::SetAutoHideCursor(bool auto_hide) { void NativeWindow::SetVibrancy(const std::string& filename) { } +void NativeWindow::SetTouchBar( + const std::vector& items) { +} + +void NativeWindow::RefreshTouchBarItem(const std::string& item_id) { +} + +void NativeWindow::SetEscapeTouchBarItem( + const mate::PersistentDictionary& item) { +} + void NativeWindow::FocusOnWebView() { web_contents()->GetRenderViewHost()->GetWidget()->Focus(); } @@ -464,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(); @@ -531,7 +546,7 @@ void NativeWindow::NotifyWindowScrollTouchBegin() { void NativeWindow::NotifyWindowScrollTouchEnd() { for (NativeWindowObserver& observer : observers_) - observer.OnWindowScrollTouchEdge(); + observer.OnWindowScrollTouchEnd(); } void NativeWindow::NotifyWindowScrollTouchEdge() { @@ -544,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(); @@ -565,6 +590,13 @@ void NativeWindow::NotifyWindowExecuteWindowsCommand( observer.OnExecuteWindowsCommand(command); } +void NativeWindow::NotifyTouchBarItemInteraction( + const std::string& item_id, + const base::DictionaryValue& details) { + for (NativeWindowObserver& observer : observers_) + observer.OnTouchBarItemResult(item_id, details); +} + #if defined(OS_WIN) void NativeWindow::NotifyWindowMessage( UINT message, WPARAM w_param, LPARAM l_param) { diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 6f2d525469..d3f18d8fb9 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -21,6 +21,7 @@ #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" #include "extensions/browser/app_window/size_constraints.h" +#include "native_mate/persistent_dictionary.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" @@ -46,6 +47,8 @@ class Dictionary; namespace atom { +class NativeBrowserView; + struct DraggableRegion; class NativeWindow : public base::SupportsUserData, @@ -124,6 +127,7 @@ class NativeWindow : public base::SupportsUserData, std::string* error = nullptr) = 0; virtual bool IsAlwaysOnTop() = 0; virtual void Center() = 0; + virtual void Invalidate() = 0; virtual void SetTitle(const std::string& title) = 0; virtual std::string GetTitle() = 0; virtual void FlashFrame(bool flash) = 0; @@ -142,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; @@ -168,6 +173,12 @@ class NativeWindow : public base::SupportsUserData, // Vibrancy API virtual void SetVibrancy(const std::string& type); + // Touchbar API + 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(); virtual void BlurWebView(); @@ -207,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(); @@ -222,11 +234,15 @@ 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(); void NotifyWindowLeaveHtmlFullScreen(); void NotifyWindowExecuteWindowsCommand(const std::string& command); + void NotifyTouchBarItemInteraction(const std::string& item_id, + const base::DictionaryValue& details); #if defined(OS_WIN) void NotifyWindowMessage(UINT message, WPARAM w_param, LPARAM l_param); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 186051200a..af0f157eca 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -71,6 +71,7 @@ class NativeWindowMac : public NativeWindow, int relativeLevel, std::string* error) override; bool IsAlwaysOnTop() override; void Center() override; + void Invalidate() override; void SetTitle(const std::string& title) override; std::string GetTitle() override; void FlashFrame(bool flash) override; @@ -86,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; @@ -99,6 +101,10 @@ class NativeWindowMac : public NativeWindow, void SetAutoHideCursor(bool auto_hide) override; void SetVibrancy(const std::string& type) override; + 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; @@ -159,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 e3cf300bba..9dc119e239 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -7,6 +7,8 @@ #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" #include "atom/common/draggable_region.h" @@ -18,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" @@ -228,7 +230,7 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)windowWillEnterFullScreen:(NSNotification*)notification { // Hide the native toolbar before entering fullscreen, so there is no visual // artifacts. - if (base::mac::IsOS10_10() && + if (base::mac::IsAtLeastOS10_10() && shell_->title_bar_style() == atom::NativeWindowMac::HIDDEN_INSET) { NSWindow* window = shell_->GetNativeWindow(); [window setToolbar:nil]; @@ -243,7 +245,7 @@ bool ScopedDisableResize::disable_resize_ = false; // have to set one, because title bar is visible here. NSWindow* window = shell_->GetNativeWindow(); if ((shell_->transparent() || !shell_->has_frame()) && - base::mac::IsOS10_10() && + base::mac::IsAtLeastOS10_10() && // FIXME(zcbenz): Showing titlebar for hiddenInset window is weird under // fullscreen mode. shell_->title_bar_style() != atom::NativeWindowMac::HIDDEN_INSET) { @@ -252,7 +254,7 @@ bool ScopedDisableResize::disable_resize_ = false; // Restore the native toolbar immediately after entering fullscreen, if we do // this before leaving fullscreen, traffic light buttons will be jumping. - if (base::mac::IsOS10_10() && + if (base::mac::IsAtLeastOS10_10() && shell_->title_bar_style() == atom::NativeWindowMac::HIDDEN_INSET) { base::scoped_nsobject toolbar( [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); @@ -269,13 +271,13 @@ bool ScopedDisableResize::disable_resize_ = false; // Restore the titlebar visibility. NSWindow* window = shell_->GetNativeWindow(); if ((shell_->transparent() || !shell_->has_frame()) && - base::mac::IsOS10_10() && + base::mac::IsAtLeastOS10_10() && shell_->title_bar_style() != atom::NativeWindowMac::HIDDEN_INSET) { [window setTitleVisibility:NSWindowTitleHidden]; } // Turn off the style for toolbar. - if (base::mac::IsOS10_10() && + if (base::mac::IsAtLeastOS10_10() && shell_->title_bar_style() == atom::NativeWindowMac::HIDDEN_INSET) { shell_->SetStyleMask(false, NSFullSizeContentViewWindowMask); } @@ -311,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 @@ -335,10 +345,24 @@ bool ScopedDisableResize::disable_resize_ = false; @end -@interface AtomNSWindow : EventDispatchingWindow { +#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_; bool enable_larger_than_screen_; + base::scoped_nsobject atom_touch_bar_; CGFloat windowButtonsInterButtonSpacing_; } @property BOOL acceptsFirstMouse; @@ -351,6 +375,10 @@ bool ScopedDisableResize::disable_resize_ = false; - (void)setShell:(atom::NativeWindowMac*)shell; - (void)setEnableLargerThanScreen:(bool)enable; - (void)enableWindowButtonsOffset; +- (void)resetTouchBar:(const std::vector&)settings; +- (void)refreshTouchBarItem:(const std::string&)item_id; +- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item; + @end @implementation AtomNSWindow @@ -363,6 +391,40 @@ bool ScopedDisableResize::disable_resize_ = false; enable_larger_than_screen_ = enable; } +- (void)resetTouchBar:(const std::vector&)settings { + if (![self respondsToSelector:@selector(touchBar)]) return; + + atom_touch_bar_.reset([[AtomTouchBar alloc] initWithDelegate:self + window:shell_ + settings:settings]); + self.touchBar = nil; +} + +- (void)refreshTouchBarItem:(const std::string&)item_id { + if (atom_touch_bar_ && self.touchBar) + [atom_touch_bar_ refreshTouchBarItem:self.touchBar id:item_id]; +} + +- (NSTouchBar*)makeTouchBar { + if (atom_touch_bar_) + return [atom_touch_bar_ makeTouchBar]; + else + return nil; +} + +- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + if (touchBar && atom_touch_bar_) + return [atom_touch_bar_ makeItemForIdentifier:identifier]; + else + 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 { @@ -618,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), @@ -648,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); @@ -712,7 +778,7 @@ NativeWindowMac::NativeWindowMac( [window_ setDisableKeyOrMainWindow:YES]; if (transparent() || !has_frame()) { - if (base::mac::IsOS10_10()) { + if (base::mac::IsAtLeastOS10_10()) { // Don't show title bar. [window_ setTitleVisibility:NSWindowTitleHidden]; } @@ -720,12 +786,24 @@ 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]; // Hide the title bar. if (title_bar_style_ == HIDDEN_INSET) { - if (base::mac::IsOS10_10()) { + if (base::mac::IsAtLeastOS10_10()) { [window_ setTitlebarAppearsTransparent:YES]; base::scoped_nsobject toolbar( [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]); @@ -1061,7 +1139,7 @@ void NativeWindowMac::SetAlwaysOnTop(bool top, const std::string& level, int windowLevel = NSNormalWindowLevel; CGWindowLevel maxWindowLevel = CGWindowLevelForKey(kCGMaximumWindowLevelKey); CGWindowLevel minWindowLevel = CGWindowLevelForKey(kCGMinimumWindowLevelKey); - + if (top) { if (level == "floating") { windowLevel = NSFloatingWindowLevel; @@ -1101,10 +1179,15 @@ void NativeWindowMac::Center() { [window_ center]; } +void NativeWindowMac::Invalidate() { + [window_ flushWindow]; + [[window_ contentView] setNeedsDisplay:YES]; +} + void NativeWindowMac::SetTitle(const std::string& title) { // For macOS <= 10.9, the setTitleVisibility API is not available, we have // to avoid calling setTitle for frameless window. - if (!base::mac::IsOS10_10() && (transparent() || !has_frame())) + if (!base::mac::IsAtLeastOS10_10() && (transparent() || !has_frame())) return; [window_ setTitle:base::SysUTF8ToNSString(title)]; @@ -1196,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; @@ -1223,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]; @@ -1275,7 +1378,7 @@ void NativeWindowMac::SetAutoHideCursor(bool auto_hide) { } void NativeWindowMac::SetVibrancy(const std::string& type) { - if (!base::mac::IsOS10_10()) return; + if (!base::mac::IsAtLeastOS10_10()) return; NSView* vibrant_view = [window_ vibrantView]; @@ -1314,33 +1417,46 @@ void NativeWindowMac::SetVibrancy(const std::string& type) { vibrancyType = NSVisualEffectMaterialTitlebar; } - if (base::mac::IsOS10_11()) { + if (base::mac::IsAtLeastOS10_11()) { // TODO(kevinsawicki): Use NSVisualEffectMaterial* constants directly once // 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); } } [effect_view setMaterial:vibrancyType]; } +void NativeWindowMac::SetTouchBar( + const std::vector& items) { + [window_ resetTouchBar:items]; +} + +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 42d6b0287f..8c908dc823 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -8,6 +8,7 @@ #include #include "base/strings/string16.h" +#include "base/values.h" #include "ui/base/window_open_disposition.h" #include "url/gurl.h" @@ -39,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() {} @@ -66,10 +70,14 @@ 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() {} virtual void OnWindowLeaveHtmlFullScreen() {} + virtual void OnTouchBarItemResult(const std::string& item_id, + const base::DictionaryValue& details) {} // Called when window message received #if defined(OS_WIN) diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 3270add583..7e6f23947c 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" @@ -48,6 +48,7 @@ #include "ui/views/window/native_frame_view.h" #elif defined(OS_WIN) #include "atom/browser/ui/views/win_frame_view.h" +#include "atom/browser/ui/win/atom_desktop_native_widget_aura.h" #include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h" #include "skia/ext/skia_utils_win.h" #include "ui/base/win/shell.h" @@ -134,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), @@ -204,8 +206,7 @@ NativeWindowViews::NativeWindowViews( if (parent) params.parent = parent->GetNativeWindow(); - params.native_widget = - new views::DesktopNativeWidgetAura(window_.get()); + params.native_widget = new AtomDesktopNativeWidgetAura(window_.get()); atom_desktop_window_tree_host_win_ = new AtomDesktopWindowTreeHostWin( this, window_.get(), @@ -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) @@ -695,6 +693,10 @@ void NativeWindowViews::Center() { window_->CenterWindow(GetSize()); } +void NativeWindowViews::Invalidate() { + window_->SchedulePaintInRect(gfx::Rect(GetBounds().size())); +} + void NativeWindowViews::SetTitle(const std::string& title) { title_ = title; window_->UpdateWindowTitle(); @@ -877,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); @@ -1244,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 7d983b89a5..276cd4adde 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -90,6 +90,7 @@ class NativeWindowViews : public NativeWindow, int relativeLevel, std::string* error) override; bool IsAlwaysOnTop() override; void Center() override; + void Invalidate() override; void SetTitle(const std::string& title) override; std::string GetTitle() override; void FlashFrame(bool flash) override; @@ -103,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, @@ -175,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; @@ -188,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 1b523e90b8..abda0d0b02 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 61c7439e27..2a2229f19d 100644 --- a/atom/browser/net/atom_cert_verifier.cc +++ b/atom/browser/net/atom_cert_verifier.cc @@ -7,6 +7,9 @@ #include "atom/browser/browser.h" #include "atom/browser/net/atom_ct_delegate.h" #include "atom/common/native_mate_converters/net_converter.h" +#include "base/containers/linked_list.h" +#include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" #include "content/public/browser/browser_thread.h" #include "net/base/net_errors.h" #include "net/cert/cert_verify_result.h" @@ -19,17 +22,130 @@ namespace atom { namespace { -void OnResult( - net::CertVerifyResult* verify_result, - const net::CompletionCallback& callback, - bool result) { - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - base::Bind(callback, result ? net::OK : net::ERR_FAILED)); -} +class Response : public base::LinkNode { + public: + Response(net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback) + : verify_result_(verify_result), callback_(callback) {} + net::CertVerifyResult* verify_result() { return verify_result_; } + net::CompletionCallback callback() { return callback_; } + + private: + net::CertVerifyResult* verify_result_; + net::CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(Response); +}; } // namespace +class CertVerifierRequest : public AtomCertVerifier::Request { + public: + CertVerifierRequest(const AtomCertVerifier::RequestParams& params, + AtomCertVerifier* cert_verifier) + : params_(params), + cert_verifier_(cert_verifier), + error_(net::ERR_IO_PENDING), + custom_response_(net::ERR_IO_PENDING), + first_response_(true), + weak_ptr_factory_(this) {} + + ~CertVerifierRequest() override { + cert_verifier_->RemoveRequest(params_); + default_verifier_request_.reset(); + while (!response_list_.empty() && !first_response_) { + base::LinkNode* response_node = response_list_.head(); + response_node->RemoveFromList(); + Response* response = response_node->value(); + RunResponse(response); + } + cert_verifier_ = nullptr; + weak_ptr_factory_.InvalidateWeakPtrs(); + } + + void RunResponse(Response* response) { + if (custom_response_ == net::ERR_ABORTED) { + *(response->verify_result()) = result_; + response->callback().Run(error_); + } else { + response->verify_result()->Reset(); + response->verify_result()->verified_cert = params_.certificate(); + cert_verifier_->ct_delegate()->AddCTExcludedHost(params_.hostname()); + response->callback().Run(custom_response_); + } + delete response; + } + + void Start(net::CRLSet* crl_set, + const net::NetLogWithSource& net_log) { + int error = cert_verifier_->default_verifier()->Verify( + params_, crl_set, &result_, + base::Bind(&CertVerifierRequest::OnDefaultVerificationDone, + weak_ptr_factory_.GetWeakPtr()), + &default_verifier_request_, net_log); + if (error != net::ERR_IO_PENDING) + OnDefaultVerificationDone(error); + } + + void OnDefaultVerificationDone(int error) { + error_ = error; + std::unique_ptr request(new VerifyRequestParams()); + request->hostname = params_.hostname(); + request->default_result = net::ErrorToString(error); + request->certificate = params_.certificate(); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&CertVerifierRequest::OnVerifyRequestInUI, + weak_ptr_factory_.GetWeakPtr(), + cert_verifier_->verify_proc(), + base::Passed(&request))); + } + + void OnVerifyRequestInUI(const AtomCertVerifier::VerifyProc& verify_proc, + std::unique_ptr request) { + verify_proc.Run(*(request.get()), + base::Bind(&CertVerifierRequest::OnResponseInUI, + weak_ptr_factory_.GetWeakPtr())); + } + + void OnResponseInUI(int result) { + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&CertVerifierRequest::NotifyResponseInIO, + weak_ptr_factory_.GetWeakPtr(), result)); + } + + void NotifyResponseInIO(int result) { + custom_response_ = result; + first_response_ = false; + // Responding to first request in the list will initiate destruction of + // the class, respond to others in the list inside destructor. + base::LinkNode* response_node = response_list_.head(); + response_node->RemoveFromList(); + Response* response = response_node->value(); + RunResponse(response); + } + + void AddResponseListener(net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback) { + response_list_.Append(new Response(verify_result, callback)); + } + + const AtomCertVerifier::RequestParams& params() const { return params_; } + + private: + using ResponseList = base::LinkedList; + + const AtomCertVerifier::RequestParams params_; + AtomCertVerifier* cert_verifier_; + int error_; + int custom_response_; + bool first_response_; + ResponseList response_list_; + net::CertVerifyResult result_; + std::unique_ptr default_verifier_request_; + base::WeakPtrFactory weak_ptr_factory_; +}; + AtomCertVerifier::AtomCertVerifier(AtomCTDelegate* ct_delegate) : default_cert_verifier_(net::CertVerifier::CreateDefault()), ct_delegate_(ct_delegate) {} @@ -51,23 +167,43 @@ int AtomCertVerifier::Verify( if (verify_proc_.is_null()) { ct_delegate_->ClearCTExcludedHostsList(); - return default_cert_verifier_->Verify( - params, crl_set, verify_result, callback, out_req, net_log); + return default_cert_verifier_->Verify(params, crl_set, verify_result, + callback, out_req, net_log); + } else { + CertVerifierRequest* request = FindRequest(params); + if (!request) { + out_req->reset(); + std::unique_ptr new_request = + base::MakeUnique(params, this); + new_request->Start(crl_set, net_log); + request = new_request.get(); + *out_req = std::move(new_request); + inflight_requests_[params] = request; + } + request->AddResponseListener(verify_result, callback); + + return net::ERR_IO_PENDING; } - - verify_result->Reset(); - verify_result->verified_cert = params.certificate(); - ct_delegate_->AddCTExcludedHost(params.hostname()); - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(verify_proc_, params.hostname(), params.certificate(), - base::Bind(OnResult, verify_result, callback))); - return net::ERR_IO_PENDING; } bool AtomCertVerifier::SupportsOCSPStapling() { - return true; + if (verify_proc_.is_null()) + return default_cert_verifier_->SupportsOCSPStapling(); + return false; +} + +void AtomCertVerifier::RemoveRequest(const RequestParams& params) { + auto it = inflight_requests_.find(params); + if (it != inflight_requests_.end()) + inflight_requests_.erase(it); +} + +CertVerifierRequest* AtomCertVerifier::FindRequest( + const RequestParams& params) { + auto it = inflight_requests_.find(params); + if (it != inflight_requests_.end()) + return it->second; + return nullptr; } } // namespace atom diff --git a/atom/browser/net/atom_cert_verifier.h b/atom/browser/net/atom_cert_verifier.h index e321f7d3d9..09fa0f2778 100644 --- a/atom/browser/net/atom_cert_verifier.h +++ b/atom/browser/net/atom_cert_verifier.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_NET_ATOM_CERT_VERIFIER_H_ #define ATOM_BROWSER_NET_ATOM_CERT_VERIFIER_H_ +#include #include #include @@ -13,19 +14,30 @@ namespace atom { class AtomCTDelegate; +class CertVerifierRequest; + +struct VerifyRequestParams { + std::string hostname; + std::string default_result; + scoped_refptr certificate; +}; class AtomCertVerifier : public net::CertVerifier { public: explicit AtomCertVerifier(AtomCTDelegate* ct_delegate); virtual ~AtomCertVerifier(); - using VerifyProc = - base::Callback, - const base::Callback&)>; + using VerifyProc = base::Callback; void SetVerifyProc(const VerifyProc& proc); + const VerifyProc verify_proc() const { return verify_proc_; } + AtomCTDelegate* ct_delegate() const { return ct_delegate_; } + net::CertVerifier* default_verifier() const { + return default_cert_verifier_.get(); + } + protected: // net::CertVerifier: int Verify(const RequestParams& params, @@ -37,6 +49,12 @@ class AtomCertVerifier : public net::CertVerifier { bool SupportsOCSPStapling() override; private: + friend class CertVerifierRequest; + + void RemoveRequest(const RequestParams& params); + CertVerifierRequest* FindRequest(const RequestParams& params); + + std::map inflight_requests_; VerifyProc verify_proc_; std::unique_ptr default_cert_verifier_; AtomCTDelegate* ct_delegate_; diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 2ba3f8e357..3a5b667f06 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 2c7bb61da0..9400f361f1 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 db00390b95..654798d8aa 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 e95369fba1..9fdeb6099e 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 7355f1a0c1..2003118f38 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 664261947d..7cf010ff8e 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 55007cd987..673bceb38f 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,9 +17,9 @@ CFBundleIconFile electron.icns CFBundleVersion - 1.5.1 + 1.6.8 CFBundleShortVersionString - 1.5.1 + 1.6.8 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/atom/browser/resources/win/atom.manifest b/atom/browser/resources/win/atom.manifest index 64c07ded17..7608ffb20f 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 4220927a91..bc9d66a580 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,5,1,0 - PRODUCTVERSION 1,5,1,0 + FILEVERSION 1,6,8,0 + PRODUCTVERSION 1,6,8,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "1.5.1" + VALUE "FileVersion", "1.6.8" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "1.5.1" + VALUE "ProductVersion", "1.6.8" 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 0000000000..7cbf31ea41 --- /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 0000000000..e0888dd3ea --- /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/certificate_trust_win.cc b/atom/browser/ui/certificate_trust_win.cc new file mode 100644 index 0000000000..06c0ffd7f1 --- /dev/null +++ b/atom/browser/ui/certificate_trust_win.cc @@ -0,0 +1,98 @@ +// 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" + +#include +#include + +#include "base/callback.h" +#include "net/cert/cert_database.h" + +namespace certificate_trust { + +// Add the provided certificate to the Trusted Root Certificate Authorities +// store for the current user. +// +// This requires prompting the user to confirm they trust the certificate. +BOOL AddToTrustedRootStore(const PCCERT_CONTEXT cert_context, + const scoped_refptr& cert) { + auto root_cert_store = CertOpenStore( + CERT_STORE_PROV_SYSTEM, + 0, + NULL, + CERT_SYSTEM_STORE_CURRENT_USER, + L"Root"); + + if (root_cert_store == NULL) { + return false; + } + + auto result = CertAddCertificateContextToStore( + root_cert_store, + cert_context, + CERT_STORE_ADD_REPLACE_EXISTING, + NULL); + + if (result) { + // force Chromium to reload it's database for this certificate + auto cert_db = net::CertDatabase::GetInstance(); + cert_db->NotifyObserversCertDBChanged(cert.get()); + } + + CertCloseStore(root_cert_store, CERT_CLOSE_STORE_FORCE_FLAG); + + return result; +} + +CERT_CHAIN_PARA GetCertificateChainParameters() { + CERT_ENHKEY_USAGE enhkey_usage; + enhkey_usage.cUsageIdentifier = 0; + enhkey_usage.rgpszUsageIdentifier = NULL; + + CERT_USAGE_MATCH cert_usage; + // ensure the rules are applied to the entire chain + cert_usage.dwType = USAGE_MATCH_TYPE_AND; + cert_usage.Usage = enhkey_usage; + + CERT_CHAIN_PARA params = { sizeof(CERT_CHAIN_PARA) }; + params.RequestedUsage = cert_usage; + + return params; +} + +void ShowCertificateTrust(atom::NativeWindow* parent_window, + const scoped_refptr& cert, + const std::string& message, + const ShowTrustCallback& callback) { + PCCERT_CHAIN_CONTEXT chain_context; + + auto cert_context = cert->CreateOSCertChainForCert(); + + auto params = GetCertificateChainParameters(); + + if (CertGetCertificateChain(NULL, + cert_context, + NULL, + NULL, + ¶ms, + NULL, + NULL, + &chain_context)) { + auto error_status = chain_context->TrustStatus.dwErrorStatus; + if (error_status == CERT_TRUST_IS_SELF_SIGNED || + error_status == CERT_TRUST_IS_UNTRUSTED_ROOT) { + // these are the only scenarios we're interested in supporting + AddToTrustedRootStore(cert_context, cert); + } + + CertFreeCertificateChain(chain_context); + } + + CertFreeCertificateContext(cert_context); + + callback.Run(); +} + +} // namespace certificate_trust diff --git a/atom/browser/ui/cocoa/atom_menu_controller.h b/atom/browser/ui/cocoa/atom_menu_controller.h index af0b276961..a230437f53 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.h +++ b/atom/browser/ui/cocoa/atom_menu_controller.h @@ -8,6 +8,7 @@ #import +#include "base/callback.h" #include "base/mac/scoped_nsobject.h" #include "base/strings/string16.h" @@ -27,6 +28,7 @@ class AtomMenuModel; base::scoped_nsobject menu_; BOOL isMenuOpen_; BOOL useDefaultAccelerator_; + base::Callback closeCallback; } @property(nonatomic, assign) atom::AtomMenuModel* model; @@ -35,6 +37,8 @@ class AtomMenuModel; // to the contents of the model after calling this will not be noticed. - (id)initWithModel:(atom::AtomMenuModel*)model useDefaultAccelerator:(BOOL)use; +- (void)setCloseCallback:(const base::Callback&)callback; + // Populate current NSMenu with |model|. - (void)populateWithModel:(atom::AtomMenuModel*)model; diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index 6bdaa4c780..d0bbf6a153 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -12,9 +12,12 @@ #include "ui/base/accelerators/accelerator.h" #include "ui/base/accelerators/platform_accelerator_cocoa.h" #include "ui/base/l10n/l10n_util_mac.h" +#include "content/public/browser/browser_thread.h" #include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/gfx/image/image.h" +using content::BrowserThread; + namespace { struct Role { @@ -67,10 +70,14 @@ Role kRolesMap[] = { // while its context menu is still open. [self cancel]; - model_ = NULL; + model_ = nullptr; [super dealloc]; } +- (void)setCloseCallback:(const base::Callback&)callback { + closeCallback = callback; +} + - (void)populateWithModel:(atom::AtomMenuModel*)model { if (!menu_) return; @@ -265,8 +272,13 @@ Role kRolesMap[] = { - (void)menuDidClose:(NSMenu*)menu { if (isMenuOpen_) { - model_->MenuWillClose(); isMenuOpen_ = NO; + model_->MenuWillClose(); + + // Post async task so that itemSelected runs before the close callback + // deletes the controller from the map which deallocates it + if (!closeCallback.is_null()) + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, closeCallback); } } diff --git a/atom/browser/ui/cocoa/atom_touch_bar.h b/atom/browser/ui/cocoa/atom_touch_bar.h new file mode 100644 index 0000000000..26662cf485 --- /dev/null +++ b/atom/browser/ui/cocoa/atom_touch_bar.h @@ -0,0 +1,66 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_ +#define ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_ + +#import + +#include +#include +#include + +#include "atom/browser/native_window.h" +#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h" +#include "base/mac/scoped_nsobject.h" +#include "native_mate/constructor.h" +#include "native_mate/persistent_dictionary.h" + +@interface AtomTouchBar : NSObject { + @protected + std::vector ordered_settings_; + std::map settings_; + id delegate_; + atom::NativeWindow* window_; +} + +- (id)initWithDelegate:(id)delegate window:(atom::NativeWindow*)window settings:(const std::vector&)settings; + +- (NSTouchBar*)makeTouchBar; +- (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; +- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id type:(const std::string&)typere; +- (bool)hasItemWithID:(const std::string&)item_id; +- (NSColor*)colorFromHexColorString:(const std::string&)colorString; + +// Selector actions +- (void)buttonAction:(id)sender; +- (void)colorPickerAction:(id)sender; +- (void)sliderAction:(id)sender; + +// Helpers to create touch bar items +- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier; +- (NSTouchBarItem*)makeButtonForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeLabelForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeSliderForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makePopoverForID:(NSString*)id withIdentifier:(NSString*)identifier; +- (NSTouchBarItem*)makeGroupForID:(NSString*)id withIdentifier:(NSString*)identifier; + +// Helpers to update touch bar items +- (void)updateButton:(NSCustomTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updateLabel:(NSCustomTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updateSlider:(NSSliderTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; +- (void)updatePopover:(NSPopoverTouchBarItem*)item withSettings:(const mate::PersistentDictionary&)settings; + +@end + +#endif // ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_ diff --git a/atom/browser/ui/cocoa/atom_touch_bar.mm b/atom/browser/ui/cocoa/atom_touch_bar.mm new file mode 100644 index 0000000000..30a3e6643f --- /dev/null +++ b/atom/browser/ui/cocoa/atom_touch_bar.mm @@ -0,0 +1,669 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#import "atom/browser/ui/cocoa/atom_touch_bar.h" + +#include "atom/common/color_util.h" +#include "atom/common/native_mate_converters/image_converter.h" +#include "base/strings/sys_string_conversions.h" +#include "skia/ext/skia_utils_mac.h" +#include "ui/gfx/image/image.h" + +@implementation AtomTouchBar + +static NSTouchBarItemIdentifier ButtonIdentifier = @"com.electron.touchbar.button."; +static NSTouchBarItemIdentifier ColorPickerIdentifier = @"com.electron.touchbar.colorpicker."; +static NSTouchBarItemIdentifier GroupIdentifier = @"com.electron.touchbar.group."; +static NSTouchBarItemIdentifier LabelIdentifier = @"com.electron.touchbar.label."; +static NSTouchBarItemIdentifier PopoverIdentifier = @"com.electron.touchbar.popover."; +static NSTouchBarItemIdentifier SliderIdentifier = @"com.electron.touchbar.slider."; +static NSTouchBarItemIdentifier SegmentedControlIdentifier = @"com.electron.touchbar.segmentedcontrol."; +static NSTouchBarItemIdentifier ScrubberIdentifier = @"com.electron.touchbar.scrubber."; + +static NSString* const TextScrubberItemIdentifier = @"scrubber.text.item"; +static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item"; + +- (id)initWithDelegate:(id)delegate + window:(atom::NativeWindow*)window + settings:(const std::vector&)settings { + if ((self = [super init])) { + delegate_ = delegate; + window_ = window; + ordered_settings_ = settings; + } + return self; +} + +- (NSTouchBar*)makeTouchBar { + NSMutableArray* identifiers = [self identifiersFromSettings:ordered_settings_]; + return [self touchBarFromItemIdentifiers:identifiers]; +} + +- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items { + base::scoped_nsobject bar( + [[NSClassFromString(@"NSTouchBar") alloc] init]); + [bar setDelegate:delegate_]; + [bar setDefaultItemIdentifiers:items]; + return bar.autorelease(); +} + +- (NSMutableArray*)identifiersFromSettings:(const std::vector&)dicts { + NSMutableArray* identifiers = [NSMutableArray array]; + + for (const auto& item : dicts) { + std::string type; + std::string item_id; + if (item.Get("type", &type) && item.Get("id", &item_id)) { + NSTouchBarItemIdentifier identifier = nil; + if (type == "spacer") { + std::string size; + item.Get("size", &size); + if (size == "large") { + identifier = NSTouchBarItemIdentifierFixedSpaceLarge; + } else if (size == "flexible") { + identifier = NSTouchBarItemIdentifierFlexibleSpace; + } else { + identifier = NSTouchBarItemIdentifierFixedSpaceSmall; + } + } else { + identifier = [self identifierFromID:item_id type:type]; + } + + if (identifier) { + settings_[item_id] = item; + [identifiers addObject:identifier]; + } + } + } + [identifiers addObject:NSTouchBarItemIdentifierOtherItemsProxy]; + + return identifiers; +} + +- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier { + NSString* item_id = nil; + + if ([identifier hasPrefix:ButtonIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier]; + return [self makeButtonForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:LabelIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier]; + return [self makeLabelForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:ColorPickerIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:ColorPickerIdentifier]; + return [self makeColorPickerForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:SliderIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier]; + return [self makeSliderForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:PopoverIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:PopoverIdentifier]; + return [self makePopoverForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:GroupIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier]; + return [self makeGroupForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:SegmentedControlIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:SegmentedControlIdentifier]; + return [self makeSegmentedControlForID:item_id withIdentifier:identifier]; + } else if ([identifier hasPrefix:ScrubberIdentifier]) { + item_id = [self idFromIdentifier:identifier withPrefix:ScrubberIdentifier]; + return [self makeScrubberForID:item_id withIdentifier:identifier]; + } + + return nil; +} + +- (void)refreshTouchBarItem:(NSTouchBar*)touchBar + id:(NSTouchBarItemIdentifier)identifier + withType:(const std::string&)item_type + withSettings:(const mate::PersistentDictionary&)settings { + NSTouchBarItem* item = [touchBar itemForIdentifier:identifier]; + if (!item) return; + + if (item_type == "button") { + [self updateButton:(NSCustomTouchBarItem*)item withSettings:settings]; + } else if (item_type == "label") { + [self updateLabel:(NSCustomTouchBarItem*)item withSettings:settings]; + } else if (item_type == "colorpicker") { + [self updateColorPicker:(NSColorPickerTouchBarItem*)item + withSettings:settings]; + } else if (item_type == "slider") { + [self updateSlider:(NSSliderTouchBarItem*)item withSettings:settings]; + } else if (item_type == "popover") { + [self updatePopover:(NSPopoverTouchBarItem*)item withSettings:settings]; + } else if (item_type == "segmented_control") { + [self updateSegmentedControl:(NSCustomTouchBarItem*)item withSettings:settings]; + } 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 { + NSString* item_id = [NSString stringWithFormat:@"%ld", ((NSButton*)sender).tag]; + window_->NotifyTouchBarItemInteraction([item_id UTF8String], + base::DictionaryValue()); +} + +- (void)colorPickerAction:(id)sender { + NSString* identifier = ((NSColorPickerTouchBarItem*)sender).identifier; + NSString* item_id = [self idFromIdentifier:identifier + withPrefix:ColorPickerIdentifier]; + NSColor* color = ((NSColorPickerTouchBarItem*)sender).color; + std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color)); + base::DictionaryValue details; + details.SetString("color", hex_color); + window_->NotifyTouchBarItemInteraction([item_id UTF8String], details); +} + +- (void)sliderAction:(id)sender { + NSString* identifier = ((NSSliderTouchBarItem*)sender).identifier; + NSString* item_id = [self idFromIdentifier:identifier + withPrefix:SliderIdentifier]; + base::DictionaryValue details; + details.SetInteger("value", + [((NSSliderTouchBarItem*)sender).slider intValue]); + window_->NotifyTouchBarItemInteraction([item_id UTF8String], details); +} + +- (NSString*)idFromIdentifier:(NSString*)identifier + withPrefix:(NSString*)prefix { + return [identifier substringFromIndex:[prefix length]]; +} + +- (void)segmentedControlAction:(id)sender { + NSString* item_id = [NSString stringWithFormat:@"%ld", ((NSSegmentedControl*)sender).tag]; + base::DictionaryValue details; + details.SetInteger("selectedIndex", ((NSSegmentedControl*)sender).selectedSegment); + details.SetBoolean("isSelected", [((NSSegmentedControl*)sender) isSelectedForSegment:((NSSegmentedControl*)sender).selectedSegment]); + window_->NotifyTouchBarItemInteraction([item_id UTF8String], + details); +} + +- (void)scrubber:(NSScrubber*)scrubber didSelectItemAtIndex:(NSInteger)selectedIndex { + base::DictionaryValue details; + details.SetInteger("selectedIndex", selectedIndex); + details.SetString("type", "select"); + window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String], details); +} + +- (void)scrubber:(NSScrubber*)scrubber didHighlightItemAtIndex:(NSInteger)highlightedIndex { + base::DictionaryValue details; + details.SetInteger("highlightedIndex", highlightedIndex); + details.SetString("type", "highlight"); + window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String], details); +} + +- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id + type:(const std::string&)type { + NSTouchBarItemIdentifier base_identifier = nil; + if (type == "button") + base_identifier = ButtonIdentifier; + else if (type == "label") + base_identifier = LabelIdentifier; + else if (type == "colorpicker") + base_identifier = ColorPickerIdentifier; + else if (type == "slider") + base_identifier = SliderIdentifier; + else if (type == "popover") + base_identifier = PopoverIdentifier; + else if (type == "group") + base_identifier = GroupIdentifier; + else if (type == "segmented_control") + base_identifier = SegmentedControlIdentifier; + else if (type == "scrubber") + base_identifier = ScrubberIdentifier; + + if (base_identifier) + return [NSString stringWithFormat:@"%@%s", base_identifier, item_id.data()]; + else + return nil; +} + +- (bool)hasItemWithID:(const std::string&)item_id { + return settings_.find(item_id) != settings_.end(); +} + +- (NSColor*)colorFromHexColorString:(const std::string&)colorString { + SkColor color = atom::ParseHexColor(colorString); + return skia::SkColorToDeviceNSColor(color); +} + +- (NSTouchBarItem*)makeButtonForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); + NSButton* button = [NSButton buttonWithTitle:@"" + target:self + action:@selector(buttonAction:)]; + button.tag = [id floatValue]; + [item setView:button]; + [self updateButton:item withSettings:settings]; + return item.autorelease(); +} + +- (void)updateButton:(NSCustomTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + NSButton* button = (NSButton*)item.view; + + std::string backgroundColor; + if (settings.Get("backgroundColor", &backgroundColor)) { + button.bezelColor = [self colorFromHexColorString:backgroundColor]; + } + + std::string label; + settings.Get("label", &label); + button.title = base::SysUTF8ToNSString(label); + + 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; + } + } +} + +- (NSTouchBarItem*)makeLabelForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); + [item setView:[NSTextField labelWithString:@""]]; + [self updateLabel:item withSettings:settings]; + return item.autorelease(); +} + +- (void)updateLabel:(NSCustomTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + NSTextField* text_field = (NSTextField*)item.view; + + std::string label; + settings.Get("label", &label); + text_field.stringValue = base::SysUTF8ToNSString(label); + + std::string textColor; + if (settings.Get("textColor", &textColor) && !textColor.empty()) { + text_field.textColor = [self colorFromHexColorString:textColor]; + } else { + text_field.textColor = nil; + } +} + +- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]); + [item setTarget:self]; + [item setAction:@selector(colorPickerAction:)]; + [self updateColorPicker:item withSettings:settings]; + return item.autorelease(); +} + +- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + std::vector colors; + 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]] + key:base::SysUTF8ToNSString(colors[i]) + atIndex:i]; + } + item.colorList = color_list; + } + + std::string selectedColor; + if (settings.Get("selectedColor", &selectedColor)) { + item.color = [self colorFromHexColorString:selectedColor]; + } +} + +- (NSTouchBarItem*)makeSliderForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]); + [item setTarget:self]; + [item setAction:@selector(sliderAction:)]; + [self updateSlider:item withSettings:settings]; + return item.autorelease(); +} + +- (void)updateSlider:(NSSliderTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + std::string label; + settings.Get("label", &label); + item.label = base::SysUTF8ToNSString(label); + + int maxValue = 100; + int minValue = 0; + int value = 50; + settings.Get("minValue", &minValue); + settings.Get("maxValue", &maxValue); + settings.Get("value", &value); + + item.slider.minValue = minValue; + item.slider.maxValue = maxValue; + item.slider.doubleValue = value; +} + +- (NSTouchBarItem*)makePopoverForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]); + [self updatePopover:item withSettings:settings]; + return item.autorelease(); +} + +- (void)updatePopover:(NSPopoverTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + std::string label; + settings.Get("label", &label); + item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label); + + gfx::Image image; + if (settings.Get("icon", &image)) { + item.collapsedRepresentationImage = image.AsNSImage(); + } + + bool showCloseButton = true; + settings.Get("showCloseButton", &showCloseButton); + item.showsCloseButton = showCloseButton; + + mate::PersistentDictionary child; + std::vector items; + if (settings.Get("child", &child) && child.Get("ordereredItems", &items)) { + item.popoverTouchBar = [self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]]; + } +} + +- (NSTouchBarItem*)makeGroupForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + mate::PersistentDictionary settings = settings_[s_id]; + + mate::PersistentDictionary child; + if (!settings.Get("child", &child)) return nil; + std::vector items; + if (!child.Get("ordereredItems", &items)) return nil; + + NSMutableArray* generatedItems = [NSMutableArray array]; + NSMutableArray* identifiers = [self identifiersFromSettings:items]; + for (NSUInteger i = 0; i < [identifiers count]; ++i) { + if ([identifiers objectAtIndex:i] != NSTouchBarItemIdentifierOtherItemsProxy) { + NSTouchBarItem* generatedItem = [self makeItemForIdentifier:[identifiers objectAtIndex:i]]; + if (generatedItem) { + [generatedItems addObject:generatedItem]; + } + } + } + return [NSClassFromString(@"NSGroupTouchBarItem") groupItemWithIdentifier:identifier + items:generatedItems]; +} + +- (NSTouchBarItem*)makeSegmentedControlForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); + + NSSegmentedControl* control = [NSSegmentedControl segmentedControlWithLabels:[NSMutableArray array] + trackingMode:NSSegmentSwitchTrackingSelectOne + target:self + action:@selector(segmentedControlAction:)]; + control.tag = [id floatValue]; + [item setView:control]; + + [self updateSegmentedControl:item withSettings:settings]; + return item.autorelease(); +} + +- (void)updateSegmentedControl:(NSCustomTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + + NSSegmentedControl* control = item.view; + + std::string segmentStyle; + settings.Get("segmentStyle", &segmentStyle); + if (segmentStyle == "rounded") + control.segmentStyle = NSSegmentStyleRounded; + else if (segmentStyle == "textured-rounded") + control.segmentStyle = NSSegmentStyleTexturedRounded; + else if (segmentStyle == "round-rect") + control.segmentStyle = NSSegmentStyleRoundRect; + else if (segmentStyle == "textured-square") + control.segmentStyle = NSSegmentStyleTexturedSquare; + else if (segmentStyle == "capsule") + control.segmentStyle = NSSegmentStyleCapsule; + else if (segmentStyle == "small-square") + control.segmentStyle = NSSegmentStyleSmallSquare; + else if (segmentStyle == "separated") + control.segmentStyle = NSSegmentStyleSeparated; + else + control.segmentStyle = NSSegmentStyleAutomatic; + + std::string segmentMode; + settings.Get("mode", &segmentMode); + if (segmentMode == "multiple") + control.trackingMode = NSSegmentSwitchTrackingSelectAny; + else if (segmentMode == "buttons") + control.trackingMode = NSSegmentSwitchTrackingMomentary; + else + control.trackingMode = NSSegmentSwitchTrackingSelectOne; + + std::vector segments; + settings.Get("segments", &segments); + + control.segmentCount = segments.size(); + for (size_t i = 0; i < segments.size(); ++i) { + std::string label; + gfx::Image image; + bool enabled = true; + segments[i].Get("enabled", &enabled); + if (segments[i].Get("label", &label)) { + [control setLabel:base::SysUTF8ToNSString(label) forSegment:i]; + } else if (segments[i].Get("icon", &image)) { + [control setImage:image.AsNSImage() forSegment:i]; + [control setImageScaling:NSImageScaleProportionallyUpOrDown forSegment:i]; + } + [control setEnabled:enabled forSegment:i]; + } + + int selectedIndex = 0; + settings.Get("selectedIndex", &selectedIndex); + if (selectedIndex >= 0 && selectedIndex < control.segmentCount) + control.selectedSegment = selectedIndex; +} + +- (NSTouchBarItem*)makeScrubberForID:(NSString*)id + withIdentifier:(NSString*)identifier { + std::string s_id([id UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + base::scoped_nsobject item([[NSClassFromString( + @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); + + NSScrubber* scrubber = [[[NSClassFromString(@"NSScrubber") alloc] initWithFrame:NSZeroRect] autorelease]; + + [scrubber registerClass:NSClassFromString(@"NSScrubberTextItemView") forItemIdentifier:TextScrubberItemIdentifier]; + [scrubber registerClass:NSClassFromString(@"NSScrubberImageItemView") forItemIdentifier:ImageScrubberItemIdentifier]; + + scrubber.delegate = self; + scrubber.dataSource = self; + scrubber.identifier = id; + + [item setView:scrubber]; + [self updateScrubber:item withSettings:settings]; + + return item.autorelease(); +} + +- (void)updateScrubber:(NSCustomTouchBarItem*)item + withSettings:(const mate::PersistentDictionary&)settings { + NSScrubber* scrubber = item.view; + + bool showsArrowButtons = false; + settings.Get("showArrowButtons", &showsArrowButtons); + scrubber.showsArrowButtons = showsArrowButtons; + + std::string selectedStyle; + std::string overlayStyle; + settings.Get("selectedStyle", &selectedStyle); + settings.Get("overlayStyle", &overlayStyle); + + if (selectedStyle == "outline") { + scrubber.selectionBackgroundStyle = [NSClassFromString(@"NSScrubberSelectionStyle") outlineOverlayStyle]; + } else if (selectedStyle == "background") { + scrubber.selectionBackgroundStyle = [NSClassFromString(@"NSScrubberSelectionStyle") roundedBackgroundStyle]; + } else { + scrubber.selectionBackgroundStyle = nil; + } + + if (overlayStyle == "outline") { + scrubber.selectionOverlayStyle = [NSClassFromString(@"NSScrubberSelectionStyle") outlineOverlayStyle]; + } else if (overlayStyle == "background") { + scrubber.selectionOverlayStyle = [NSClassFromString(@"NSScrubberSelectionStyle") roundedBackgroundStyle]; + } else { + scrubber.selectionOverlayStyle = nil; + } + + std::string mode; + settings.Get("mode", &mode); + if (mode == "fixed") { + scrubber.mode = NSScrubberModeFixed; + } else { + scrubber.mode = NSScrubberModeFree; + } + + bool continuous = true; + settings.Get("continuous", &continuous); + scrubber.continuous = continuous; + + [scrubber reloadData]; +} + +- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)scrubber { + std::string s_id([[scrubber identifier] UTF8String]); + if (![self hasItemWithID:s_id]) return 0; + + mate::PersistentDictionary settings = settings_[s_id]; + std::vector items; + settings.Get("items", &items); + return items.size(); +} + +- (NSScrubberItemView*)scrubber:(NSScrubber*)scrubber + viewForItemAtIndex:(NSInteger)index { + std::string s_id([[scrubber identifier] UTF8String]); + if (![self hasItemWithID:s_id]) return nil; + + mate::PersistentDictionary settings = settings_[s_id]; + std::vector items; + if (!settings.Get("items", &items)) return nil; + + if (index >= static_cast(items.size())) return nil; + + mate::PersistentDictionary item = items[index]; + + NSScrubberItemView* itemView; + std::string title; + + if (item.Get("label", &title)) { + NSScrubberTextItemView* view = [scrubber makeItemWithIdentifier:TextScrubberItemIdentifier + owner:self]; + view.title = base::SysUTF8ToNSString(title); + itemView = view; + } else { + NSScrubberImageItemView* view = [scrubber makeItemWithIdentifier:ImageScrubberItemIdentifier + owner:self]; + gfx::Image image; + if (item.Get("icon", &image)) { + view.image = image.AsNSImage(); + } + itemView = view; + } + + return itemView; +} + +@end diff --git a/atom/browser/ui/cocoa/touch_bar_forward_declarations.h b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h new file mode 100644 index 0000000000..6fe7c820a1 --- /dev/null +++ b/atom/browser/ui/cocoa/touch_bar_forward_declarations.h @@ -0,0 +1,251 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_ +#define ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_ + +// Once Chrome no longer supports OSX 10.12.0, this file can be deleted. + +#import + +#if !defined(MAC_OS_X_VERSION_10_12_1) + +#pragma clang assume_nonnull begin + +@class NSTouchBar, NSTouchBarItem; +@class NSScrubber, NSScrubberItemView, NSScrubberArrangedView, NSScrubberTextItemView, NSScrubberImageItemView, NSScrubberSelectionStyle; +@protocol NSTouchBarDelegate, NSScrubberDelegate, NSScrubberDataSource; + +typedef float NSTouchBarItemPriority; +static const NSTouchBarItemPriority NSTouchBarItemPriorityHigh = 1000; +static const NSTouchBarItemPriority NSTouchBarItemPriorityNormal = 0; +static const NSTouchBarItemPriority NSTouchBarItemPriorityLow = -1000; + +enum NSScrubberMode { + NSScrubberModeFixed = 0, + NSScrubberModeFree +}; + +typedef NSString* NSTouchBarItemIdentifier; +typedef NSString* NSTouchBarCustomizationIdentifier; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceSmall = + @"NSTouchBarItemIdentifierFixedSpaceSmall"; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceLarge = + @"NSTouchBarItemIdentifierFixedSpaceLarge"; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFlexibleSpace = + @"NSTouchBarItemIdentifierFlexibleSpace"; + +static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy = + @"NSTouchBarItemIdentifierOtherItemsProxy"; + +@interface NSTouchBar : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder*)aDecoder + NS_DESIGNATED_INITIALIZER; + +@property(copy, nullable) + NSTouchBarCustomizationIdentifier customizationIdentifier; +@property(copy) NSArray* customizationAllowedItemIdentifiers; +@property(copy) NSArray* customizationRequiredItemIdentifiers; +@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; + +- (nullable __kindof NSTouchBarItem*)itemForIdentifier: + (NSTouchBarItemIdentifier)identifier; + +@property(readonly, getter=isVisible) BOOL visible; + +@end + +@interface NSTouchBarItem : NSObject + +- (instancetype)initWithIdentifier:(NSTouchBarItemIdentifier)identifier + NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder*)coder + NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +@property(readonly, copy) NSTouchBarItemIdentifier identifier; +@property NSTouchBarItemPriority visibilityPriority; +@property(readonly, nullable) NSView* view; +@property(readonly, nullable) NSViewController* viewController; +@property(readwrite, copy) NSString* customizationLabel; +@property(readonly, getter=isVisible) BOOL visible; + +@end + +@interface NSGroupTouchBarItem : NSTouchBarItem + ++ (NSGroupTouchBarItem*)groupItemWithIdentifier: + (NSTouchBarItemIdentifier)identifier + items:(NSArray*)items; + +@property(strong) NSTouchBar* groupTouchBar; +@property(readwrite, copy, null_resettable) NSString* customizationLabel; + +@end + +@interface NSCustomTouchBarItem : NSTouchBarItem + +@property(readwrite, strong) __kindof NSView* view; +@property(readwrite, strong, nullable) + __kindof NSViewController* viewController; +@property(readwrite, copy, null_resettable) NSString* customizationLabel; + +@end + +@interface NSColorPickerTouchBarItem : NSTouchBarItem + +@property SEL action; +@property(weak) id target; +@property(copy) NSColor* color; +@property(strong) NSColorList* colorList; + +@end + +@interface NSPopoverTouchBarItem : NSTouchBarItem + +@property BOOL showsCloseButton; +@property(strong) NSImage* collapsedRepresentationImage; +@property(strong) NSString* collapsedRepresentationLabel; +@property(strong) NSTouchBar* popoverTouchBar; + +@end + +@interface NSSliderTouchBarItem : NSTouchBarItem + +@property SEL action; +@property(weak) id target; +@property(copy) NSString* label; +@property(strong) NSSlider* slider; + +@end + +@interface NSScrubber : NSView + +@property(weak) id delegate; +@property(weak) id dataSource; +@property NSScrubberMode mode; +@property BOOL showsArrowButtons; +@property(getter=isContinuous) BOOL continuous; +@property(strong, nullable) NSScrubberSelectionStyle* selectionBackgroundStyle; +@property(strong, nullable) NSScrubberSelectionStyle* selectionOverlayStyle; + +- (void)registerClass:(Class)itemViewClass + forItemIdentifier:(NSString*)itemIdentifier; + +- (__kindof NSScrubberItemView*)makeItemWithIdentifier:(NSString*)itemIdentifier + owner:(id)owner; +- (void)reloadData; + +@end + +@interface NSScrubberSelectionStyle : NSObject + +@property(class, strong, readonly) NSScrubberSelectionStyle* outlineOverlayStyle; +@property(class, strong, readonly) NSScrubberSelectionStyle* roundedBackgroundStyle; + +@end + +@interface NSScrubberArrangedView : NSView + +@end + +@interface NSScrubberItemView : NSScrubberArrangedView + +@end + +@interface NSScrubberTextItemView : NSScrubberItemView + +@property(copy) NSString* title; + +@end + +@interface NSScrubberImageItemView : NSScrubberItemView + +@property(copy) NSImage* image; + +@end + +@interface NSWindow (TouchBarSDK) + +@property(strong, readwrite, nullable) NSTouchBar* touchBar; + +@end + +@interface NSButton (TouchBarSDK) + +@property(copy) NSColor* bezelColor; ++ (instancetype)buttonWithTitle:(NSString*)title + target:(id)target + action:(SEL)action; + +@end + +@interface NSTextField (TouchBarSDK) + ++ (instancetype)labelWithString:(NSString*)stringValue; + +@end + +@interface NSSegmentedControl (TouchBarSDK) + ++ (instancetype)segmentedControlWithLabels:(NSArray*)labels + trackingMode:(NSSegmentSwitchTracking)trackingMode + target:(id)target + action:(SEL)action; + +@end + +@protocol NSTouchBarDelegate + +@optional +- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier; + +@end + +@protocol NSScrubberDelegate + +- (void)scrubber:(NSScrubber*)scrubber didHighlightItemAtIndex:(NSInteger)highlightedIndex; +- (void)scrubber:(NSScrubber*)scrubber didSelectItemAtIndex:(NSInteger)selectedIndex; + +@end + +@protocol NSScrubberDataSource + +- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)scrubber; +- (__kindof NSScrubberItemView*)scrubber:(NSScrubber*)scrubber + viewForItemAtIndex:(NSInteger)index; + +@end + +#pragma clang assume_nonnull end + +#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12_1 + +// When compiling against the 10.12.1 SDK or later, just provide forward +// declarations to suppress the partial availability warnings. + +@class NSCustomTouchBarItem; +@class NSGroupTouchBarItem; +@class NSTouchBar; +@protocol NSTouchBarDelegate; +@class NSTouchBarItem; + +@interface NSWindow (TouchBarSDK) +@property(strong, readonly) NSTouchBar* touchBar; +@end + +#endif // MAC_OS_X_VERSION_10_12_1 + +#endif // ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_ diff --git a/atom/browser/ui/file_dialog.h b/atom/browser/ui/file_dialog.h index a8703bebf8..6f33c46f20 100644 --- a/atom/browser/ui/file_dialog.h +++ b/atom/browser/ui/file_dialog.h @@ -23,12 +23,13 @@ typedef std::pair > Filter; typedef std::vector Filters; enum FileDialogProperty { - FILE_DIALOG_OPEN_FILE = 1 << 0, - FILE_DIALOG_OPEN_DIRECTORY = 1 << 1, - FILE_DIALOG_MULTI_SELECTIONS = 1 << 2, - FILE_DIALOG_CREATE_DIRECTORY = 1 << 3, - FILE_DIALOG_SHOW_HIDDEN_FILES = 1 << 4, - FILE_DIALOG_PROMPT_TO_CREATE = 1 << 5, + FILE_DIALOG_OPEN_FILE = 1 << 0, + FILE_DIALOG_OPEN_DIRECTORY = 1 << 1, + FILE_DIALOG_MULTI_SELECTIONS = 1 << 2, + FILE_DIALOG_CREATE_DIRECTORY = 1 << 3, + FILE_DIALOG_SHOW_HIDDEN_FILES = 1 << 4, + FILE_DIALOG_PROMPT_TO_CREATE = 1 << 5, + FILE_DIALOG_NO_RESOLVE_ALIASES = 1 << 6, }; typedef base::Callback SaveDialogCallback; -bool ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +struct DialogSettings { + atom::NativeWindow* parent_window = nullptr; + std::string title; + std::string message; + std::string button_label; + std::string name_field_label; + base::FilePath default_path; + Filters filters; + int properties = 0; + bool shows_tag_field = true; +}; + +bool ShowOpenDialog(const DialogSettings& settings, std::vector* paths); -void ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +void ShowOpenDialog(const DialogSettings& settings, const OpenDialogCallback& callback); -bool ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path); -void ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& callback); } // namespace file_dialog diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 18f53eba6f..c35d2d9d36 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -36,24 +36,20 @@ void OnFileFilterDataDestroyed(std::string* file_extension) { class FileChooserDialog { public: FileChooserDialog(GtkFileChooserAction action, - atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters) - : parent_(static_cast(parent_window)), - filters_(filters) { + const DialogSettings& settings) + : parent_(static_cast(settings.parent_window)), + filters_(settings.filters) { const char* confirm_text = GTK_STOCK_OK; - if (!button_label.empty()) - confirm_text = button_label.c_str(); + if (!settings.button_label.empty()) + confirm_text = settings.button_label.c_str(); else if (action == GTK_FILE_CHOOSER_ACTION_SAVE) confirm_text = GTK_STOCK_SAVE; else if (action == GTK_FILE_CHOOSER_ACTION_OPEN) confirm_text = GTK_STOCK_OPEN; dialog_ = gtk_file_chooser_dialog_new( - title.c_str(), + settings.title.c_str(), NULL, action, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, @@ -71,20 +67,20 @@ class FileChooserDialog { if (action != GTK_FILE_CHOOSER_ACTION_OPEN) gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dialog_), TRUE); - if (!default_path.empty()) { - if (base::DirectoryExists(default_path)) { + if (!settings.default_path.empty()) { + if (base::DirectoryExists(settings.default_path)) { gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog_), - default_path.value().c_str()); + settings.default_path.value().c_str()); } else { gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog_), - default_path.DirName().value().c_str()); + settings.default_path.DirName().value().c_str()); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog_), - default_path.BaseName().value().c_str()); + settings.default_path.BaseName().value().c_str()); } } - if (!filters.empty()) - AddFilters(filters); + if (!settings.filters.empty()) + AddFilters(settings.filters); } ~FileChooserDialog() { @@ -230,19 +226,13 @@ base::FilePath FileChooserDialog::AddExtensionForFilename( } // namespace -bool ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +bool ShowOpenDialog(const DialogSettings& settings, std::vector* paths) { GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; - if (properties & FILE_DIALOG_OPEN_DIRECTORY) + if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY) action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; - FileChooserDialog open_dialog(action, parent_window, title, button_label, - default_path, filters); - open_dialog.SetupProperties(properties); + FileChooserDialog open_dialog(action, settings); + open_dialog.SetupProperties(settings.properties); gtk_widget_show_all(open_dialog.dialog()); int response = gtk_dialog_run(GTK_DIALOG(open_dialog.dialog())); @@ -254,30 +244,19 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, } } -void ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +void ShowOpenDialog(const DialogSettings& settings, const OpenDialogCallback& callback) { GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; - if (properties & FILE_DIALOG_OPEN_DIRECTORY) + if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY) action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; - FileChooserDialog* open_dialog = new FileChooserDialog( - action, parent_window, title, button_label, default_path, filters); - open_dialog->SetupProperties(properties); + FileChooserDialog* open_dialog = new FileChooserDialog(action, settings); + open_dialog->SetupProperties(settings.properties); open_dialog->RunOpenAsynchronous(callback); } -bool ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { - FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, - title, button_label, default_path, filters); + FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings); gtk_widget_show_all(save_dialog.dialog()); int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog())); if (response == GTK_RESPONSE_ACCEPT) { @@ -288,15 +267,10 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, } } -void ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& callback) { FileChooserDialog* save_dialog = new FileChooserDialog( - GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, button_label, - default_path, filters); + GTK_FILE_CHOOSER_ACTION_SAVE, settings); save_dialog->RunSaveAsynchronous(callback); } diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 9492fe90ee..80143a987b 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -44,25 +44,31 @@ void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { } void SetupDialog(NSSavePanel* dialog, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters) { - if (!title.empty()) - [dialog setTitle:base::SysUTF8ToNSString(title)]; + const DialogSettings& settings) { + if (!settings.title.empty()) + [dialog setTitle:base::SysUTF8ToNSString(settings.title)]; - if (!button_label.empty()) - [dialog setPrompt:base::SysUTF8ToNSString(button_label)]; + if (!settings.button_label.empty()) + [dialog setPrompt:base::SysUTF8ToNSString(settings.button_label)]; + + if (!settings.message.empty()) + [dialog setMessage:base::SysUTF8ToNSString(settings.message)]; + + if (!settings.name_field_label.empty()) + [dialog setNameFieldLabel:base::SysUTF8ToNSString(settings.name_field_label)]; + + [dialog setShowsTagField:settings.shows_tag_field]; NSString* default_dir = nil; NSString* default_filename = nil; - if (!default_path.empty()) { - if (base::DirectoryExists(default_path)) { - default_dir = base::SysUTF8ToNSString(default_path.value()); + if (!settings.default_path.empty()) { + if (base::DirectoryExists(settings.default_path)) { + default_dir = base::SysUTF8ToNSString(settings.default_path.value()); } else { - default_dir = base::SysUTF8ToNSString(default_path.DirName().value()); + default_dir = + base::SysUTF8ToNSString(settings.default_path.DirName().value()); default_filename = - base::SysUTF8ToNSString(default_path.BaseName().value()); + base::SysUTF8ToNSString(settings.default_path.BaseName().value()); } } @@ -71,10 +77,10 @@ void SetupDialog(NSSavePanel* dialog, if (default_filename) [dialog setNameFieldStringValue:default_filename]; - if (filters.empty()) + if (settings.filters.empty()) [dialog setAllowsOtherFileTypes:YES]; else - SetAllowedFileTypes(dialog, filters); + SetAllowedFileTypes(dialog, settings.filters); } void SetupDialogForProperties(NSOpenPanel* dialog, int properties) { @@ -87,6 +93,8 @@ void SetupDialogForProperties(NSOpenPanel* dialog, int properties) { [dialog setAllowsMultipleSelection:YES]; if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES) [dialog setShowsHiddenFiles:YES]; + if (properties & FILE_DIALOG_NO_RESOLVE_ALIASES) + [dialog setResolvesAliases:NO]; } // Run modal dialog with parent window and return user's choice. @@ -117,20 +125,15 @@ void ReadDialogPaths(NSOpenPanel* dialog, std::vector* paths) { } // namespace -bool ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +bool ShowOpenDialog(const DialogSettings& settings, std::vector* paths) { DCHECK(paths); NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, button_label, default_path, filters); - SetupDialogForProperties(dialog, properties); + SetupDialog(dialog, settings); + SetupDialogForProperties(dialog, settings.properties); - int chosen = RunModalDialog(dialog, parent_window); + int chosen = RunModalDialog(dialog, settings.parent_window); if (chosen == NSFileHandlingPanelCancelButton) return false; @@ -138,23 +141,20 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, return true; } -void ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +void ShowOpenDialog(const DialogSettings& settings, const OpenDialogCallback& c) { NSOpenPanel* dialog = [NSOpenPanel openPanel]; - SetupDialog(dialog, title, button_label, default_path, filters); - SetupDialogForProperties(dialog, properties); + SetupDialog(dialog, settings); + SetupDialogForProperties(dialog, settings.properties); // Duplicate the callback object here since c is a reference and gcd would // only store the pointer, by duplication we can force gcd to store a copy. __block OpenDialogCallback callback = c; - NSWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL; + NSWindow* window = settings.parent_window ? + settings.parent_window->GetNativeWindow() : + nullptr; [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { if (chosen == NSFileHandlingPanelCancelButton) { @@ -167,18 +167,14 @@ void ShowOpenDialog(atom::NativeWindow* parent_window, }]; } -bool ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { DCHECK(path); NSSavePanel* dialog = [NSSavePanel savePanel]; - SetupDialog(dialog, title, button_label, default_path, filters); + SetupDialog(dialog, settings); - int chosen = RunModalDialog(dialog, parent_window); + int chosen = RunModalDialog(dialog, settings.parent_window); if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL]) return false; @@ -186,20 +182,18 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, return true; } -void ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& c) { NSSavePanel* dialog = [NSSavePanel savePanel]; - SetupDialog(dialog, title, button_label, default_path, filters); + SetupDialog(dialog, settings); [dialog setCanSelectHiddenExtension:YES]; __block SaveDialogCallback callback = c; - NSWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL; + NSWindow* window = settings.parent_window ? + settings.parent_window->GetNativeWindow() : + nullptr; [dialog beginSheetModalForWindow:window completionHandler:^(NSInteger chosen) { if (chosen == NSFileHandlingPanelCancelButton) { diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index 8e973432f5..eaaac5e39e 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -66,26 +66,24 @@ void ConvertFilters(const Filters& filters, template class FileDialog { public: - FileDialog(const base::FilePath& default_path, - const std::string& title, - const std::string& button_label, - const Filters& filters, int options) { + FileDialog(const DialogSettings& settings, int options) { std::wstring file_part; - if (!IsDirectory(default_path)) - file_part = default_path.BaseName().value(); + if (!IsDirectory(settings.default_path)) + file_part = settings.default_path.BaseName().value(); std::vector buffer; std::vector filterspec; - ConvertFilters(filters, &buffer, &filterspec); + ConvertFilters(settings.filters, &buffer, &filterspec); dialog_.reset(new T(file_part.c_str(), options, NULL, filterspec.data(), filterspec.size())); - if (!title.empty()) - GetPtr()->SetTitle(base::UTF8ToUTF16(title).c_str()); + if (!settings.title.empty()) + GetPtr()->SetTitle(base::UTF8ToUTF16(settings.title).c_str()); - if (!button_label.empty()) - GetPtr()->SetOkButtonLabel(base::UTF8ToUTF16(button_label).c_str()); + if (!settings.button_label.empty()) + GetPtr()->SetOkButtonLabel( + base::UTF8ToUTF16(settings.button_label).c_str()); // By default, *.* will be added to the file name if file type is "*.*". In // Electron, we disable it to make a better experience. @@ -107,7 +105,7 @@ class FileDialog { } } - SetDefaultFolder(default_path); + SetDefaultFolder(settings.default_path); } bool Show(atom::NativeWindow* parent_window) { @@ -160,31 +158,20 @@ bool CreateDialogThread(RunState* run_state) { } void RunOpenDialogInNewThread(const RunState& run_state, - atom::NativeWindow* parent, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, + const DialogSettings& settings, const OpenDialogCallback& callback) { std::vector paths; - bool result = ShowOpenDialog(parent, title, button_label, default_path, - filters, properties, &paths); + bool result = ShowOpenDialog(settings, &paths); run_state.ui_task_runner->PostTask(FROM_HERE, base::Bind(callback, result, paths)); run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread); } void RunSaveDialogInNewThread(const RunState& run_state, - atom::NativeWindow* parent, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, + const DialogSettings& settings, const SaveDialogCallback& callback) { base::FilePath path; - bool result = ShowSaveDialog(parent, title, button_label, default_path, - filters, &path); + bool result = ShowSaveDialog(settings, &path); run_state.ui_task_runner->PostTask(FROM_HERE, base::Bind(callback, result, path)); run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread); @@ -192,26 +179,20 @@ void RunSaveDialogInNewThread(const RunState& run_state, } // namespace -bool ShowOpenDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +bool ShowOpenDialog(const DialogSettings& settings, std::vector* paths) { int options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST; - if (properties & FILE_DIALOG_OPEN_DIRECTORY) + if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY) options |= FOS_PICKFOLDERS; - if (properties & FILE_DIALOG_MULTI_SELECTIONS) + if (settings.properties & FILE_DIALOG_MULTI_SELECTIONS) options |= FOS_ALLOWMULTISELECT; - if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES) + if (settings.properties & FILE_DIALOG_SHOW_HIDDEN_FILES) options |= FOS_FORCESHOWHIDDEN; - if (properties & FILE_DIALOG_PROMPT_TO_CREATE) + if (settings.properties & FILE_DIALOG_PROMPT_TO_CREATE) options |= FOS_CREATEPROMPT; - FileDialog open_dialog( - default_path, title, button_label, filters, options); - if (!open_dialog.Show(parent_window)) + FileDialog open_dialog(settings, options); + if (!open_dialog.Show(settings.parent_window)) return false; ATL::CComPtr items; @@ -244,12 +225,7 @@ bool ShowOpenDialog(atom::NativeWindow* parent_window, return true; } -void ShowOpenDialog(atom::NativeWindow* parent, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, - int properties, +void ShowOpenDialog(const DialogSettings& settings, const OpenDialogCallback& callback) { RunState run_state; if (!CreateDialogThread(&run_state)) { @@ -259,20 +235,14 @@ void ShowOpenDialog(atom::NativeWindow* parent, run_state.dialog_thread->task_runner()->PostTask( FROM_HERE, - base::Bind(&RunOpenDialogInNewThread, run_state, parent, title, - button_label, default_path, filters, properties, callback)); + base::Bind(&RunOpenDialogInNewThread, run_state, settings, callback)); } -bool ShowSaveDialog(atom::NativeWindow* parent_window, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +bool ShowSaveDialog(const DialogSettings& settings, base::FilePath* path) { FileDialog save_dialog( - default_path, title, button_label, filters, - FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT); - if (!save_dialog.Show(parent_window)) + settings, FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT); + if (!save_dialog.Show(settings.parent_window)) return false; wchar_t buffer[MAX_PATH]; @@ -284,11 +254,7 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, return true; } -void ShowSaveDialog(atom::NativeWindow* parent, - const std::string& title, - const std::string& button_label, - const base::FilePath& default_path, - const Filters& filters, +void ShowSaveDialog(const DialogSettings& settings, const SaveDialogCallback& callback) { RunState run_state; if (!CreateDialogThread(&run_state)) { @@ -298,8 +264,7 @@ void ShowSaveDialog(atom::NativeWindow* parent, run_state.dialog_thread->task_runner()->PostTask( FROM_HERE, - base::Bind(&RunSaveDialogInNewThread, run_state, parent, title, - button_label, default_path, filters, callback)); + base::Bind(&RunSaveDialogInNewThread, run_state, settings, callback)); } } // namespace file_dialog diff --git a/atom/browser/ui/message_box.h b/atom/browser/ui/message_box.h index 16eef3c463..6c826719ee 100644 --- a/atom/browser/ui/message_box.h +++ b/atom/browser/ui/message_box.h @@ -32,7 +32,8 @@ enum MessageBoxOptions { MESSAGE_BOX_NO_LINK = 1 << 0, }; -typedef base::Callback MessageBoxCallback; +typedef base::Callback + MessageBoxCallback; int ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, @@ -54,6 +55,8 @@ void ShowMessageBox(NativeWindow* parent_window, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback); diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc index 0717d4d06d..a7bbe51ecf 100644 --- a/atom/browser/ui/message_box_gtk.cc +++ b/atom/browser/ui/message_box_gtk.cc @@ -36,8 +36,11 @@ class GtkMessageBox : public NativeWindowObserver { const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon) : cancel_id_(cancel_id), + checkbox_checked_(false), parent_(static_cast(parent_window)) { // Create dialog. dialog_ = gtk_message_dialog_new( @@ -68,6 +71,18 @@ class GtkMessageBox : public NativeWindowObserver { g_object_unref(pixbuf); } + if (!checkbox_label.empty()) { + GtkWidget* message_area = + gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog_)); + GtkWidget* check_button = + gtk_check_button_new_with_label(checkbox_label.c_str()); + g_signal_connect(check_button, "toggled", + G_CALLBACK(OnCheckboxToggledThunk), this); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button), + checkbox_checked); + gtk_container_add(GTK_CONTAINER(message_area), check_button); + } + // Add buttons. for (size_t i = 0; i < buttons.size(); ++i) { GtkWidget* button = gtk_dialog_add_button( @@ -154,6 +169,7 @@ class GtkMessageBox : public NativeWindowObserver { } CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, int); + CHROMEGTK_CALLBACK_0(GtkMessageBox, void, OnCheckboxToggled); private: atom::UnresponsiveSuppressor unresponsive_suppressor_; @@ -161,6 +177,8 @@ class GtkMessageBox : public NativeWindowObserver { // The id to return when the dialog is closed without pressing buttons. int cancel_id_; + bool checkbox_checked_; + NativeWindowViews* parent_; GtkWidget* dialog_; MessageBoxCallback callback_; @@ -172,12 +190,16 @@ void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) { gtk_widget_hide(dialog_); if (response < 0) - callback_.Run(cancel_id_); + callback_.Run(cancel_id_, checkbox_checked_); else - callback_.Run(response); + callback_.Run(response, checkbox_checked_); delete this; } +void GtkMessageBox::OnCheckboxToggled(GtkWidget* widget) { + checkbox_checked_ = GTK_TOGGLE_BUTTON(widget)->active; +} + } // namespace int ShowMessageBox(NativeWindow* parent, @@ -190,8 +212,9 @@ int ShowMessageBox(NativeWindow* parent, const std::string& message, const std::string& detail, const gfx::ImageSkia& icon) { - return GtkMessageBox(parent, type, buttons, default_id, cancel_id, - title, message, detail, icon).RunSynchronous(); + return GtkMessageBox(parent, type, buttons, default_id, cancel_id, title, + message, detail, "", false, icon) + .RunSynchronous(); } void ShowMessageBox(NativeWindow* parent, @@ -203,18 +226,22 @@ void ShowMessageBox(NativeWindow* parent, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - (new GtkMessageBox(parent, type, buttons, default_id, cancel_id, - title, message, detail, icon))->RunAsynchronous(callback); + (new GtkMessageBox(parent, type, buttons, default_id, cancel_id, title, + message, detail, checkbox_label, checkbox_checked, icon)) + ->RunAsynchronous(callback); } void ShowErrorBox(const base::string16& title, const base::string16& content) { if (Browser::Get()->is_ready()) { - GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, { "OK" }, -1, 0, "Error", + GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, {"OK"}, -1, 0, "Error", base::UTF16ToUTF8(title).c_str(), - base::UTF16ToUTF8(content).c_str(), - gfx::ImageSkia()).RunSynchronous(); + base::UTF16ToUTF8(content).c_str(), "", false, + gfx::ImageSkia()) + .RunSynchronous(); } else { fprintf(stderr, ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index 8fd28e2446..f752f2945c 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -39,7 +39,7 @@ - (void)alertDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(void*)contextInfo { - callback_.Run(returnCode); + callback_.Run(returnCode, alert.suppressionButton.state == NSOnState); [alert_ release]; [self release]; @@ -57,9 +57,12 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, int default_id, + int cancel_id, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon) { // Ignore the title; it's the window title on other platforms and ignorable. NSAlert* alert = [[NSAlert alloc] init]; @@ -68,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; @@ -87,7 +94,14 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, } NSArray* ns_buttons = [alert buttons]; - if (default_id >= 0 && default_id < static_cast([ns_buttons count])) { + int button_count = static_cast([ns_buttons count]); + + // Bind cancel id button to escape key if there is more than one button + if (button_count > 1 && cancel_id >= 0 && cancel_id < button_count) { + [[ns_buttons objectAtIndex:cancel_id] setKeyEquivalent:@"\e"]; + } + + if (default_id >= 0 && default_id < button_count) { // Focus the button at default_id if the user opted to do so. // The first button added gets set as the default selected. // So remove that default, and make the requested button the default. @@ -95,6 +109,12 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, [[ns_buttons objectAtIndex:default_id] setKeyEquivalent:@"\r"]; } + if (!checkbox_label.empty()) { + alert.showsSuppressionButton = YES; + alert.suppressionButton.title = base::SysUTF8ToNSString(checkbox_label); + alert.suppressionButton.state = checkbox_checked ? NSOnState : NSOffState; + } + if (!icon.isNull()) { NSImage* image = skia::SkBitmapToNSImageWithColorSpace( *icon.bitmap(), base::mac::GetGenericRGBColorSpace()); @@ -104,7 +124,7 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, return alert; } -void SetReturnCode(int* ret_code, int result) { +void SetReturnCode(int* ret_code, int result, bool checkbox_checked) { *ret_code = result; } @@ -120,9 +140,9 @@ int ShowMessageBox(NativeWindow* parent_window, const std::string& message, const std::string& detail, const gfx::ImageSkia& icon) { - NSAlert* alert = CreateNSAlert( - parent_window, type, buttons, default_id, title, message, - detail, icon); + NSAlert* alert = CreateNSAlert(parent_window, type, buttons, default_id, + cancel_id, title, message, detail, "", false, + icon); // Use runModal for synchronous alert without parent, since we don't have a // window to wait for. @@ -154,11 +174,13 @@ void ShowMessageBox(NativeWindow* parent_window, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - NSAlert* alert = CreateNSAlert( - parent_window, type, buttons, default_id, title, message, - detail, icon); + NSAlert* alert = + CreateNSAlert(parent_window, type, buttons, default_id, cancel_id, title, + message, detail, checkbox_label, checkbox_checked, icon); ModalDelegate* delegate = [[ModalDelegate alloc] initWithCallback:callback andAlert:alert callEndModal:false]; @@ -174,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/message_box_win.cc b/atom/browser/ui/message_box_win.cc index b6777fb1da..844a057ae6 100644 --- a/atom/browser/ui/message_box_win.cc +++ b/atom/browser/ui/message_box_win.cc @@ -72,7 +72,7 @@ void MapToCommonID(const std::vector& buttons, } } -int ShowMessageBoxUTF16(HWND parent, +int ShowTaskDialogUTF16(NativeWindow* parent, MessageBoxType type, const std::vector& buttons, int default_id, @@ -81,6 +81,8 @@ int ShowMessageBoxUTF16(HWND parent, const base::string16& title, const base::string16& message, const base::string16& detail, + const base::string16& checkbox_label, + bool* checkbox_checked, const gfx::ImageSkia& icon) { TASKDIALOG_FLAGS flags = TDF_SIZE_TO_CONTENT | // Show all content. @@ -88,10 +90,14 @@ int ShowMessageBoxUTF16(HWND parent, TASKDIALOGCONFIG config = { 0 }; config.cbSize = sizeof(config); - config.hwndParent = parent; config.hInstance = GetModuleHandle(NULL); config.dwFlags = flags; + if (parent) { + config.hwndParent = + static_cast(parent)->GetAcceleratedWidget(); + } + if (default_id > 0) config.nDefaultButton = kIDStart + default_id; @@ -132,6 +138,14 @@ int ShowMessageBoxUTF16(HWND parent, config.pszContent = detail.c_str(); } + if (!checkbox_label.empty()) { + config.pszVerificationText = checkbox_label.c_str(); + + if (checkbox_checked && *checkbox_checked) { + config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED; + } + } + // Iterate through the buttons, put common buttons in dwCommonButtons // and custom buttons in pButtons. std::map id_map; @@ -151,7 +165,12 @@ int ShowMessageBoxUTF16(HWND parent, } int id = 0; - TaskDialogIndirect(&config, &id, NULL, NULL); + BOOL verificationFlagChecked = FALSE; + TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked); + if (checkbox_checked) { + *checkbox_checked = verificationFlagChecked; + } + if (id_map.find(id) != id_map.end()) // common button. return id_map[id]; else if (id >= kIDStart) // custom button. @@ -160,6 +179,29 @@ int ShowMessageBoxUTF16(HWND parent, return cancel_id; } +int ShowTaskDialogUTF8(NativeWindow* parent, + MessageBoxType type, + const std::vector& buttons, + int default_id, + int cancel_id, + int options, + const std::string& title, + const std::string& message, + const std::string& detail, + const std::string& checkbox_label, + bool* checkbox_checked, + const gfx::ImageSkia& icon) { + std::vector utf16_buttons; + for (const auto& button : buttons) + utf16_buttons.push_back(base::UTF8ToUTF16(button)); + + return ShowTaskDialogUTF16( + parent, type, utf16_buttons, default_id, cancel_id, options, + base::UTF8ToUTF16(title), base::UTF8ToUTF16(message), + base::UTF8ToUTF16(detail), base::UTF8ToUTF16(checkbox_label), + checkbox_checked, icon); +} + void RunMessageBoxInNewThread(base::Thread* thread, NativeWindow* parent, MessageBoxType type, @@ -170,12 +212,16 @@ void RunMessageBoxInNewThread(base::Thread* thread, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { - int result = ShowMessageBox(parent, type, buttons, default_id, - cancel_id, options, title, message, detail, icon); + int result = ShowTaskDialogUTF8(parent, type, buttons, default_id, cancel_id, + options, title, message, detail, + checkbox_label, &checkbox_checked, icon); content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); + content::BrowserThread::UI, FROM_HERE, + base::Bind(callback, result, checkbox_checked)); content::BrowserThread::DeleteSoon( content::BrowserThread::UI, FROM_HERE, thread); } @@ -192,25 +238,9 @@ int ShowMessageBox(NativeWindow* parent, const std::string& message, const std::string& detail, const gfx::ImageSkia& icon) { - std::vector utf16_buttons; - for (const auto& button : buttons) - utf16_buttons.push_back(base::UTF8ToUTF16(button)); - - HWND hwnd_parent = parent ? - static_cast(parent)->GetAcceleratedWidget() : - NULL; - atom::UnresponsiveSuppressor suppressor; - return ShowMessageBoxUTF16(hwnd_parent, - type, - utf16_buttons, - default_id, - cancel_id, - options, - base::UTF8ToUTF16(title), - base::UTF8ToUTF16(message), - base::UTF8ToUTF16(detail), - icon); + return ShowTaskDialogUTF8(parent, type, buttons, default_id, cancel_id, + options, title, message, detail, "", nullptr, icon); } void ShowMessageBox(NativeWindow* parent, @@ -222,13 +252,15 @@ void ShowMessageBox(NativeWindow* parent, const std::string& title, const std::string& message, const std::string& detail, + const std::string& checkbox_label, + bool checkbox_checked, const gfx::ImageSkia& icon, const MessageBoxCallback& callback) { std::unique_ptr thread( new base::Thread(ATOM_PRODUCT_NAME "MessageBoxThread")); thread->init_com_with_mta(false); if (!thread->Start()) { - callback.Run(cancel_id); + callback.Run(cancel_id, checkbox_checked); return; } @@ -237,13 +269,14 @@ void ShowMessageBox(NativeWindow* parent, FROM_HERE, base::Bind(&RunMessageBoxInNewThread, base::Unretained(unretained), parent, type, buttons, default_id, cancel_id, options, title, - message, detail, icon, callback)); + message, detail, checkbox_label, checkbox_checked, icon, + callback)); } void ShowErrorBox(const base::string16& title, const base::string16& content) { atom::UnresponsiveSuppressor suppressor; - ShowMessageBoxUTF16(NULL, MESSAGE_BOX_TYPE_ERROR, {}, -1, 0, 0, L"Error", - title, content, gfx::ImageSkia()); + ShowTaskDialogUTF16(nullptr, MESSAGE_BOX_TYPE_ERROR, {}, -1, 0, 0, L"Error", + title, content, L"", nullptr, gfx::ImageSkia()); } } // namespace atom diff --git a/atom/browser/ui/views/menu_layout.cc b/atom/browser/ui/views/menu_layout.cc deleted file mode 100644 index d70a4655a1..0000000000 --- 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 0a8464a1d4..0000000000 --- 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 b398d84dee..617dd3346d 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 fca7cb2334..3908a2774e 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/webui/pdf_viewer_handler.cc b/atom/browser/ui/webui/pdf_viewer_handler.cc new file mode 100644 index 0000000000..be2f337305 --- /dev/null +++ b/atom/browser/ui/webui/pdf_viewer_handler.cc @@ -0,0 +1,212 @@ +// 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/webui/pdf_viewer_handler.h" + +#include "atom/common/atom_constants.h" +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "base/values.h" +#include "content/public/browser/stream_handle.h" +#include "content/public/browser/stream_info.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui.h" +#include "content/public/common/page_zoom.h" +#include "content/public/common/url_constants.h" +#include "net/http/http_response_headers.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/webui/web_ui_util.h" + +namespace atom { + +namespace { + +void CreateResponseHeadersDictionary(const net::HttpResponseHeaders* headers, + base::DictionaryValue* result) { + if (!headers) + return; + + size_t iter = 0; + std::string header_name; + std::string header_value; + while (headers->EnumerateHeaderLines(&iter, &header_name, &header_value)) { + base::Value* existing_value = nullptr; + if (result->Get(header_name, &existing_value)) { + base::StringValue* existing_string_value = + static_cast(existing_value); + existing_string_value->GetString()->append(", ").append(header_value); + } else { + result->SetString(header_name, header_value); + } + } +} + +void PopulateStreamInfo(base::DictionaryValue* stream_info, + content::StreamInfo* stream, + const std::string& original_url) { + auto headers_dict = base::MakeUnique(); + auto stream_url = stream->handle->GetURL().spec(); + CreateResponseHeadersDictionary(stream->response_headers.get(), + headers_dict.get()); + stream_info->SetString("streamURL", stream_url); + stream_info->SetString("originalURL", original_url); + stream_info->Set("responseHeaders", std::move(headers_dict)); +} + +} // namespace + +PdfViewerHandler::PdfViewerHandler(const std::string& src) + : stream_(nullptr), original_url_(src) {} + +PdfViewerHandler::~PdfViewerHandler() {} + +void PdfViewerHandler::SetPdfResourceStream(content::StreamInfo* stream) { + stream_ = stream; + if (!!initialize_callback_id_.get()) { + auto list = base::MakeUnique(); + list->Set(0, std::move(initialize_callback_id_)); + Initialize(list.get()); + } +} + +void PdfViewerHandler::RegisterMessages() { + web_ui()->RegisterMessageCallback( + "initialize", + base::Bind(&PdfViewerHandler::Initialize, base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "getDefaultZoom", + base::Bind(&PdfViewerHandler::GetInitialZoom, base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "getInitialZoom", + base::Bind(&PdfViewerHandler::GetInitialZoom, base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "setZoom", + base::Bind(&PdfViewerHandler::SetZoom, base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "getStrings", + base::Bind(&PdfViewerHandler::GetStrings, base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "reload", base::Bind(&PdfViewerHandler::Reload, base::Unretained(this))); +} + +void PdfViewerHandler::OnJavascriptAllowed() { + auto host_zoom_map = + content::HostZoomMap::GetForWebContents(web_ui()->GetWebContents()); + host_zoom_map_subscription_ = + host_zoom_map->AddZoomLevelChangedCallback(base::Bind( + &PdfViewerHandler::OnZoomLevelChanged, base::Unretained(this))); +} + +void PdfViewerHandler::OnJavascriptDisallowed() { + host_zoom_map_subscription_.reset(); +} + +void PdfViewerHandler::Initialize(const base::ListValue* args) { + CHECK_EQ(1U, args->GetSize()); + const base::Value* callback_id; + CHECK(args->Get(0, &callback_id)); + + if (stream_) { + CHECK(!initialize_callback_id_.get()); + AllowJavascript(); + + auto stream_info = base::MakeUnique(); + PopulateStreamInfo(stream_info.get(), stream_, original_url_); + ResolveJavascriptCallback(*callback_id, *stream_info); + } else { + initialize_callback_id_ = callback_id->CreateDeepCopy(); + } +} + +void PdfViewerHandler::GetDefaultZoom(const base::ListValue* args) { + if (!IsJavascriptAllowed()) + return; + CHECK_EQ(1U, args->GetSize()); + const base::Value* callback_id; + CHECK(args->Get(0, &callback_id)); + + auto host_zoom_map = + content::HostZoomMap::GetForWebContents(web_ui()->GetWebContents()); + double zoom_level = host_zoom_map->GetDefaultZoomLevel(); + ResolveJavascriptCallback( + *callback_id, + base::FundamentalValue(content::ZoomLevelToZoomFactor(zoom_level))); +} + +void PdfViewerHandler::GetInitialZoom(const base::ListValue* args) { + if (!IsJavascriptAllowed()) + return; + CHECK_EQ(1U, args->GetSize()); + const base::Value* callback_id; + CHECK(args->Get(0, &callback_id)); + + double zoom_level = + content::HostZoomMap::GetZoomLevel(web_ui()->GetWebContents()); + ResolveJavascriptCallback( + *callback_id, + base::FundamentalValue(content::ZoomLevelToZoomFactor(zoom_level))); +} + +void PdfViewerHandler::SetZoom(const base::ListValue* args) { + if (!IsJavascriptAllowed()) + return; + CHECK_EQ(2U, args->GetSize()); + const base::Value* callback_id; + CHECK(args->Get(0, &callback_id)); + double zoom_level = 0.0; + CHECK(args->GetDouble(1, &zoom_level)); + + content::HostZoomMap::SetZoomLevel(web_ui()->GetWebContents(), + zoom_level); + ResolveJavascriptCallback(*callback_id, base::FundamentalValue(zoom_level)); +} + +void PdfViewerHandler::GetStrings(const base::ListValue* args) { + if (!IsJavascriptAllowed()) + return; + CHECK_EQ(1U, args->GetSize()); + const base::Value* callback_id; + CHECK(args->Get(0, &callback_id)); + + auto result = base::MakeUnique(); +// TODO(deepak1556): Generate strings from components/pdf_strings.grdp. +#define SET_STRING(id, resource) result->SetString(id, resource) + SET_STRING("passwordPrompt", + "This document is password protected. Please enter a password."); + SET_STRING("passwordSubmit", "Submit"); + SET_STRING("passwordInvalid", "Incorrect password"); + SET_STRING("pageLoading", "Loading..."); + SET_STRING("pageLoadFailed", "Failed to load PDF document"); + SET_STRING("pageReload", "Reload"); + SET_STRING("bookmarks", "Bookmarks"); + SET_STRING("labelPageNumber", "Page number"); + SET_STRING("tooltipRotateCW", "Rotate clockwise"); + SET_STRING("tooltipDownload", "Download"); + SET_STRING("tooltipFitToPage", "Fit to page"); + SET_STRING("tooltipFitToWidth", "Fit to width"); + SET_STRING("tooltipZoomIn", "Zoom in"); + SET_STRING("tooltipZoomOut", "Zoom out"); +#undef SET_STRING + + webui::SetLoadTimeDataDefaults(l10n_util::GetApplicationLocale(""), + result.get()); + ResolveJavascriptCallback(*callback_id, *result); +} + +void PdfViewerHandler::Reload(const base::ListValue* args) { + CHECK_EQ(0U, args->GetSize()); + web_ui()->GetWebContents()->ReloadFocusedFrame(false); +} + +void PdfViewerHandler::OnZoomLevelChanged( + const content::HostZoomMap::ZoomLevelChange& change) { + if (change.host == kPdfViewerUIHost) { + CallJavascriptFunction( + "cr.webUIListenerCallback", base::StringValue("onZoomLevelChanged"), + base::FundamentalValue( + content::ZoomLevelToZoomFactor(change.zoom_level))); + } +} + +} // namespace atom diff --git a/atom/browser/ui/webui/pdf_viewer_handler.h b/atom/browser/ui/webui/pdf_viewer_handler.h new file mode 100644 index 0000000000..7576b06c85 --- /dev/null +++ b/atom/browser/ui/webui/pdf_viewer_handler.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_BROWSER_UI_WEBUI_PDF_VIEWER_HANDLER_H_ +#define ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_HANDLER_H_ + +#include + +#include "base/macros.h" +#include "content/public/browser/host_zoom_map.h" +#include "content/public/browser/web_ui_message_handler.h" + +namespace base { +class ListValue; +} + +namespace content { +struct StreamInfo; +} + +namespace atom { + +class PdfViewerHandler : public content::WebUIMessageHandler { + public: + explicit PdfViewerHandler(const std::string& original_url); + ~PdfViewerHandler() override; + + void SetPdfResourceStream(content::StreamInfo* stream); + + protected: + // WebUIMessageHandler implementation. + void RegisterMessages() override; + void OnJavascriptAllowed() override; + void OnJavascriptDisallowed() override; + + private: + void Initialize(const base::ListValue* args); + void GetDefaultZoom(const base::ListValue* args); + void GetInitialZoom(const base::ListValue* args); + void SetZoom(const base::ListValue* args); + void GetStrings(const base::ListValue* args); + void Reload(const base::ListValue* args); + void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange& change); + + // Keeps track of events related to zooming. + std::unique_ptr + host_zoom_map_subscription_; + std::unique_ptr initialize_callback_id_; + content::StreamInfo* stream_; + std::string original_url_; + + DISALLOW_COPY_AND_ASSIGN(PdfViewerHandler); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_HANDLER_H_ diff --git a/atom/browser/ui/webui/pdf_viewer_ui.cc b/atom/browser/ui/webui/pdf_viewer_ui.cc new file mode 100644 index 0000000000..580d1831e5 --- /dev/null +++ b/atom/browser/ui/webui/pdf_viewer_ui.cc @@ -0,0 +1,251 @@ +// 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/webui/pdf_viewer_ui.h" + +#include + +#include "atom/browser/atom_browser_context.h" +#include "atom/browser/loader/layered_resource_handler.h" +#include "atom/browser/ui/webui/pdf_viewer_handler.h" +#include "atom/common/atom_constants.h" +#include "base/sequenced_task_runner_helpers.h" +#include "components/pdf/common/pdf_messages.h" +#include "content/browser/loader/resource_dispatcher_host_impl.h" +#include "content/browser/loader/resource_request_info_impl.h" +#include "content/browser/loader/stream_resource_handler.h" +#include "content/browser/resource_context_impl.h" +#include "content/browser/streams/stream.h" +#include "content/browser/streams/stream_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/resource_context.h" +#include "content/public/browser/stream_handle.h" +#include "content/public/browser/stream_info.h" +#include "content/public/browser/url_data_source.h" +#include "content/public/browser/web_contents.h" +#include "grit/pdf_viewer_resources_map.h" +#include "net/base/load_flags.h" +#include "net/base/mime_util.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "ui/base/resource/resource_bundle.h" + +using content::BrowserThread; + +namespace atom { + +namespace { + +// Extracts the path value from the URL without the leading '/', +// which follows the mapping of names in pdf_viewer_resources_map. +std::string PathWithoutParams(const std::string& path) { + return GURL(kPdfViewerUIOrigin + path).path().substr(1); +} + +class BundledDataSource : public content::URLDataSource { + public: + BundledDataSource() { + for (size_t i = 0; i < kPdfViewerResourcesSize; ++i) { + std::string resource_path = kPdfViewerResources[i].name; + DCHECK(path_to_resource_id_.find(resource_path) == + path_to_resource_id_.end()); + path_to_resource_id_[resource_path] = kPdfViewerResources[i].value; + } + } + + // content::URLDataSource implementation. + std::string GetSource() const override { return kPdfViewerUIHost; } + + void StartDataRequest( + const std::string& path, + const content::ResourceRequestInfo::WebContentsGetter& wc_getter, + const GotDataCallback& callback) override { + std::string filename = PathWithoutParams(path); + auto entry = path_to_resource_id_.find(filename); + + if (entry != path_to_resource_id_.end()) { + int resource_id = entry->second; + const ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + callback.Run(rb.LoadDataResourceBytes(resource_id)); + } else { + LOG(ERROR) << "Unable to find: " << path; + callback.Run(new base::RefCountedString()); + } + } + + std::string GetMimeType(const std::string& path) const override { + std::string filename = PathWithoutParams(path); + std::string mime_type; + net::GetMimeTypeFromFile( + base::FilePath::FromUTF8Unsafe(filename), &mime_type); + return mime_type; + } + + bool ShouldAddContentSecurityPolicy() const override { return false; } + + bool ShouldDenyXFrameOptions() const override { return false; } + + bool ShouldServeMimeTypeAsContentTypeHeader() const override { return true; } + + private: + ~BundledDataSource() override {} + + // A map from a resource path to the resource ID. + std::map path_to_resource_id_; + + DISALLOW_COPY_AND_ASSIGN(BundledDataSource); +}; + +// Helper to convert from OnceCallback to Callback. +template +void CallMigrationCallback(T callback, + std::unique_ptr stream_info) { + std::move(callback).Run(std::move(stream_info)); +} + +} // namespace + +class PdfViewerUI::ResourceRequester + : public base::RefCountedThreadSafe, + public atom::LayeredResourceHandler::Delegate { + public: + explicit ResourceRequester(StreamResponseCallback cb) + : stream_response_cb_(std::move(cb)) {} + + void StartRequest(const GURL& url, + const GURL& origin, + int render_process_id, + int render_view_id, + int render_frame_id, + content::ResourceContext* resource_context) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + const net::URLRequestContext* request_context = + resource_context->GetRequestContext(); + std::unique_ptr request( + request_context->CreateRequest(url, net::DEFAULT_PRIORITY, nullptr)); + request->set_method("GET"); + + content::ResourceDispatcherHostImpl::Get()->InitializeURLRequest( + request.get(), content::Referrer(url, blink::WebReferrerPolicyDefault), + false, // download. + render_process_id, render_view_id, render_frame_id, resource_context); + + content::ResourceRequestInfoImpl* info = + content::ResourceRequestInfoImpl::ForRequest(request.get()); + content::StreamContext* stream_context = + content::GetStreamContextForResourceContext(resource_context); + + std::unique_ptr handler = + base::MakeUnique( + request.get(), stream_context->registry(), origin); + info->set_is_stream(true); + stream_info_.reset(new content::StreamInfo); + stream_info_->handle = + static_cast(handler.get()) + ->stream() + ->CreateHandle(); + stream_info_->original_url = request->url(); + + // Helper to fill stream response details. + handler.reset(new atom::LayeredResourceHandler(request.get(), + std::move(handler), this)); + + content::ResourceDispatcherHostImpl::Get()->BeginURLRequest( + std::move(request), std::move(handler), + false, // download + false, // content_initiated (download specific) + false, // do_not_prompt_for_login (download specific) + resource_context); + } + + protected: + // atom::LayeredResourceHandler::Delegate: + void OnResponseStarted(content::ResourceResponse* response) override { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + auto resource_response_head = response->head; + auto headers = resource_response_head.headers; + auto mime_type = resource_response_head.mime_type; + if (headers.get()) + stream_info_->response_headers = + new net::HttpResponseHeaders(headers->raw_headers()); + stream_info_->mime_type = mime_type; + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&CallMigrationCallback, + base::Passed(&stream_response_cb_), + base::Passed(&stream_info_))); + } + + private: + friend struct BrowserThread::DeleteOnThread; + friend class base::DeleteHelper; + ~ResourceRequester() override {} + + StreamResponseCallback stream_response_cb_; + std::unique_ptr stream_info_; + + DISALLOW_COPY_AND_ASSIGN(ResourceRequester); +}; + +PdfViewerUI::PdfViewerUI(content::BrowserContext* browser_context, + content::WebUI* web_ui, + const std::string& src) + : content::WebUIController(web_ui), + content::WebContentsObserver(web_ui->GetWebContents()), + src_(src) { + pdf_handler_ = new PdfViewerHandler(src); + web_ui->AddMessageHandler(pdf_handler_); + content::URLDataSource::Add(browser_context, new BundledDataSource); +} + +PdfViewerUI::~PdfViewerUI() {} + +bool PdfViewerUI::OnMessageReceived( + const IPC::Message& message, + content::RenderFrameHost* render_frame_host) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PdfViewerUI, message) + IPC_MESSAGE_HANDLER(PDFHostMsg_PDFSaveURLAs, OnSaveURLAs) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void PdfViewerUI::OnPdfStreamCreated( + std::unique_ptr stream) { + stream_ = std::move(stream); + if (pdf_handler_) + pdf_handler_->SetPdfResourceStream(stream_.get()); + resource_requester_ = nullptr; +} + +void PdfViewerUI::RenderFrameCreated(content::RenderFrameHost* rfh) { + int render_process_id = rfh->GetProcess()->GetID(); + int render_frame_id = rfh->GetRoutingID(); + int render_view_id = rfh->GetRenderViewHost()->GetRoutingID(); + auto resource_context = + web_contents()->GetBrowserContext()->GetResourceContext(); + auto callback = + base::BindOnce(&PdfViewerUI::OnPdfStreamCreated, base::Unretained(this)); + resource_requester_ = new ResourceRequester(std::move(callback)); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&ResourceRequester::StartRequest, resource_requester_, + GURL(src_), GURL(kPdfViewerUIOrigin), render_process_id, + render_view_id, render_frame_id, resource_context)); +} + +void PdfViewerUI::OnSaveURLAs(const GURL& url, + const content::Referrer& referrer) { + web_contents()->SaveFrame(url, referrer); +} + +} // namespace atom diff --git a/atom/browser/ui/webui/pdf_viewer_ui.h b/atom/browser/ui/webui/pdf_viewer_ui.h new file mode 100644 index 0000000000..2f514f5114 --- /dev/null +++ b/atom/browser/ui/webui/pdf_viewer_ui.h @@ -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. + +#ifndef ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_UI_H_ +#define ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_UI_H_ + +#include + +#include "base/macros.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_ui_controller.h" +#include "ipc/ipc_message.h" + +namespace content { +class BrowserContext; +struct StreamInfo; +} + +namespace atom { + +class PdfViewerHandler; + +class PdfViewerUI : public content::WebUIController, + public content::WebContentsObserver { + public: + PdfViewerUI(content::BrowserContext* browser_context, + content::WebUI* web_ui, + const std::string& src); + ~PdfViewerUI() override; + + // content::WebContentsObserver: + bool OnMessageReceived(const IPC::Message& message, + content::RenderFrameHost* render_frame_host) override; + void RenderFrameCreated(content::RenderFrameHost* rfh) override; + + private: + using StreamResponseCallback = + base::OnceCallback)>; + class ResourceRequester; + + void OnPdfStreamCreated(std::unique_ptr stream_info); + void OnSaveURLAs(const GURL& url, const content::Referrer& referrer); + + // Source URL from where the PDF originates. + std::string src_; + + PdfViewerHandler* pdf_handler_; + + scoped_refptr resource_requester_; + + // Pdf Resource stream. + std::unique_ptr stream_; + + DISALLOW_COPY_AND_ASSIGN(PdfViewerUI); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_UI_H_ diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.cc b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc new file mode 100644 index 0000000000..e0cd68608a --- /dev/null +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.cc @@ -0,0 +1,22 @@ +// 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/win/atom_desktop_native_widget_aura.h" + +namespace atom { + +AtomDesktopNativeWidgetAura::AtomDesktopNativeWidgetAura( + views::internal::NativeWidgetDelegate* delegate) + : views::DesktopNativeWidgetAura(delegate) { +} + +void AtomDesktopNativeWidgetAura::Activate() { + // Activate can cause the focused window to be blurred so only + // call when the window being activated is visible. This prevents + // hidden windows from blurring the focused window when created. + if (IsVisible()) + views::DesktopNativeWidgetAura::Activate(); +} + +} // namespace atom diff --git a/atom/browser/ui/win/atom_desktop_native_widget_aura.h b/atom/browser/ui/win/atom_desktop_native_widget_aura.h new file mode 100644 index 0000000000..b5a6c0933d --- /dev/null +++ b/atom/browser/ui/win/atom_desktop_native_widget_aura.h @@ -0,0 +1,27 @@ +// 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_WIN_ATOM_DESKTOP_NATIVE_WIDGET_AURA_H_ +#define ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_NATIVE_WIDGET_AURA_H_ + +#include "atom/browser/native_window_views.h" +#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" + +namespace atom { + +class AtomDesktopNativeWidgetAura : public views::DesktopNativeWidgetAura { + public: + explicit AtomDesktopNativeWidgetAura( + views::internal::NativeWidgetDelegate* delegate); + + // internal::NativeWidgetPrivate: + void Activate() override; + + private: + DISALLOW_COPY_AND_ASSIGN(AtomDesktopNativeWidgetAura); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_NATIVE_WIDGET_AURA_H_ 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 9cc2ef9e8d..84a6d9aa3e 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 2df70547c5..47e4cb6aed 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 2c79270591..ec9e4ad6e9 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_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 5afa3070f0..7bb8605039 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -97,6 +97,10 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( command_line->AppendSwitchASCII(switches::kNodeIntegration, node_integration ? "true" : "false"); + // Whether to enable node integration in Worker. + if (web_preferences.GetBoolean(options::kNodeIntegrationInWorker, &b) && b) + command_line->AppendSwitch(switches::kNodeIntegrationInWorker); + // If the `sandbox` option was passed to the BrowserWindow's webPreferences, // pass `--enable-sandbox` to the renderer so it won't have any node.js // integration. @@ -130,13 +134,6 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( if (web_preferences.GetString(options::kBackgroundColor, &color)) command_line->AppendSwitchASCII(switches::kBackgroundColor, color); - // The zoom factor. - double zoom_factor = 1.0; - if (web_preferences.GetDouble(options::kZoomFactor, &zoom_factor) && - zoom_factor != 1.0) - command_line->AppendSwitchASCII(switches::kZoomFactor, - base::DoubleToString(zoom_factor)); - // --guest-instance-id, which is used to identify guest WebContents. int guest_instance_id = 0; if (web_preferences.GetInteger(options::kGuestInstanceID, &guest_instance_id)) @@ -254,15 +251,28 @@ void WebContentsPreferences::OverrideWebkitPrefs( prefs->fantasy_font_family_map[content::kCommonScript] = font; } int size; - if (self->web_preferences_.GetInteger("defaultFontSize", &size)) + if (self->GetInteger("defaultFontSize", &size)) prefs->default_font_size = size; - if (self->web_preferences_.GetInteger("defaultMonospaceFontSize", &size)) + if (self->GetInteger("defaultMonospaceFontSize", &size)) prefs->default_fixed_font_size = size; - if (self->web_preferences_.GetInteger("minimumFontSize", &size)) + if (self->GetInteger("minimumFontSize", &size)) prefs->minimum_font_size = size; std::string encoding; if (self->web_preferences_.GetString("defaultEncoding", &encoding)) prefs->default_encoding = encoding; } +bool WebContentsPreferences::GetInteger(const std::string& attributeName, + int* intValue) { + // if it is already an integer, no conversion needed + if (web_preferences_.GetInteger(attributeName, intValue)) + return true; + + base::string16 stringValue; + if (web_preferences_.GetString(attributeName, &stringValue)) + return base::StringToInt(stringValue, intValue); + + return false; +} + } // namespace atom diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index 3a04ea9eea..f6e44c51b1 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ #define ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ +#include #include #include "base/values.h" @@ -60,6 +61,9 @@ class WebContentsPreferences content::WebContents* web_contents_; base::DictionaryValue web_preferences_; + // Get preferences value as integer possibly coercing it from a string + bool GetInteger(const std::string& attributeName, int* intValue); + DISALLOW_COPY_AND_ASSIGN(WebContentsPreferences); }; diff --git a/atom/browser/web_contents_zoom_controller.cc b/atom/browser/web_contents_zoom_controller.cc new file mode 100644 index 0000000000..f48e643d96 --- /dev/null +++ b/atom/browser/web_contents_zoom_controller.cc @@ -0,0 +1,157 @@ +// 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/web_contents_zoom_controller.h" + +#include "content/public/browser/navigation_details.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/page_type.h" +#include "content/public/common/page_zoom.h" +#include "net/base/url_util.h" + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsZoomController); + +namespace atom { + +WebContentsZoomController::WebContentsZoomController( + content::WebContents* web_contents) + : content::WebContentsObserver(web_contents), + old_process_id_(-1), + old_view_id_(-1), + embedder_zoom_controller_(nullptr) { + default_zoom_factor_ = content::kEpsilon; + host_zoom_map_ = content::HostZoomMap::GetForWebContents(web_contents); +} + +WebContentsZoomController::~WebContentsZoomController() {} + +void WebContentsZoomController::AddObserver( + WebContentsZoomController::Observer* observer) { + observers_.AddObserver(observer); +} + +void WebContentsZoomController::RemoveObserver( + WebContentsZoomController::Observer* observer) { + observers_.RemoveObserver(observer); +} + +void WebContentsZoomController::SetEmbedderZoomController( + WebContentsZoomController* controller) { + embedder_zoom_controller_ = controller; +} + +void WebContentsZoomController::SetZoomLevel(double level) { + if (!web_contents()->GetRenderViewHost()->IsRenderViewLive() || + content::ZoomValuesEqual(GetZoomLevel(), level)) + return; + + int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); + int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); + if (host_zoom_map_->UsesTemporaryZoomLevel(render_process_id, + render_view_id)) { + host_zoom_map_->ClearTemporaryZoomLevel(render_process_id, render_view_id); + } + + content::HostZoomMap::SetZoomLevel(web_contents(), level); + // Notify observers of zoom level changes. + for (Observer& observer : observers_) + observer.OnZoomLevelChanged(web_contents(), level, false); +} + +double WebContentsZoomController::GetZoomLevel() { + return content::HostZoomMap::GetZoomLevel(web_contents()); +} + +void WebContentsZoomController::SetDefaultZoomFactor(double factor) { + default_zoom_factor_ = factor; +} + +double WebContentsZoomController::GetDefaultZoomFactor() { + return default_zoom_factor_; +} + +void WebContentsZoomController::SetTemporaryZoomLevel(double level) { + old_process_id_ = web_contents()->GetRenderProcessHost()->GetID(); + old_view_id_ = web_contents()->GetRenderViewHost()->GetRoutingID(); + host_zoom_map_->SetTemporaryZoomLevel(old_process_id_, old_view_id_, level); + // Notify observers of zoom level changes. + for (Observer& observer : observers_) + observer.OnZoomLevelChanged(web_contents(), level, true); +} + +bool WebContentsZoomController::UsesTemporaryZoomLevel() { + int render_process_id = web_contents()->GetRenderProcessHost()->GetID(); + int render_view_id = web_contents()->GetRenderViewHost()->GetRoutingID(); + return host_zoom_map_->UsesTemporaryZoomLevel(render_process_id, + render_view_id); +} + +void WebContentsZoomController::DidFinishNavigation( + content::NavigationHandle* navigation_handle) { + if (!navigation_handle->IsInMainFrame() || !navigation_handle->HasCommitted()) + return; + + if (navigation_handle->IsErrorPage()) { + content::HostZoomMap::SendErrorPageZoomLevelRefresh(web_contents()); + return; + } + + SetZoomFactorOnNavigationIfNeeded(navigation_handle->GetURL()); +} + +void WebContentsZoomController::WebContentsDestroyed() { + observers_.Clear(); + embedder_zoom_controller_ = nullptr; +} + +void WebContentsZoomController::RenderFrameHostChanged( + content::RenderFrameHost* old_host, + content::RenderFrameHost* new_host) { + // If our associated HostZoomMap changes, update our event subscription. + content::HostZoomMap* new_host_zoom_map = + content::HostZoomMap::GetForWebContents(web_contents()); + if (new_host_zoom_map == host_zoom_map_) + return; + + host_zoom_map_ = new_host_zoom_map; +} + +void WebContentsZoomController::SetZoomFactorOnNavigationIfNeeded( + const GURL& url) { + if (content::ZoomValuesEqual(GetDefaultZoomFactor(), content::kEpsilon)) + return; + + if (host_zoom_map_->UsesTemporaryZoomLevel(old_process_id_, old_view_id_)) { + host_zoom_map_->ClearTemporaryZoomLevel(old_process_id_, old_view_id_); + } + + if (embedder_zoom_controller_ && + embedder_zoom_controller_->UsesTemporaryZoomLevel()) { + double level = embedder_zoom_controller_->GetZoomLevel(); + SetTemporaryZoomLevel(level); + return; + } + + // When kZoomFactor is available, it takes precedence over + // pref store values but if the host has zoom factor set explicitly + // then it takes precendence. + // pref store < kZoomFactor < setZoomLevel + std::string host = net::GetHostOrSpecFromURL(url); + std::string scheme = url.scheme(); + double zoom_factor = GetDefaultZoomFactor(); + double zoom_level = content::ZoomFactorToZoomLevel(zoom_factor); + if (host_zoom_map_->HasZoomLevel(scheme, host)) { + zoom_level = host_zoom_map_->GetZoomLevelForHostAndScheme(scheme, host); + } + if (content::ZoomValuesEqual(zoom_level, GetZoomLevel())) + return; + + SetZoomLevel(zoom_level); +} + +} // namespace atom diff --git a/atom/browser/web_contents_zoom_controller.h b/atom/browser/web_contents_zoom_controller.h new file mode 100644 index 0000000000..a0d28ad9b9 --- /dev/null +++ b/atom/browser/web_contents_zoom_controller.h @@ -0,0 +1,79 @@ +// 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_WEB_CONTENTS_ZOOM_CONTROLLER_H_ +#define ATOM_BROWSER_WEB_CONTENTS_ZOOM_CONTROLLER_H_ + +#include +#include + +#include "content/public/browser/host_zoom_map.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" + +namespace atom { + +// Manages the zoom changes of WebContents. +class WebContentsZoomController + : public content::WebContentsObserver, + public content::WebContentsUserData { + public: + class Observer { + public: + virtual void OnZoomLevelChanged(content::WebContents* web_contents, + double level, + bool is_temporary) {} + + protected: + virtual ~Observer() {} + }; + + explicit WebContentsZoomController(content::WebContents* web_contents); + ~WebContentsZoomController() override; + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + void SetEmbedderZoomController(WebContentsZoomController* controller); + + // Methods for managing zoom levels. + void SetZoomLevel(double level); + double GetZoomLevel(); + void SetDefaultZoomFactor(double factor); + double GetDefaultZoomFactor(); + void SetTemporaryZoomLevel(double level); + bool UsesTemporaryZoomLevel(); + + protected: + // content::WebContentsObserver: + void DidFinishNavigation(content::NavigationHandle* handle) override; + void WebContentsDestroyed() override; + void RenderFrameHostChanged(content::RenderFrameHost* old_host, + content::RenderFrameHost* new_host) override; + + private: + friend class content::WebContentsUserData; + + // Called after a navigation has committed to set default zoom factor. + void SetZoomFactorOnNavigationIfNeeded(const GURL& url); + + // kZoomFactor. + double default_zoom_factor_; + double temporary_zoom_level_; + + int old_process_id_; + int old_view_id_; + + WebContentsZoomController* embedder_zoom_controller_; + + base::ObserverList observers_; + + content::HostZoomMap* host_zoom_map_; + + DISALLOW_COPY_AND_ASSIGN(WebContentsZoomController); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_WEB_CONTENTS_ZOOM_CONTROLLER_H_ diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index e3942f1f71..fcd598b1aa 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -17,6 +17,7 @@ #include "chrome/common/pref_names.h" #include "components/prefs/pref_service.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "content/public/common/file_chooser_file_info.h" @@ -26,6 +27,91 @@ namespace { +class FileSelectHelper : public base::RefCounted, + public content::WebContentsObserver { + public: + FileSelectHelper(content::RenderFrameHost* render_frame_host, + const content::FileChooserParams::Mode& mode) + : render_frame_host_(render_frame_host), mode_(mode) { + auto web_contents = content::WebContents::FromRenderFrameHost( + render_frame_host); + content::WebContentsObserver::Observe(web_contents); + } + + void ShowOpenDialog(const file_dialog::DialogSettings& settings) { + auto callback = base::Bind(&FileSelectHelper::OnOpenDialogDone, this); + file_dialog::ShowOpenDialog(settings, callback); + } + + void ShowSaveDialog(const file_dialog::DialogSettings& settings) { + auto callback = base::Bind(&FileSelectHelper::OnSaveDialogDone, this); + file_dialog::ShowSaveDialog(settings, callback); + } + + private: + friend class base::RefCounted; + + ~FileSelectHelper() override {} + + void OnOpenDialogDone(bool result, const std::vector& paths) { + std::vector file_info; + if (result) { + for (auto& path : paths) { + content::FileChooserFileInfo info; + info.file_path = path; + info.display_name = path.BaseName().value(); + file_info.push_back(info); + } + + if (render_frame_host_ && !paths.empty()) { + auto browser_context = static_cast( + render_frame_host_->GetProcess()->GetBrowserContext()); + browser_context->prefs()->SetFilePath(prefs::kSelectFileLastDirectory, + paths[0].DirName()); + } + } + OnFilesSelected(file_info); + } + + void OnSaveDialogDone(bool result, const base::FilePath& path) { + std::vector file_info; + if (result) { + content::FileChooserFileInfo info; + info.file_path = path; + info.display_name = path.BaseName().value(); + file_info.push_back(info); + } + OnFilesSelected(file_info); + } + + void OnFilesSelected( + const std::vector& file_info) { + if (render_frame_host_) + render_frame_host_->FilesSelectedInChooser(file_info, mode_); + } + + // content::WebContentsObserver: + void RenderFrameHostChanged(content::RenderFrameHost* old_host, + content::RenderFrameHost* new_host) override { + if (old_host == render_frame_host_) + render_frame_host_ = nullptr; + } + + // content::WebContentsObserver: + void RenderFrameDeleted(content::RenderFrameHost* deleted_host) override { + if (deleted_host == render_frame_host_) + render_frame_host_ = nullptr; + } + + // content::WebContentsObserver: + void WebContentsDestroyed() override { + render_frame_host_ = nullptr; + } + + content::RenderFrameHost* render_frame_host_; + content::FileChooserParams::Mode mode_; +}; + file_dialog::Filters GetFileTypesFromAcceptType( const std::vector& accept_types) { file_dialog::Filters filters; @@ -81,21 +167,17 @@ void WebDialogHelper::RunFileChooser( content::RenderFrameHost* render_frame_host, const content::FileChooserParams& params) { std::vector result; - file_dialog::Filters filters = GetFileTypesFromAcceptType( - params.accept_types); + + file_dialog::DialogSettings settings; + settings.filters = GetFileTypesFromAcceptType(params.accept_types); + settings.parent_window = window_; + settings.title = base::UTF16ToUTF8(params.title); + + scoped_refptr file_select_helper( + new FileSelectHelper(render_frame_host, params.mode)); if (params.mode == content::FileChooserParams::Save) { - base::FilePath path; - if (file_dialog::ShowSaveDialog(window_, - base::UTF16ToUTF8(params.title), - "", - params.default_file_name, - filters, - &path)) { - content::FileChooserFileInfo info; - info.file_path = path; - info.display_name = path.BaseName().value(); - result.push_back(info); - } + settings.default_path = params.default_file_name; + file_select_helper->ShowSaveDialog(settings); } else { int flags = file_dialog::FILE_DIALOG_CREATE_DIRECTORY; switch (params.mode) { @@ -111,32 +193,13 @@ void WebDialogHelper::RunFileChooser( NOTREACHED(); } - std::vector paths; AtomBrowserContext* browser_context = static_cast( window_->web_contents()->GetBrowserContext()); - base::FilePath default_file_path = browser_context->prefs()->GetFilePath( + settings.default_path = browser_context->prefs()->GetFilePath( prefs::kSelectFileLastDirectory).Append(params.default_file_name); - if (file_dialog::ShowOpenDialog(window_, - base::UTF16ToUTF8(params.title), - "", - default_file_path, - filters, - flags, - &paths)) { - for (auto& path : paths) { - content::FileChooserFileInfo info; - info.file_path = path; - info.display_name = path.BaseName().value(); - result.push_back(info); - } - if (!paths.empty()) { - browser_context->prefs()->SetFilePath(prefs::kSelectFileLastDirectory, - paths[0].DirName()); - } - } + settings.properties = flags; + file_select_helper->ShowOpenDialog(settings); } - - render_frame_host->FilesSelectedInChooser(result, params.mode); } void WebDialogHelper::EnumerateDirectory(content::WebContents* web_contents, diff --git a/atom/browser/web_view_guest_delegate.cc b/atom/browser/web_view_guest_delegate.cc index cfb3264963..21b917ac61 100644 --- a/atom/browser/web_view_guest_delegate.cc +++ b/atom/browser/web_view_guest_delegate.cc @@ -23,11 +23,11 @@ const int kDefaultHeight = 300; } // namespace WebViewGuestDelegate::WebViewGuestDelegate() - : guest_host_(nullptr), + : embedder_zoom_controller_(nullptr), + guest_host_(nullptr), auto_size_enabled_(false), is_full_page_plugin_(false), - api_web_contents_(nullptr) { -} + api_web_contents_(nullptr) {} WebViewGuestDelegate::~WebViewGuestDelegate() { } @@ -39,6 +39,10 @@ void WebViewGuestDelegate::Initialize(api::WebContents* api_web_contents) { void WebViewGuestDelegate::Destroy() { // Give the content module an opportunity to perform some cleanup. + if (embedder_zoom_controller_) { + embedder_zoom_controller_->RemoveObserver(this); + embedder_zoom_controller_ = nullptr; + } guest_host_->WillDestroy(); guest_host_ = nullptr; } @@ -107,6 +111,11 @@ void WebViewGuestDelegate::DidFinishNavigation( void WebViewGuestDelegate::DidAttach(int guest_proxy_routing_id) { api_web_contents_->Emit("did-attach"); + embedder_zoom_controller_ = + WebContentsZoomController::FromWebContents(embedder_web_contents_); + auto zoom_controller = api_web_contents_->GetZoomController(); + embedder_zoom_controller_->AddObserver(this); + zoom_controller->SetEmbedderZoomController(embedder_zoom_controller_); } content::WebContents* WebViewGuestDelegate::GetOwnerWebContents() const { @@ -134,6 +143,22 @@ void WebViewGuestDelegate::WillAttach( completion_callback.Run(); } +void WebViewGuestDelegate::OnZoomLevelChanged( + content::WebContents* web_contents, + double level, + bool is_temporary) { + if (web_contents == GetOwnerWebContents()) { + if (is_temporary) { + api_web_contents_->GetZoomController()->SetTemporaryZoomLevel(level); + } else { + api_web_contents_->GetZoomController()->SetZoomLevel(level); + } + // Change the default zoom factor to match the embedders' new zoom level. + double zoom_factor = content::ZoomLevelToZoomFactor(level); + api_web_contents_->GetZoomController()->SetDefaultZoomFactor(zoom_factor); + } +} + void WebViewGuestDelegate::GuestSizeChangedDueToAutoSize( const gfx::Size& old_size, const gfx::Size& new_size) { api_web_contents_->Emit("size-changed", diff --git a/atom/browser/web_view_guest_delegate.h b/atom/browser/web_view_guest_delegate.h index eade31234c..329b7ec317 100644 --- a/atom/browser/web_view_guest_delegate.h +++ b/atom/browser/web_view_guest_delegate.h @@ -5,6 +5,7 @@ #ifndef ATOM_BROWSER_WEB_VIEW_GUEST_DELEGATE_H_ #define ATOM_BROWSER_WEB_VIEW_GUEST_DELEGATE_H_ +#include "atom/browser/web_contents_zoom_controller.h" #include "content/public/browser/browser_plugin_guest_delegate.h" #include "content/public/browser/web_contents_observer.h" @@ -31,7 +32,8 @@ struct SetSizeParams { }; class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, - public content::WebContentsObserver { + public content::WebContentsObserver, + public WebContentsZoomController::Observer { public: WebViewGuestDelegate(); ~WebViewGuestDelegate() override; @@ -63,6 +65,11 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, content::RenderWidgetHost* GetOwnerRenderWidgetHost() override; content::SiteInstance* GetOwnerSiteInstance() override; + // WebContentsZoomController::Observer: + void OnZoomLevelChanged(content::WebContents* web_contents, + double level, + bool is_temporary) override; + private: // This method is invoked when the contents auto-resized to give the container // an opportunity to match it if it wishes. @@ -78,6 +85,10 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, // The WebContents that attaches this guest view. content::WebContents* embedder_web_contents_; + // The zoom controller of the embedder that is used + // to subscribe for zoom changes. + WebContentsZoomController* embedder_zoom_controller_; + // The size of the container element. gfx::Size element_size_; diff --git a/atom/browser/window_list.cc b/atom/browser/window_list.cc index b835e07e75..374389e0a7 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 d9b307352e..e336c8073d 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/api_messages.h b/atom/common/api/api_messages.h index ab27d5a251..ef945d9eeb 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -41,3 +41,11 @@ IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions, // Update renderer process preferences. IPC_MESSAGE_CONTROL1(AtomMsg_UpdatePreferences, base::ListValue) + +// Sent by renderer to set the temporary zoom level. +IPC_SYNC_MESSAGE_ROUTED1_1(AtomViewHostMsg_SetTemporaryZoomLevel, + double /* zoom level */, + double /* result */) + +// Sent by renderer to get the zoom level. +IPC_SYNC_MESSAGE_ROUTED0_1(AtomViewHostMsg_GetZoomLevel, double /* result */) diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index e94099bacf..3151da6f17 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -141,15 +141,16 @@ void InitAsarSupport(v8::Isolate* isolate, v8::Local result = asar_init->Run(); // Initialize asar support. - base::Callback, - v8::Local, - std::string)> init; - if (mate::ConvertFromV8(isolate, result, &init)) { + if (result->IsFunction()) { const char* asar_native = reinterpret_cast( static_cast(node::asar_data)); - init.Run(process, - require, - std::string(asar_native, sizeof(node::asar_data) - 1)); + base::StringPiece asar_data(asar_native, sizeof(node::asar_data) - 1); + v8::Local args[] = { + process, + require, + mate::ConvertToV8(isolate, asar_data), + }; + result.As()->Call(result, 3, args); } } diff --git a/atom/common/api/atom_api_clipboard.cc b/atom/common/api/atom_api_clipboard.cc index d2de0f5166..5ae318510e 100644 --- a/atom/common/api/atom_api_clipboard.cc +++ b/atom/common/api/atom_api_clipboard.cc @@ -37,8 +37,7 @@ bool Clipboard::Has(const std::string& format_string, mate::Arguments* args) { return clipboard->IsFormatAvailable(format, GetClipboardType(args)); } -std::string Clipboard::Read(const std::string& format_string, - mate::Arguments* args) { +std::string Clipboard::Read(const std::string& format_string) { ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); ui::Clipboard::FormatType format(ui::Clipboard::GetFormatType(format_string)); @@ -47,6 +46,13 @@ std::string Clipboard::Read(const std::string& format_string, return data; } +v8::Local Clipboard::ReadBuffer(const std::string& format_string, + mate::Arguments* args) { + std::string data = Read(format_string); + return node::Buffer::Copy( + args->isolate(), data.data(), data.length()).ToLocalChecked(); +} + void Clipboard::Write(const mate::Dictionary& data, mate::Arguments* args) { ui::ScopedClipboardWriter writer(GetClipboardType(args)); base::string16 text, html, bookmark; @@ -184,6 +190,7 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("writeImage", &atom::api::Clipboard::WriteImage); dict.SetMethod("readFindText", &atom::api::Clipboard::ReadFindText); dict.SetMethod("writeFindText", &atom::api::Clipboard::WriteFindText); + dict.SetMethod("readBuffer", &atom::api::Clipboard::ReadBuffer); dict.SetMethod("clear", &atom::api::Clipboard::Clear); // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings diff --git a/atom/common/api/atom_api_clipboard.h b/atom/common/api/atom_api_clipboard.h index e46e035b31..d7cbe7a42a 100644 --- a/atom/common/api/atom_api_clipboard.h +++ b/atom/common/api/atom_api_clipboard.h @@ -24,8 +24,7 @@ class Clipboard { static bool Has(const std::string& format_string, mate::Arguments* args); static void Clear(mate::Arguments* args); - static std::string Read(const std::string& format_string, - mate::Arguments* args); + static std::string Read(const std::string& format_string); static void Write(const mate::Dictionary& data, mate::Arguments* args); static base::string16 ReadText(mate::Arguments* args); @@ -48,6 +47,9 @@ class Clipboard { static base::string16 ReadFindText(); static void WriteFindText(const base::string16& text); + static v8::Local ReadBuffer(const std::string& format_string, + mate::Arguments* args); + private: DISALLOW_COPY_AND_ASSIGN(Clipboard); }; diff --git a/atom/common/api/atom_api_crash_reporter.cc b/atom/common/api/atom_api_crash_reporter.cc index aaaf200b32..0edd787e55 100644 --- a/atom/common/api/atom_api_crash_reporter.cc +++ b/atom/common/api/atom_api_crash_reporter.cc @@ -31,19 +31,27 @@ struct Converter { namespace { +void SetExtraParameter(const std::string& key, mate::Arguments* args) { + std::string value; + if (args->GetNext(&value)) + CrashReporter::GetInstance()->SetExtraParameter(key, value); + else + CrashReporter::GetInstance()->RemoveExtraParameter(key); +} + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); - auto report = base::Unretained(CrashReporter::GetInstance()); - dict.SetMethod("start", - base::Bind(&CrashReporter::Start, report)); - dict.SetMethod("_getUploadedReports", - base::Bind(&CrashReporter::GetUploadedReports, report)); - dict.SetMethod("_setUploadToServer", - base::Bind(&CrashReporter::SetUploadToServer, report)); - dict.SetMethod("_getUploadToServer", - base::Bind(&CrashReporter::GetUploadToServer, report)); + auto reporter = base::Unretained(CrashReporter::GetInstance()); + dict.SetMethod("start", base::Bind(&CrashReporter::Start, reporter)); + dict.SetMethod("setExtraParameter", &SetExtraParameter); + dict.SetMethod("getUploadedReports", + base::Bind(&CrashReporter::GetUploadedReports, reporter)); + dict.SetMethod("setUploadToServer", + base::Bind(&CrashReporter::SetUploadToServer, reporter)); + dict.SetMethod("getUploadToServer", + base::Bind(&CrashReporter::GetUploadToServer, reporter)); } } // namespace diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 0c174941db..6811ab9193 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -12,15 +12,14 @@ #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" -#include "base/base64.h" #include "base/files/file_util.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" -#include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/base/data_url.h" #include "third_party/skia/include/core/SkPixelRef.h" #include "ui/base/layout.h" +#include "ui/base/webui/web_ui_util.h" #include "ui/gfx/codec/jpeg_codec.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/geometry/size.h" @@ -76,6 +75,15 @@ float GetScaleFactorFromPath(const base::FilePath& path) { return 1.0f; } +// Get the scale factor from options object at the first argument +float GetScaleFactorFromOptions(mate::Arguments* args) { + float scale_factor = 1.0f; + mate::Dictionary options; + if (args->GetNext(&options)) + options.Get("scaleFactor", &scale_factor); + return scale_factor; +} + bool AddImageSkiaRep(gfx::ImageSkia* image, const unsigned char* data, size_t size, @@ -230,21 +238,40 @@ HICON NativeImage::GetHICON(int size) { } #endif -v8::Local NativeImage::ToPNG(v8::Isolate* isolate) { - scoped_refptr png = image_.As1xPNGBytes(); - return node::Buffer::Copy(isolate, - reinterpret_cast(png->front()), - static_cast(png->size())).ToLocalChecked(); +v8::Local NativeImage::ToPNG(mate::Arguments* args) { + float scale_factor = GetScaleFactorFromOptions(args); + + if (scale_factor == 1.0f) { + // Use raw 1x PNG bytes when available + scoped_refptr png = image_.As1xPNGBytes(); + if (png->size() > 0) { + const char* data = reinterpret_cast(png->front()); + size_t size = png->size(); + return node::Buffer::Copy(args->isolate(), data, size).ToLocalChecked(); + } + } + + const SkBitmap bitmap = + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); + std::unique_ptr> encoded( + new std::vector()); + gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, encoded.get()); + const char* data = reinterpret_cast(encoded->data()); + size_t size = encoded->size(); + return node::Buffer::Copy(args->isolate(), data, size).ToLocalChecked(); } -v8::Local NativeImage::ToBitmap(v8::Isolate* isolate) { - if (IsEmpty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); +v8::Local NativeImage::ToBitmap(mate::Arguments* args) { + float scale_factor = GetScaleFactorFromOptions(args); - const SkBitmap* bitmap = image_.ToSkBitmap(); - SkPixelRef* ref = bitmap->pixelRef(); - return node::Buffer::Copy(isolate, + const SkBitmap bitmap = + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); + SkPixelRef* ref = bitmap.pixelRef(); + if (!ref) + return node::Buffer::New(args->isolate(), 0).ToLocalChecked(); + return node::Buffer::Copy(args->isolate(), reinterpret_cast(ref->pixels()), - bitmap->getSafeSize()).ToLocalChecked(); + bitmap.getSafeSize()).ToLocalChecked(); } v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { @@ -253,26 +280,34 @@ v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { return node::Buffer::Copy( isolate, reinterpret_cast(&output.front()), - static_cast(output.size())).ToLocalChecked(); + output.size()).ToLocalChecked(); } -std::string NativeImage::ToDataURL() { - scoped_refptr png = image_.As1xPNGBytes(); - std::string data_url; - data_url.insert(data_url.end(), png->front(), png->front() + png->size()); - base::Base64Encode(data_url, &data_url); - data_url.insert(0, "data:image/png;base64,"); - return data_url; +std::string NativeImage::ToDataURL(mate::Arguments* args) { + float scale_factor = GetScaleFactorFromOptions(args); + + if (scale_factor == 1.0f) { + // Use raw 1x PNG bytes when available + scoped_refptr png = image_.As1xPNGBytes(); + if (png->size() > 0) + return webui::GetPngDataUrl(png->front(), png->size()); + } + + return webui::GetBitmapDataUrl( + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap()); } -v8::Local NativeImage::GetBitmap(v8::Isolate* isolate) { - if (IsEmpty()) return node::Buffer::New(isolate, 0).ToLocalChecked(); +v8::Local NativeImage::GetBitmap(mate::Arguments* args) { + float scale_factor = GetScaleFactorFromOptions(args); - const SkBitmap* bitmap = image_.ToSkBitmap(); - SkPixelRef* ref = bitmap->pixelRef(); - return node::Buffer::New(isolate, + const SkBitmap bitmap = + image_.AsImageSkia().GetRepresentation(scale_factor).sk_bitmap(); + SkPixelRef* ref = bitmap.pixelRef(); + if (!ref) + return node::Buffer::New(args->isolate(), 0).ToLocalChecked(); + return node::Buffer::New(args->isolate(), reinterpret_cast(ref->pixels()), - bitmap->getSafeSize(), + bitmap.getSafeSize(), &Noop, nullptr).ToLocalChecked(); } @@ -351,6 +386,46 @@ mate::Handle NativeImage::Crop(v8::Isolate* isolate, new NativeImage(isolate, gfx::Image(cropped))); } +void NativeImage::AddRepresentation(const mate::Dictionary& options) { + int width = 0; + int height = 0; + float scale_factor = 1.0f; + options.Get("width", &width); + options.Get("height", &height); + options.Get("scaleFactor", &scale_factor); + + bool skia_rep_added = false; + gfx::ImageSkia image_skia = image_.AsImageSkia(); + + v8::Local buffer; + GURL url; + if (options.Get("buffer", &buffer) && node::Buffer::HasInstance(buffer)) { + AddImageSkiaRep( + &image_skia, + reinterpret_cast(node::Buffer::Data(buffer)), + node::Buffer::Length(buffer), + width, height, scale_factor); + skia_rep_added = true; + } else if (options.Get("dataURL", &url)) { + std::string mime_type, charset, data; + if (net::DataURL::Parse(url, &mime_type, &charset, &data)) { + if (mime_type == "image/png" || mime_type == "image/jpeg") { + AddImageSkiaRep( + &image_skia, + reinterpret_cast(data.c_str()), + data.size(), + width, height, scale_factor); + skia_rep_added = true; + } + } + } + + // Re-initialize image when first representation is added to an empty image + if (skia_rep_added && IsEmpty()) { + gfx::Image image(image_skia); + image_.SwapRepresentations(&image); + } +} #if !defined(OS_MACOSX) void NativeImage::SetTemplateImage(bool setAsTemplate) { @@ -468,6 +543,7 @@ void NativeImage::BuildPrototype( .SetMethod("resize", &NativeImage::Resize) .SetMethod("crop", &NativeImage::Crop) .SetMethod("getAspectRatio", &NativeImage::GetAspectRatio) + .SetMethod("addRepresentation", &NativeImage::AddRepresentation) // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings .SetMethod("toPng", &NativeImage::ToPNG) .SetMethod("toJpeg", &NativeImage::ToJPEG); diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index ee1b5f5d4b..6ff7a0e29f 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -9,6 +9,7 @@ #include #include "base/values.h" +#include "native_mate/dictionary.h" #include "native_mate/handle.h" #include "native_mate/wrappable.h" #include "ui/gfx/geometry/rect.h" @@ -70,10 +71,10 @@ class NativeImage : public mate::Wrappable { ~NativeImage() override; private: - v8::Local ToPNG(v8::Isolate* isolate); + v8::Local ToPNG(mate::Arguments* args); v8::Local ToJPEG(v8::Isolate* isolate, int quality); - v8::Local ToBitmap(v8::Isolate* isolate); - v8::Local GetBitmap(v8::Isolate* isolate); + v8::Local ToBitmap(mate::Arguments* args); + v8::Local GetBitmap(mate::Arguments* args); v8::Local GetNativeHandle( v8::Isolate* isolate, mate::Arguments* args); @@ -81,10 +82,11 @@ class NativeImage : public mate::Wrappable { const base::DictionaryValue& options); mate::Handle Crop(v8::Isolate* isolate, const gfx::Rect& rect); - std::string ToDataURL(); + std::string ToDataURL(mate::Arguments* args); bool IsEmpty(); gfx::Size GetSize(); float GetAspectRatio(); + void AddRepresentation(const mate::Dictionary& options); // Mark the image as template image. void SetTemplateImage(bool setAsTemplate); diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index 2b4bec6328..f4a6453152 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -13,7 +13,7 @@ #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/node_includes.h" #include "base/logging.h" -#include "base/process/process_metrics.h" +#include "base/sys_info.h" #include "native_mate/dictionary.h" namespace atom { @@ -23,51 +23,6 @@ namespace { // Dummy class type that used for crashing the program. struct DummyClass { bool crash; }; -void Hang() { - for (;;) - base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); -} - -v8::Local GetProcessMemoryInfo(v8::Isolate* isolate) { - std::unique_ptr metrics( - base::ProcessMetrics::CreateCurrentProcessMetrics()); - - mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); - dict.Set("workingSetSize", - static_cast(metrics->GetWorkingSetSize() >> 10)); - dict.Set("peakWorkingSetSize", - static_cast(metrics->GetPeakWorkingSetSize() >> 10)); - - size_t private_bytes, shared_bytes; - if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes)) { - dict.Set("privateBytes", static_cast(private_bytes >> 10)); - dict.Set("sharedBytes", static_cast(shared_bytes >> 10)); - } - - return dict.GetHandle(); -} - -v8::Local GetSystemMemoryInfo(v8::Isolate* isolate, - mate::Arguments* args) { - base::SystemMemoryInfoKB mem_info; - if (!base::GetSystemMemoryInfo(&mem_info)) { - args->ThrowError("Unable to retrieve system memory information"); - return v8::Undefined(isolate); - } - - mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); - dict.Set("total", mem_info.total); - dict.Set("free", mem_info.free); - - // NB: These return bogus values on macOS -#if !defined(OS_MACOSX) - dict.Set("swapTotal", mem_info.swap_total); - dict.Set("swapFree", mem_info.swap_free); -#endif - - return dict.GetHandle(); -} - // Called when there is a fatal error in V8, we just crash the process here so // we can get the stack trace. void FatalErrorCallback(const char* location, const char* message) { @@ -78,12 +33,14 @@ void FatalErrorCallback(const char* location, const char* message) { } // namespace -AtomBindings::AtomBindings() { - uv_async_init(uv_default_loop(), &call_next_tick_async_, OnCallNextTick); +AtomBindings::AtomBindings(uv_loop_t* loop) { + uv_async_init(loop, &call_next_tick_async_, OnCallNextTick); call_next_tick_async_.data = this; + metrics_ = base::ProcessMetrics::CreateCurrentProcessMetrics(); } AtomBindings::~AtomBindings() { + uv_close(reinterpret_cast(&call_next_tick_async_), nullptr); } void AtomBindings::BindTo(v8::Isolate* isolate, @@ -96,6 +53,9 @@ void AtomBindings::BindTo(v8::Isolate* isolate, dict.SetMethod("log", &Log); dict.SetMethod("getProcessMemoryInfo", &GetProcessMemoryInfo); dict.SetMethod("getSystemMemoryInfo", &GetSystemMemoryInfo); + dict.SetMethod("getCPUUsage", + base::Bind(&AtomBindings::GetCPUUsage, base::Unretained(this))); + dict.SetMethod("getIOCounters", &GetIOCounters); #if defined(OS_POSIX) dict.SetMethod("setFdLimit", &base::SetFdLimit); #endif @@ -117,6 +77,13 @@ void AtomBindings::BindTo(v8::Isolate* isolate, } } +void AtomBindings::EnvironmentDestroyed(node::Environment* env) { + auto it = std::find(pending_next_ticks_.begin(), pending_next_ticks_.end(), + env); + if (it != pending_next_ticks_.end()) + pending_next_ticks_.erase(it); +} + void AtomBindings::ActivateUVLoop(v8::Isolate* isolate) { node::Environment* env = node::Environment::GetCurrent(isolate); if (std::find(pending_next_ticks_.begin(), pending_next_ticks_.end(), env) != @@ -160,4 +127,81 @@ void AtomBindings::Crash() { static_cast(nullptr)->crash = true; } +// static +void AtomBindings::Hang() { + for (;;) + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); +} + +// static +v8::Local AtomBindings::GetProcessMemoryInfo(v8::Isolate* isolate) { + std::unique_ptr metrics( + base::ProcessMetrics::CreateCurrentProcessMetrics()); + + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("workingSetSize", + static_cast(metrics->GetWorkingSetSize() >> 10)); + dict.Set("peakWorkingSetSize", + static_cast(metrics->GetPeakWorkingSetSize() >> 10)); + + size_t private_bytes, shared_bytes; + if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes)) { + dict.Set("privateBytes", static_cast(private_bytes >> 10)); + dict.Set("sharedBytes", static_cast(shared_bytes >> 10)); + } + + return dict.GetHandle(); +} + +// static +v8::Local AtomBindings::GetSystemMemoryInfo(v8::Isolate* isolate, + mate::Arguments* args) { + base::SystemMemoryInfoKB mem_info; + if (!base::GetSystemMemoryInfo(&mem_info)) { + args->ThrowError("Unable to retrieve system memory information"); + return v8::Undefined(isolate); + } + + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("total", mem_info.total); + dict.Set("free", mem_info.free); + + // NB: These return bogus values on macOS +#if !defined(OS_MACOSX) + dict.Set("swapTotal", mem_info.swap_total); + dict.Set("swapFree", mem_info.swap_free); +#endif + + return dict.GetHandle(); +} + +v8::Local AtomBindings::GetCPUUsage(v8::Isolate* isolate) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + int processor_count = base::SysInfo::NumberOfProcessors(); + dict.Set("percentCPUUsage", + metrics_->GetPlatformIndependentCPUUsage() / processor_count); + dict.Set("idleWakeupsPerSecond", metrics_->GetIdleWakeupsPerSecond()); + + return dict.GetHandle(); +} + +// static +v8::Local AtomBindings::GetIOCounters(v8::Isolate* isolate) { + std::unique_ptr metrics( + base::ProcessMetrics::CreateCurrentProcessMetrics()); + base::IoCounters io_counters; + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + + if (metrics->GetIOCounters(&io_counters)) { + dict.Set("readOperationCount", io_counters.ReadOperationCount); + dict.Set("writeOperationCount", io_counters.WriteOperationCount); + dict.Set("otherOperationCount", io_counters.OtherOperationCount); + dict.Set("readTransferCount", io_counters.ReadTransferCount); + dict.Set("writeTransferCount", io_counters.WriteTransferCount); + dict.Set("otherTransferCount", io_counters.OtherTransferCount); + } + + return dict.GetHandle(); +} + } // namespace atom diff --git a/atom/common/api/atom_bindings.h b/atom/common/api/atom_bindings.h index 58c2336c0e..a37497ccc5 100644 --- a/atom/common/api/atom_bindings.h +++ b/atom/common/api/atom_bindings.h @@ -8,7 +8,9 @@ #include #include "base/macros.h" +#include "base/process/process_metrics.h" #include "base/strings/string16.h" +#include "native_mate/arguments.h" #include "v8/include/v8.h" #include "vendor/node/deps/uv/include/uv.h" @@ -20,15 +22,24 @@ namespace atom { class AtomBindings { public: - AtomBindings(); + explicit AtomBindings(uv_loop_t* loop); virtual ~AtomBindings(); // Add process.atomBinding function, which behaves like process.binding but // load native code from Electron instead. void BindTo(v8::Isolate* isolate, v8::Local process); + // Should be called when a node::Environment has been destroyed. + void EnvironmentDestroyed(node::Environment* env); + static void Log(const base::string16& message); static void Crash(); + static void Hang(); + static v8::Local GetProcessMemoryInfo(v8::Isolate* isolate); + static v8::Local GetSystemMemoryInfo(v8::Isolate* isolate, + mate::Arguments* args); + v8::Local GetCPUUsage(v8::Isolate* isolate); + static v8::Local GetIOCounters(v8::Isolate* isolate); private: void ActivateUVLoop(v8::Isolate* isolate); @@ -37,6 +48,7 @@ class AtomBindings { uv_async_t call_next_tick_async_; std::list pending_next_ticks_; + std::unique_ptr metrics_; DISALLOW_COPY_AND_ASSIGN(AtomBindings); }; diff --git a/atom/common/api/object_life_monitor.cc b/atom/common/api/object_life_monitor.cc index cd5537e887..cc68130d34 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 73030864b9..e047960e81 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/asar/asar_util.cc b/atom/common/asar/asar_util.cc index 1eee09949a..0ffbfc6c36 100644 --- a/atom/common/asar/asar_util.cc +++ b/atom/common/asar/asar_util.cc @@ -12,6 +12,7 @@ #include "base/files/file_util.h" #include "base/lazy_instance.h" #include "base/stl_util.h" +#include "base/threading/thread_local.h" namespace asar { @@ -19,14 +20,17 @@ namespace { // The global instance of ArchiveMap, will be destroyed on exit. typedef std::map> ArchiveMap; -static base::LazyInstance g_archive_map = LAZY_INSTANCE_INITIALIZER; +base::LazyInstance>::Leaky + g_archive_map_tls = LAZY_INSTANCE_INITIALIZER; const base::FilePath::CharType kAsarExtension[] = FILE_PATH_LITERAL(".asar"); } // namespace std::shared_ptr GetOrCreateAsarArchive(const base::FilePath& path) { - ArchiveMap& archive_map = *g_archive_map.Pointer(); + if (!g_archive_map_tls.Pointer()->Get()) + g_archive_map_tls.Pointer()->Set(new ArchiveMap); + ArchiveMap& archive_map = *g_archive_map_tls.Pointer()->Get(); if (!ContainsKey(archive_map, path)) { std::shared_ptr archive(new Archive(path)); if (!archive->Init()) @@ -36,6 +40,11 @@ std::shared_ptr GetOrCreateAsarArchive(const base::FilePath& path) { return archive_map[path]; } +void ClearArchives() { + if (g_archive_map_tls.Pointer()->Get()) + delete g_archive_map_tls.Pointer()->Get(); +} + bool GetAsarArchivePath(const base::FilePath& full_path, base::FilePath* asar_path, base::FilePath* relative_path) { diff --git a/atom/common/asar/asar_util.h b/atom/common/asar/asar_util.h index 4cb5b88e04..90ffb9b46a 100644 --- a/atom/common/asar/asar_util.h +++ b/atom/common/asar/asar_util.h @@ -19,6 +19,9 @@ class Archive; // Gets or creates a new Archive from the path. std::shared_ptr GetOrCreateAsarArchive(const base::FilePath& path); +// Destroy cached Archive objects. +void ClearArchives(); + // Separates the path to Archive out. bool GetAsarArchivePath(const base::FilePath& full_path, base::FilePath* asar_path, diff --git a/atom/common/atom_constants.cc b/atom/common/atom_constants.cc index f66c947aa2..85307ded9e 100644 --- a/atom/common/atom_constants.cc +++ b/atom/common/atom_constants.cc @@ -24,4 +24,11 @@ const char kSecureProtocolDescription[] = "The connection to this site is using a strong protocol version " "and cipher suite."; +const char kPdfPluginMimeType[] = "application/x-google-chrome-pdf"; +const char kPdfPluginPath[] = "chrome://pdf-viewer/"; +const char kPdfPluginSrc[] = "src"; + +const char kPdfViewerUIOrigin[] = "chrome://pdf-viewer/"; +const char kPdfViewerUIHost[] = "pdf-viewer"; + } // namespace atom diff --git a/atom/common/atom_constants.h b/atom/common/atom_constants.h index b67b7b2e4a..f507214d9d 100644 --- a/atom/common/atom_constants.h +++ b/atom/common/atom_constants.h @@ -20,6 +20,15 @@ extern const char kValidCertificateDescription[]; extern const char kSecureProtocol[]; extern const char kSecureProtocolDescription[]; +// The MIME type used for the PDF plugin. +extern const char kPdfPluginMimeType[]; +extern const char kPdfPluginPath[]; +extern const char kPdfPluginSrc[]; + +// Constants for PDF viewer webui. +extern const char kPdfViewerUIOrigin[]; +extern const char kPdfViewerUIHost[]; + } // namespace atom #endif // ATOM_COMMON_ATOM_CONSTANTS_H_ diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 4355205398..5474abbc93 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -6,8 +6,8 @@ #define ATOM_COMMON_ATOM_VERSION_H_ #define ATOM_MAJOR_VERSION 1 -#define ATOM_MINOR_VERSION 5 -#define ATOM_PATCH_VERSION 1 +#define ATOM_MINOR_VERSION 6 +#define ATOM_PATCH_VERSION 8 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/common_message_generator.h b/atom/common/common_message_generator.h index 6420695686..a63c40b962 100644 --- a/atom/common/common_message_generator.h +++ b/atom/common/common_message_generator.h @@ -9,3 +9,4 @@ #include "chrome/common/tts_messages.h" #include "chrome/common/widevine_cdm_messages.h" #include "chrome/common/chrome_utility_messages.h" +#include "components/pdf/common/pdf_messages.h" diff --git a/atom/common/crash_reporter/crash_reporter.cc b/atom/common/crash_reporter/crash_reporter.cc index f8a5f5e29e..d901f83fa4 100644 --- a/atom/common/crash_reporter/crash_reporter.cc +++ b/atom/common/crash_reporter/crash_reporter.cc @@ -86,6 +86,13 @@ void CrashReporter::InitBreakpad(const std::string& product_name, void CrashReporter::SetUploadParameters() { } +void CrashReporter::SetExtraParameter(const std::string& key, + const std::string& value) { +} + +void CrashReporter::RemoveExtraParameter(const std::string& key) { +} + #if defined(OS_MACOSX) && defined(MAS_BUILD) // static CrashReporter* CrashReporter::GetInstance() { diff --git a/atom/common/crash_reporter/crash_reporter.h b/atom/common/crash_reporter/crash_reporter.h index c564527109..2bdcd9b02d 100644 --- a/atom/common/crash_reporter/crash_reporter.h +++ b/atom/common/crash_reporter/crash_reporter.h @@ -37,6 +37,9 @@ class CrashReporter { virtual void SetUploadToServer(bool upload_to_server); virtual bool GetUploadToServer(); + virtual void SetExtraParameter(const std::string& key, + const std::string& value); + virtual void RemoveExtraParameter(const std::string& key); protected: CrashReporter(); diff --git a/atom/common/crash_reporter/crash_reporter_linux.cc b/atom/common/crash_reporter/crash_reporter_linux.cc index 50e1c5ec86..d14c15de19 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.cc +++ b/atom/common/crash_reporter/crash_reporter_linux.cc @@ -38,7 +38,8 @@ static const off_t kMaxMinidumpFileSize = 1258291; CrashReporterLinux::CrashReporterLinux() : process_start_time_(0), - pid_(getpid()) { + pid_(getpid()), + upload_to_server_(true) { // Set the base process start time value. struct timeval tv; if (!gettimeofday(&tv, NULL)) { @@ -64,19 +65,30 @@ void CrashReporterLinux::InitBreakpad(const std::string& product_name, bool skip_system_crash_handler) { EnableCrashDumping(crashes_dir); - crash_keys_.SetKeyValue("prod", ATOM_PRODUCT_NAME); - crash_keys_.SetKeyValue("ver", version.c_str()); + crash_keys_.reset(new CrashKeyStorage()); + + crash_keys_->SetKeyValue("prod", ATOM_PRODUCT_NAME); + crash_keys_->SetKeyValue("ver", version.c_str()); upload_url_ = submit_url; + upload_to_server_ = upload_to_server; for (StringMap::const_iterator iter = upload_parameters_.begin(); iter != upload_parameters_.end(); ++iter) - crash_keys_.SetKeyValue(iter->first.c_str(), iter->second.c_str()); + crash_keys_->SetKeyValue(iter->first.c_str(), iter->second.c_str()); } void CrashReporterLinux::SetUploadParameters() { upload_parameters_["platform"] = "linux"; } +void CrashReporterLinux::SetUploadToServer(const bool upload_to_server) { + upload_to_server_ = upload_to_server; +} + +bool CrashReporterLinux::GetUploadToServer() { + return upload_to_server_; +} + void CrashReporterLinux::EnableCrashDumping(const base::FilePath& crashes_dir) { base::CreateDirectory(crashes_dir); @@ -115,12 +127,12 @@ bool CrashReporterLinux::CrashDone(const MinidumpDescriptor& minidump, info.fd = minidump.fd(); info.distro = base::g_linux_distro; info.distro_length = my_strlen(base::g_linux_distro); - info.upload = true; + info.upload = self->upload_to_server_; info.process_start_time = self->process_start_time_; info.oom_size = base::g_oom_size; info.pid = self->pid_; info.upload_url = self->upload_url_.c_str(); - info.crash_keys = &self->crash_keys_; + info.crash_keys = self->crash_keys_.get(); HandleCrashDump(info); return true; } diff --git a/atom/common/crash_reporter/crash_reporter_linux.h b/atom/common/crash_reporter/crash_reporter_linux.h index 846e1f1b0a..75a57ec1c1 100644 --- a/atom/common/crash_reporter/crash_reporter_linux.h +++ b/atom/common/crash_reporter/crash_reporter_linux.h @@ -34,7 +34,9 @@ class CrashReporterLinux : public CrashReporter { const base::FilePath& crashes_dir, bool upload_to_server, bool skip_system_crash_handler) override; + void SetUploadToServer(bool upload_to_server) override; void SetUploadParameters() override; + bool GetUploadToServer() override; private: friend struct base::DefaultSingletonTraits; @@ -49,11 +51,12 @@ class CrashReporterLinux : public CrashReporter { const bool succeeded); std::unique_ptr breakpad_; - CrashKeyStorage crash_keys_; + std::unique_ptr crash_keys_; uint64_t process_start_time_; pid_t pid_; std::string upload_url_; + bool upload_to_server_; DISALLOW_COPY_AND_ASSIGN(CrashReporterLinux); }; diff --git a/atom/common/crash_reporter/crash_reporter_mac.h b/atom/common/crash_reporter/crash_reporter_mac.h index 9066f2017b..583c99a86e 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.h +++ b/atom/common/crash_reporter/crash_reporter_mac.h @@ -34,6 +34,9 @@ class CrashReporterMac : public CrashReporter { void SetUploadParameters() override; void SetUploadToServer(bool upload_to_server) override; bool GetUploadToServer() override; + void SetExtraParameter(const std::string& key, + const std::string& value) override; + void RemoveExtraParameter(const std::string& key) override; private: friend struct base::DefaultSingletonTraits; diff --git a/atom/common/crash_reporter/crash_reporter_mac.mm b/atom/common/crash_reporter/crash_reporter_mac.mm index cdaa5d2f52..4b59be5dfc 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -100,6 +100,21 @@ void CrashReporterMac::SetCrashKeyValue(const base::StringPiece& key, simple_string_dictionary_->SetKeyValue(key.data(), value.data()); } +void CrashReporterMac::SetExtraParameter(const std::string& key, + const std::string& value) { + if (simple_string_dictionary_) + SetCrashKeyValue(key, value); + else + upload_parameters_[key] = value; +} + +void CrashReporterMac::RemoveExtraParameter(const std::string& key) { + if (simple_string_dictionary_) + simple_string_dictionary_->RemoveKey(key.data()); + else + upload_parameters_.erase(key); +} + std::vector CrashReporterMac::GetUploadedReports(const base::FilePath& crashes_dir) { std::vector uploaded_reports; diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index 25969dc32e..a7908ef30c 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 3fc139aca1..5070df2060 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 5782fd72a3..a306a567a7 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/callback.cc b/atom/common/native_mate_converters/callback.cc index dc6d30fd23..a6a500be73 100644 --- a/atom/common/native_mate_converters/callback.cc +++ b/atom/common/native_mate_converters/callback.cc @@ -4,8 +4,6 @@ #include "atom/common/native_mate_converters/callback.h" -#include "content/public/browser/browser_thread.h" - using content::BrowserThread; namespace mate { diff --git a/atom/common/native_mate_converters/callback.h b/atom/common/native_mate_converters/callback.h index decc36eb57..28bff3c82a 100644 --- a/atom/common/native_mate_converters/callback.h +++ b/atom/common/native_mate_converters/callback.h @@ -11,6 +11,8 @@ #include "base/bind.h" #include "base/callback.h" #include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "content/public/browser/browser_thread.h" #include "native_mate/function_template.h" #include "native_mate/scoped_persistent.h" diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index 72f1011a72..7869e37dee 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 94fff2ff60..b78bc5b8e1 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 33117ca974..9e3128fdb5 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/native_mate_converters/v8_value_converter.cc b/atom/common/native_mate_converters/v8_value_converter.cc index a064526557..3ba30e14ff 100644 --- a/atom/common/native_mate_converters/v8_value_converter.cc +++ b/atom/common/native_mate_converters/v8_value_converter.cc @@ -129,6 +129,7 @@ class V8ValueConverter::ScopedUniquenessGuard { V8ValueConverter::V8ValueConverter() : reg_exp_allowed_(false), function_allowed_(false), + disable_node_(false), strip_null_from_objects_(false) {} void V8ValueConverter::SetRegExpAllowed(bool val) { @@ -143,6 +144,10 @@ void V8ValueConverter::SetStripNullFromObjects(bool val) { strip_null_from_objects_ = val; } +void V8ValueConverter::SetDisableNode(bool val) { + disable_node_ = val; +} + v8::Local V8ValueConverter::ToV8Value( const base::Value* value, v8::Local context) const { v8::Context::Scope context_scope(context); @@ -249,9 +254,49 @@ v8::Local V8ValueConverter::ToV8Object( v8::Local V8ValueConverter::ToArrayBuffer( v8::Isolate* isolate, const base::BinaryValue* value) const { - return node::Buffer::Copy(isolate, - value->GetBuffer(), - value->GetSize()).ToLocalChecked(); + const char* data = value->GetBuffer(); + size_t length = value->GetSize(); + + if (!disable_node_) { + return node::Buffer::Copy(isolate, data, length).ToLocalChecked(); + } + + if (length > node::Buffer::kMaxLength) { + return v8::Local(); + } + auto context = isolate->GetCurrentContext(); + auto array_buffer = v8::ArrayBuffer::New(isolate, length); + memcpy(array_buffer->GetContents().Data(), data, length); + // From this point, if something goes wrong(can't find Buffer class for + // example) we'll simply return a Uint8Array based on the created ArrayBuffer. + // This can happen if no preload script was specified to the renderer. + mate::Dictionary global(isolate, context->Global()); + v8::Local buffer_value; + + // Get the Buffer class stored as a hidden value in the global object. We'll + // use it return a browserified Buffer. + if (!global.GetHidden("Buffer", &buffer_value) || + !buffer_value->IsFunction()) { + return v8::Uint8Array::New(array_buffer, 0, length); + } + + mate::Dictionary buffer_class(isolate, buffer_value->ToObject()); + v8::Local from_value; + if (!buffer_class.Get("from", &from_value) || + !from_value->IsFunction()) { + return v8::Uint8Array::New(array_buffer, 0, length); + } + + v8::Local args[] = { + array_buffer + }; + auto func = v8::Local::Cast(from_value); + auto result = func->Call(context, v8::Null(isolate), 1, args); + if (!result.IsEmpty()) { + return result.ToLocalChecked(); + } + + return v8::Uint8Array::New(array_buffer, 0, length); } base::Value* V8ValueConverter::FromV8ValueImpl( diff --git a/atom/common/native_mate_converters/v8_value_converter.h b/atom/common/native_mate_converters/v8_value_converter.h index d4ddfd1308..2b8dcf8596 100644 --- a/atom/common/native_mate_converters/v8_value_converter.h +++ b/atom/common/native_mate_converters/v8_value_converter.h @@ -25,6 +25,7 @@ class V8ValueConverter { void SetRegExpAllowed(bool val); void SetFunctionAllowed(bool val); void SetStripNullFromObjects(bool val); + void SetDisableNode(bool val); v8::Local ToV8Value(const base::Value* value, v8::Local context) const; base::Value* FromV8Value(v8::Local value, @@ -64,6 +65,13 @@ class V8ValueConverter { // If true, we will convert Function JavaScript objects to dictionaries. bool function_allowed_; + // If true, will not use node::Buffer::Copy to deserialize byte arrays. + // node::Buffer::Copy depends on a working node.js environment, and this is + // not desirable in sandboxed renderers. That means Buffer instances sent from + // browser process will be deserialized as browserify-based Buffer(which are + // wrappers around Uint8Array). + bool disable_node_; + // If true, undefined and null values are ignored when converting v8 objects // into Values. bool strip_null_from_objects_; diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 0737314d07..ae757b1da8 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -25,8 +25,6 @@ #include "atom/common/node_includes.h" -using content::BrowserThread; - // Force all builtin modules to be referenced so they can actually run their // DSO constructors, see http://git.io/DRIqCg. #define REFERENCE_MODULE(name) \ @@ -35,17 +33,18 @@ using content::BrowserThread; // 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); @@ -98,9 +97,9 @@ base::FilePath GetResourcesPath(bool is_browser) { } // namespace -NodeBindings::NodeBindings(bool is_browser) - : is_browser_(is_browser), - uv_loop_(uv_default_loop()), +NodeBindings::NodeBindings(BrowserEnvironment browser_env) + : browser_env_(browser_env), + uv_loop_(browser_env == WORKER ? uv_loop_new() : uv_default_loop()), embed_closed_(false), uv_env_(nullptr), weak_factory_(this) { @@ -117,16 +116,21 @@ NodeBindings::~NodeBindings() { // Clear uv. uv_sem_destroy(&embed_sem_); + uv_close(reinterpret_cast(&dummy_uv_handle_), nullptr); + + // Destroy loop. + if (uv_loop_ != uv_default_loop()) + uv_loop_delete(uv_loop_); } void NodeBindings::Initialize() { // Open node's error reporting system for browser process. - node::g_standalone_mode = is_browser_; + node::g_standalone_mode = browser_env_ == BROWSER; node::g_upstream_node_mode = false; #if defined(OS_LINUX) // Get real command line in renderer process forked by zygote. - if (!is_browser_) + if (browser_env_ != BROWSER) AtomCommandLine::InitializeFromCommandLine(); #endif @@ -138,7 +142,7 @@ void NodeBindings::Initialize() { // uv_init overrides error mode to suppress the default crash dialog, bring // it back if user wants to show it. std::unique_ptr env(base::Environment::Create()); - if (is_browser_ || env->HasVar("ELECTRON_DEFAULT_ERROR_MODE")) + if (browser_env_ == BROWSER || env->HasVar("ELECTRON_DEFAULT_ERROR_MODE")) SetErrorMode(GetErrorMode() & ~SEM_NOGPFAULTERRORBOX); #endif } @@ -148,9 +152,19 @@ node::Environment* NodeBindings::CreateEnvironment( auto args = AtomCommandLine::argv(); // Feed node the path to initialization script. - base::FilePath::StringType process_type = is_browser_ ? - FILE_PATH_LITERAL("browser") : FILE_PATH_LITERAL("renderer"); - base::FilePath resources_path = GetResourcesPath(is_browser_); + base::FilePath::StringType process_type; + switch (browser_env_) { + case BROWSER: + process_type = FILE_PATH_LITERAL("browser"); + break; + case RENDERER: + process_type = FILE_PATH_LITERAL("renderer"); + break; + case WORKER: + process_type = FILE_PATH_LITERAL("worker"); + break; + } + base::FilePath resources_path = GetResourcesPath(browser_env_ == BROWSER); base::FilePath script_path = resources_path.Append(FILE_PATH_LITERAL("electron.asar")) .Append(process_type) @@ -160,10 +174,10 @@ node::Environment* NodeBindings::CreateEnvironment( std::unique_ptr c_argv = StringVectorToArgArray(args); node::Environment* env = node::CreateEnvironment( - new node::IsolateData(context->GetIsolate(), uv_default_loop()), context, + new node::IsolateData(context->GetIsolate(), uv_loop_), context, args.size(), c_argv.get(), 0, nullptr); - if (is_browser_) { + if (browser_env_ == BROWSER) { // SetAutorunMicrotasks is no longer called in node::CreateEnvironment // so instead call it here to match expected node behavior context->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit); @@ -177,7 +191,7 @@ node::Environment* NodeBindings::CreateEnvironment( process.Set("type", process_type); process.Set("resourcesPath", resources_path); // Do not set DOM globals for renderer process. - if (!is_browser_) + if (browser_env_ != BROWSER) process.Set("_noBrowserGlobals", resources_path); // The path to helper app. base::FilePath helper_exec_path; @@ -186,7 +200,7 @@ node::Environment* NodeBindings::CreateEnvironment( // Set process._debugWaitConnect if --debug-brk was specified to stop // the debugger on the first line - if (is_browser_ && + if (browser_env_ == BROWSER && base::CommandLine::ForCurrentProcess()->HasSwitch("debug-brk")) process.Set("_debugWaitConnect", true); @@ -199,8 +213,6 @@ void NodeBindings::LoadEnvironment(node::Environment* env) { } void NodeBindings::PrepareMessageLoop() { - DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); - // Add dummy handle for libuv, otherwise libuv would quit when there is // nothing to do. uv_async_init(uv_loop_, &dummy_uv_handle_, nullptr); @@ -211,8 +223,6 @@ void NodeBindings::PrepareMessageLoop() { } void NodeBindings::RunMessageLoop() { - DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); - // The MessageLoop should have been created, remember the one in main thread. task_runner_ = base::ThreadTaskRunnerHandle::Get(); @@ -221,10 +231,14 @@ void NodeBindings::RunMessageLoop() { } void NodeBindings::UvRunOnce() { - DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); - 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()); @@ -236,13 +250,13 @@ void NodeBindings::UvRunOnce() { v8::MicrotasksScope script_scope(env->isolate(), v8::MicrotasksScope::kRunMicrotasks); - if (!is_browser_) + if (browser_env_ != BROWSER) TRACE_EVENT_BEGIN0("devtools.timeline", "FunctionCall"); // Deal with uv events. int r = uv_run(uv_loop_, UV_RUN_NOWAIT); - if (!is_browser_) + if (browser_env_ != BROWSER) TRACE_EVENT_END0("devtools.timeline", "FunctionCall"); if (r == 0) diff --git a/atom/common/node_bindings.h b/atom/common/node_bindings.h index 663b0acf07..5047a9afb2 100644 --- a/atom/common/node_bindings.h +++ b/atom/common/node_bindings.h @@ -23,7 +23,13 @@ namespace atom { class NodeBindings { public: - static NodeBindings* Create(bool is_browser); + enum BrowserEnvironment { + BROWSER, + RENDERER, + WORKER, + }; + + static NodeBindings* Create(BrowserEnvironment browser_env); virtual ~NodeBindings(); @@ -46,8 +52,10 @@ class NodeBindings { void set_uv_env(node::Environment* env) { uv_env_ = env; } node::Environment* uv_env() const { return uv_env_; } + uv_loop_t* uv_loop() const { return uv_loop_; } + protected: - explicit NodeBindings(bool is_browser); + explicit NodeBindings(BrowserEnvironment browser_env); // Called to poll events in new thread. virtual void PollEvents() = 0; @@ -61,13 +69,13 @@ class NodeBindings { // Interrupt the PollEvents. void WakeupEmbedThread(); - // Are we running in browser. - bool is_browser_; + // Which environment we are running. + BrowserEnvironment browser_env_; - // Main thread's MessageLoop. + // Current thread's MessageLoop. scoped_refptr task_runner_; - // Main thread's libuv loop. + // Current thread's libuv loop. uv_loop_t* uv_loop_; private: diff --git a/atom/common/node_bindings_linux.cc b/atom/common/node_bindings_linux.cc index 34b9ea9523..3ced7029cb 100644 --- a/atom/common/node_bindings_linux.cc +++ b/atom/common/node_bindings_linux.cc @@ -8,8 +8,8 @@ namespace atom { -NodeBindingsLinux::NodeBindingsLinux(bool is_browser) - : NodeBindings(is_browser), +NodeBindingsLinux::NodeBindingsLinux(BrowserEnvironment browser_env) + : NodeBindings(browser_env), epoll_(epoll_create(1)) { int backend_fd = uv_backend_fd(uv_loop_); struct epoll_event ev = { 0 }; @@ -50,8 +50,8 @@ void NodeBindingsLinux::PollEvents() { } // static -NodeBindings* NodeBindings::Create(bool is_browser) { - return new NodeBindingsLinux(is_browser); +NodeBindings* NodeBindings::Create(BrowserEnvironment browser_env) { + return new NodeBindingsLinux(browser_env); } } // namespace atom diff --git a/atom/common/node_bindings_linux.h b/atom/common/node_bindings_linux.h index 341bf133ed..829a9d84dd 100644 --- a/atom/common/node_bindings_linux.h +++ b/atom/common/node_bindings_linux.h @@ -12,7 +12,7 @@ namespace atom { class NodeBindingsLinux : public NodeBindings { public: - explicit NodeBindingsLinux(bool is_browser); + explicit NodeBindingsLinux(BrowserEnvironment browser_env); virtual ~NodeBindingsLinux(); void RunMessageLoop() override; diff --git a/atom/common/node_bindings_mac.cc b/atom/common/node_bindings_mac.cc index cbcbdba360..e7006a507b 100644 --- a/atom/common/node_bindings_mac.cc +++ b/atom/common/node_bindings_mac.cc @@ -14,8 +14,8 @@ namespace atom { -NodeBindingsMac::NodeBindingsMac(bool is_browser) - : NodeBindings(is_browser) { +NodeBindingsMac::NodeBindingsMac(BrowserEnvironment browser_env) + : NodeBindings(browser_env) { } NodeBindingsMac::~NodeBindingsMac() { @@ -60,8 +60,8 @@ void NodeBindingsMac::PollEvents() { } // static -NodeBindings* NodeBindings::Create(bool is_browser) { - return new NodeBindingsMac(is_browser); +NodeBindings* NodeBindings::Create(BrowserEnvironment browser_env) { + return new NodeBindingsMac(browser_env); } } // namespace atom diff --git a/atom/common/node_bindings_mac.h b/atom/common/node_bindings_mac.h index 96c79ec6f0..6b0082bc22 100644 --- a/atom/common/node_bindings_mac.h +++ b/atom/common/node_bindings_mac.h @@ -12,7 +12,7 @@ namespace atom { class NodeBindingsMac : public NodeBindings { public: - explicit NodeBindingsMac(bool is_browser); + explicit NodeBindingsMac(BrowserEnvironment browser_env); virtual ~NodeBindingsMac(); void RunMessageLoop() override; diff --git a/atom/common/node_bindings_win.cc b/atom/common/node_bindings_win.cc index b8de4f59da..419b0ce4c6 100644 --- a/atom/common/node_bindings_win.cc +++ b/atom/common/node_bindings_win.cc @@ -14,8 +14,8 @@ extern "C" { namespace atom { -NodeBindingsWin::NodeBindingsWin(bool is_browser) - : NodeBindings(is_browser) { +NodeBindingsWin::NodeBindingsWin(BrowserEnvironment browser_env) + : NodeBindings(browser_env) { } NodeBindingsWin::~NodeBindingsWin() { @@ -45,8 +45,8 @@ void NodeBindingsWin::PollEvents() { } // static -NodeBindings* NodeBindings::Create(bool is_browser) { - return new NodeBindingsWin(is_browser); +NodeBindings* NodeBindings::Create(BrowserEnvironment browser_env) { + return new NodeBindingsWin(browser_env); } } // namespace atom diff --git a/atom/common/node_bindings_win.h b/atom/common/node_bindings_win.h index 3950098e5e..793586d88d 100644 --- a/atom/common/node_bindings_win.h +++ b/atom/common/node_bindings_win.h @@ -12,7 +12,7 @@ namespace atom { class NodeBindingsWin : public NodeBindings { public: - explicit NodeBindingsWin(bool is_browser); + explicit NodeBindingsWin(BrowserEnvironment browser_env); virtual ~NodeBindingsWin(); private: diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 4729a28127..2f1c0368f3 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"; @@ -122,6 +125,9 @@ const char kBlinkFeatures[] = "blinkFeatures"; // Disable blink features. const char kDisableBlinkFeatures[] = "disableBlinkFeatures"; +// Enable the node integration in WebWorker. +const char kNodeIntegrationInWorker[] = "nodeIntegrationInWorker"; + } // namespace options namespace switches { @@ -153,9 +159,11 @@ 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 kZoomFactor[] = "zoom-factor"; const char kPreloadScript[] = "preload"; const char kPreloadURL[] = "preload-url"; const char kNodeIntegration[] = "node-integration"; @@ -165,6 +173,9 @@ const char kOpenerID[] = "opener-id"; const char kScrollBounce[] = "scroll-bounce"; const char kHiddenPage[] = "hidden-page"; +// Command switch passed to renderer process to control nodeIntegration. +const char kNodeIntegrationInWorker[] = "node-integration-in-worker"; + // Widevine options // Path to Widevine CDM binaries. const char kWidevineCdmPath[] = "widevine-cdm-path"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index c81ab529cf..69e7af029e 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[]; @@ -62,6 +63,7 @@ extern const char kOpenerID[]; extern const char kScrollBounce[]; extern const char kBlinkFeatures[]; extern const char kDisableBlinkFeatures[]; +extern const char kNodeIntegrationInWorker[]; } // namespace options @@ -79,9 +81,9 @@ 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 kZoomFactor[]; extern const char kPreloadScript[]; extern const char kPreloadURL[]; extern const char kNodeIntegration[]; @@ -90,6 +92,7 @@ extern const char kGuestInstanceID[]; extern const char kOpenerID[]; extern const char kScrollBounce[]; extern const char kHiddenPage[]; +extern const char kNodeIntegrationInWorker[]; extern const char kWidevineCdmPath[]; extern const char kWidevineCdmVersion[]; diff --git a/atom/common/platform_util_linux.cc b/atom/common/platform_util_linux.cc index 923adbd882..5c0eaecdd1 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 aa64678caf..7259b3b130 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/node/osfhandle.cc b/atom/node/osfhandle.cc index cb3dab6139..b0ac8c1ce3 100644 --- a/atom/node/osfhandle.cc +++ b/atom/node/osfhandle.cc @@ -6,6 +6,22 @@ #include +#define U_I18N_IMPLEMENTATION + +#include "third_party/icu/source/common/unicode/ubidi.h" +#include "third_party/icu/source/common/unicode/uchar.h" +#include "third_party/icu/source/common/unicode/uidna.h" +#include "third_party/icu/source/common/unicode/unistr.h" +#include "third_party/icu/source/common/unicode/unorm.h" +#include "third_party/icu/source/common/unicode/urename.h" +#include "third_party/icu/source/common/unicode/ustring.h" +#include "third_party/icu/source/i18n/unicode/measfmt.h" +#include "third_party/icu/source/i18n/unicode/translit.h" +#include "third_party/icu/source/i18n/unicode/ucsdet.h" +#include "third_party/icu/source/i18n/unicode/ulocdata.h" +#include "third_party/icu/source/i18n/unicode/uregex.h" +#include "third_party/icu/source/i18n/unicode/uspoof.h" +#include "third_party/icu/source/i18n/unicode/usearch.h" #include "v8-profiler.h" #include "v8-inspector.h" @@ -21,12 +37,30 @@ int close(int fd) { void ReferenceSymbols() { // Following symbols are used by electron.exe but got stripped by compiler, - // for some reason, adding them to ForceSymbolReferences does not work, - // probably because of VC++ bugs. + // by using the symbols we can force compiler to keep the objects in node.dll, + // thus electron.exe can link with the exported symbols. + + // v8_profiler symbols: v8::TracingCpuProfiler::Create(nullptr); + // v8_inspector symbols: reinterpret_cast(nullptr)-> canDispatchMethod(v8_inspector::StringView()); reinterpret_cast(nullptr)->unmuteMetrics(0); + // icu symbols: + u_errorName(U_ZERO_ERROR); + ubidi_setPara(nullptr, nullptr, 0, 0, nullptr, nullptr); + ucsdet_getName(nullptr, nullptr); + uidna_openUTS46(UIDNA_CHECK_BIDI, nullptr); + ulocdata_close(nullptr); + unorm_normalize(nullptr, 0, UNORM_NFC, 0, nullptr, 0, nullptr); + uregex_matches(nullptr, 0, nullptr); + uspoof_open(nullptr); + usearch_setPattern(nullptr, nullptr, 0, nullptr); + usearch_setPattern(nullptr, nullptr, 0, nullptr); + UMeasureFormatWidth width = UMEASFMT_WIDTH_WIDE; + UErrorCode status = U_ZERO_ERROR; + icu::MeasureFormat format(icu::Locale::getRoot(), width, status); + reinterpret_cast(nullptr)->clone(); } } // namespace node diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index c750a455de..f103b89391 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -4,6 +4,7 @@ #include "atom/renderer/api/atom_api_web_frame.h" +#include "atom/common/api/api_messages.h" #include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/blink_converter.h" #include "atom/common/native_mate_converters/callback.h" @@ -72,13 +73,21 @@ void WebFrame::SetName(const std::string& name) { } double WebFrame::SetZoomLevel(double level) { - double ret = web_frame_->view()->setZoomLevel(level); - mate::EmitEvent(isolate(), GetWrapper(), "zoom-level-changed", ret); - return ret; + double result = 0.0; + content::RenderView* render_view = + content::RenderView::FromWebView(web_frame_->view()); + render_view->Send(new AtomViewHostMsg_SetTemporaryZoomLevel( + render_view->GetRoutingID(), level, &result)); + return result; } double WebFrame::GetZoomLevel() const { - return web_frame_->view()->zoomLevel(); + double result = 0.0; + content::RenderView* render_view = + content::RenderView::FromWebView(web_frame_->view()); + render_view->Send( + new AtomViewHostMsg_GetZoomLevel(render_view->GetRoutingID(), &result)); + return result; } double WebFrame::SetZoomFactor(double factor) { diff --git a/atom/renderer/atom_render_frame_observer.cc b/atom/renderer/atom_render_frame_observer.cc new file mode 100644 index 0000000000..d9a41a48d4 --- /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 0000000000..51cb21b3b0 --- /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_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index b96c6ea672..a68238ba4c 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -14,7 +14,6 @@ #include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_includes.h" -#include "atom/common/options_switches.h" #include "atom/renderer/atom_renderer_client.h" #include "base/command_line.h" #include "base/strings/string_number_conversions.h" @@ -117,17 +116,6 @@ void AtomRenderViewObserver::EmitIPCEvent(blink::WebFrame* frame, void AtomRenderViewObserver::DidCreateDocumentElement( blink::WebLocalFrame* frame) { document_created_ = true; - - // Read --zoom-factor from command line. - std::string zoom_factor_str = base::CommandLine::ForCurrentProcess()-> - GetSwitchValueASCII(switches::kZoomFactor); - if (zoom_factor_str.empty()) - return; - double zoom_factor; - if (!base::StringToDouble(zoom_factor_str, &zoom_factor)) - return; - double zoom_level = blink::WebView::zoomFactorToZoomLevel(zoom_factor); - frame->view()->setZoomLevel(zoom_level); } void AtomRenderViewObserver::DraggableRegionsChanged(blink::WebFrame* frame) { diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 9979aae900..5dafe084ce 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -9,50 +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/color_util.h" -#include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/asar/asar_util.h" +#include "atom/common/atom_constants.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" @@ -60,240 +32,38 @@ 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() { - // This maps to the name shown in the context combo box in the Console tab - // of the dev tools. - render_frame_->GetWebFrame()->setIsolatedWorldHumanReadableName( - World::ISOLATED_WORLD, - blink::WebString::fromUTF8("Electron Isolated Context")); - - blink::WebScriptSource source("void 0"); - render_frame_->GetWebFrame()->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() - : node_bindings_(NodeBindings::Create(false)), - atom_bindings_(new AtomBindings) { + : node_integration_initialized_(false), + node_bindings_(NodeBindings::Create(NodeBindings::RENDERER)), + 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() { + asar::ClearArchives(); } 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"); - - // 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( @@ -316,25 +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 || - 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 @@ -342,11 +93,9 @@ void AtomRendererClient::DidCreateScriptContext( if (!render_frame->IsMainFrame() && !IsDevToolsExtension(render_frame)) return; - // Whether the node binding has been initialized. - bool first_time = node_bindings_->uv_env() == nullptr; - // Prepare the node bindings. - if (first_time) { + if (!node_integration_initialized_) { + node_integration_initialized_ = true; node_bindings_->Initialize(); node_bindings_->PrepareMessageLoop(); } @@ -356,13 +105,12 @@ 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); - if (first_time) { + if (node_bindings_->uv_env() == nullptr) { // Make uv loop being wrapped by window context. node_bindings_->set_uv_env(env); @@ -381,6 +129,14 @@ void AtomRendererClient::WillReleaseScriptContext( node::Environment* env = node::Environment::GetCurrent(context); if (env) mate::EmitEvent(env->isolate(), env->process_object(), "exit"); + + // The main frame may be replaced. + if (env == node_bindings_->uv_env()) + node_bindings_->set_uv_env(nullptr); + + // Destroy the node environment. + node::FreeEnvironment(env); + atom_bindings_->EnvironmentDestroyed(env); } bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, @@ -397,20 +153,20 @@ 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::DidInitializeWorkerContextOnWorkerThread( + v8::Local context) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kNodeIntegrationInWorker)) { + WebWorkerObserver::GetCurrent()->ContextCreated(context); } } -void AtomRendererClient::AddSupportedKeySystems( - std::vector>* key_systems) { - AddChromeKeySystems(key_systems); +void AtomRendererClient::WillDestroyWorkerContextOnWorkerThread( + v8::Local context) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kNodeIntegrationInWorker)) { + WebWorkerObserver::GetCurrent()->ContextWillDestroy(context); + } } v8::Local AtomRendererClient::GetContext( @@ -422,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 a693262fed..3feaff4528 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,29 +48,22 @@ 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( + v8::Local context) override; + + // Whether the node integration has been initialized. + bool node_integration_initialized_; 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 6c3f3e5b49..180d3b75d3 100644 --- a/atom/renderer/atom_sandboxed_renderer_client.cc +++ b/atom/renderer/atom_sandboxed_renderer_client.cc @@ -9,18 +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/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" +#include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "atom/renderer/api/atom_api_renderer_ipc.h" #include "atom/renderer/atom_render_view_observer.h" #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" +#include "native_mate/converter.h" +#include "native_mate/dictionary.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" @@ -31,58 +35,70 @@ namespace atom { namespace { -const std::string kBindingKey = "binding"; +const std::string kIpcKey = "ipcNative"; +const std::string kModuleCacheKey = "native-module-cache"; -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")); +v8::Local GetModuleCache(v8::Isolate* isolate) { + mate::Dictionary global(isolate, isolate->GetCurrentContext()->Global()); + v8::Local cache; + + if (!global.GetHidden(kModuleCacheKey, &cache)) { + cache = v8::Object::New(isolate); + global.SetHidden(kModuleCacheKey, cache); } - 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_); + return cache->ToObject(); +} + +// adapted from node.cc +v8::Local GetBinding(v8::Isolate* isolate, v8::Local key, + mate::Arguments* margs) { + v8::Local exports; + std::string module_key = mate::V8ToString(key); + mate::Dictionary cache(isolate, GetModuleCache(isolate)); + + if (cache.Get(module_key.c_str(), &exports)) { + return exports; } - void WillReleaseScriptContext(v8::Local context, - int world_id) override { - if (world_id_ != world_id) - return; - renderer_client_->WillReleaseScriptContext(context, render_frame_); + auto mod = node::get_builtin_module(module_key.c_str()); + + if (!mod) { + char errmsg[1024]; + snprintf(errmsg, sizeof(errmsg), "No such module: %s", module_key.c_str()); + margs->ThrowError(errmsg); + return exports; } - void OnDestruct() override { - delete this; - } + exports = v8::Object::New(isolate); + DCHECK_EQ(mod->nm_register_func, nullptr); + DCHECK_NE(mod->nm_context_register_func, nullptr); + mod->nm_context_register_func(exports, v8::Null(isolate), + isolate->GetCurrentContext(), mod->nm_priv); + cache.Set(module_key.c_str(), exports); + return exports; +} - private: - content::RenderFrame* render_frame_; - int world_id_; - AtomSandboxedRendererClient* renderer_client_; - - DISALLOW_COPY_AND_ASSIGN(AtomSandboxedRenderFrameObserver); -}; +void InitializeBindings(v8::Local binding, + v8::Local context) { + auto isolate = context->GetIsolate(); + mate::Dictionary b(isolate, binding); + b.SetMethod("get", GetBinding); + b.SetMethod("crash", AtomBindings::Crash); + b.SetMethod("hang", AtomBindings::Hang); + b.SetMethod("getProcessMemoryInfo", &AtomBindings::GetProcessMemoryInfo); + b.SetMethod("getSystemMemoryInfo", &AtomBindings::GetSystemMemoryInfo); +} class AtomSandboxedRenderViewObserver : public AtomRenderViewObserver { public: AtomSandboxedRenderViewObserver(content::RenderView* render_view, AtomSandboxedRendererClient* renderer_client) : AtomRenderViewObserver(render_view, nullptr), + v8_converter_(new atom::V8ValueConverter), renderer_client_(renderer_client) { + v8_converter_->SetDisableNode(true); } protected: @@ -98,15 +114,16 @@ class AtomSandboxedRenderViewObserver : public AtomRenderViewObserver { v8::Context::Scope context_scope(context); v8::Local argv[] = { mate::ConvertToV8(isolate, channel), - mate::ConvertToV8(isolate, args) + v8_converter_->ToV8Value(&args, context) }; - renderer_client_->InvokeBindingCallback( + renderer_client_->InvokeIpcCallback( context, "onMessage", std::vector>(argv, argv + 2)); } private: + std::unique_ptr v8_converter_; AtomSandboxedRendererClient* renderer_client_; DISALLOW_COPY_AND_ASSIGN(AtomSandboxedRenderViewObserver); }; @@ -122,13 +139,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( @@ -147,7 +164,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(); @@ -158,17 +175,14 @@ void AtomSandboxedRendererClient::DidCreateScriptContext( script->Run(context).ToLocalChecked()); // Create and initialize the binding object auto binding = v8::Object::New(isolate); - api::Initialize(binding, v8::Null(isolate), context, nullptr); + InitializeBindings(binding, context); + AddRenderBindings(isolate, binding); v8::Local args[] = { binding, mate::ConvertToV8(isolate, preload_script) }; // Execute the function with proper arguments ignore_result(func->Call(context, v8::Null(isolate), 2, args)); - // Store the bindingt privately for handling messages from the main process. - auto binding_key = mate::ConvertToV8(isolate, kBindingKey)->ToString(); - auto private_binding_key = v8::Private::ForApi(isolate, binding_key); - context->Global()->SetPrivate(context, private_binding_key, binding); } void AtomSandboxedRendererClient::WillReleaseScriptContext( @@ -176,15 +190,15 @@ void AtomSandboxedRendererClient::WillReleaseScriptContext( auto isolate = context->GetIsolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); - InvokeBindingCallback(context, "onExit", std::vector>()); + InvokeIpcCallback(context, "onExit", std::vector>()); } -void AtomSandboxedRendererClient::InvokeBindingCallback( +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, kBindingKey)->ToString(); + auto binding_key = mate::ConvertToV8(isolate, kIpcKey)->ToString(); auto private_binding_key = v8::Private::ForApi(isolate, binding_key); auto global_object = context->Global(); v8::Local value; diff --git a/atom/renderer/atom_sandboxed_renderer_client.h b/atom/renderer/atom_sandboxed_renderer_client.h index 66d8278915..a8eae0ba84 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 InvokeIpcCallback(v8::Handle context, + const std::string& callback_name, + std::vector> args); + // atom::RendererClientBase: void DidCreateScriptContext( - v8::Handle context, content::RenderFrame* render_frame); + v8::Handle context, + content::RenderFrame* render_frame) override; void WillReleaseScriptContext( - v8::Handle context, content::RenderFrame* render_frame); - void InvokeBindingCallback(v8::Handle context, - std::string callback_name, - std::vector> args); + 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 0000000000..4d3675e2f6 --- /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 0000000000..c8958de904 --- /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/renderer/web_worker_observer.cc b/atom/renderer/web_worker_observer.cc new file mode 100644 index 0000000000..954b2f2361 --- /dev/null +++ b/atom/renderer/web_worker_observer.cc @@ -0,0 +1,73 @@ +// 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/web_worker_observer.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/node_bindings.h" +#include "base/lazy_instance.h" +#include "base/threading/thread_local.h" + +#include "atom/common/node_includes.h" + +namespace atom { + +namespace { + +static base::LazyInstance> + lazy_tls = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// static +WebWorkerObserver* WebWorkerObserver::GetCurrent() { + WebWorkerObserver* self = lazy_tls.Pointer()->Get(); + return self ? self : new WebWorkerObserver; +} + +WebWorkerObserver::WebWorkerObserver() + : node_bindings_(NodeBindings::Create(NodeBindings::WORKER)), + atom_bindings_(new AtomBindings(node_bindings_->uv_loop())) { + lazy_tls.Pointer()->Set(this); +} + +WebWorkerObserver::~WebWorkerObserver() { + lazy_tls.Pointer()->Set(nullptr); + node::FreeEnvironment(node_bindings_->uv_env()); + asar::ClearArchives(); +} + +void WebWorkerObserver::ContextCreated(v8::Local context) { + v8::Context::Scope context_scope(context); + + // Start the embed thread. + node_bindings_->PrepareMessageLoop(); + + // Setup node environment for each window. + node::Environment* env = node_bindings_->CreateEnvironment(context); + + // Add Electron extended APIs. + atom_bindings_->BindTo(env->isolate(), env->process_object()); + + // Load everything. + node_bindings_->LoadEnvironment(env); + + // Make uv loop being wrapped by window context. + node_bindings_->set_uv_env(env); + + // Give the node loop a run to make sure everything is ready. + node_bindings_->RunMessageLoop(); +} + +void WebWorkerObserver::ContextWillDestroy(v8::Local context) { + node::Environment* env = node::Environment::GetCurrent(context); + if (env) + mate::EmitEvent(env->isolate(), env->process_object(), "exit"); + + delete this; +} + +} // namespace atom diff --git a/atom/renderer/web_worker_observer.h b/atom/renderer/web_worker_observer.h new file mode 100644 index 0000000000..45ccfc3dc9 --- /dev/null +++ b/atom/renderer/web_worker_observer.h @@ -0,0 +1,37 @@ +// 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_WEB_WORKER_OBSERVER_H_ +#define ATOM_RENDERER_WEB_WORKER_OBSERVER_H_ + +#include "base/macros.h" +#include "v8/include/v8.h" + +namespace atom { + +class AtomBindings; +class NodeBindings; + +// Watches for WebWorker and insert node integration to it. +class WebWorkerObserver { + public: + // Returns the WebWorkerObserver for current worker thread. + static WebWorkerObserver* GetCurrent(); + + void ContextCreated(v8::Local context); + void ContextWillDestroy(v8::Local context); + + private: + WebWorkerObserver(); + ~WebWorkerObserver(); + + std::unique_ptr node_bindings_; + std::unique_ptr atom_bindings_; + + DISALLOW_COPY_AND_ASSIGN(WebWorkerObserver); +}; + +} // namespace atom + +#endif // ATOM_RENDERER_WEB_WORKER_OBSERVER_H_ diff --git a/atom/utility/atom_content_utility_client.cc b/atom/utility/atom_content_utility_client.cc index 37fa755871..cc36293f43 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 0edc4d5d80..b4aa7960f6 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/browser_process.cc b/chromium_src/chrome/browser/browser_process.cc index a38d55f871..d37478396e 100644 --- a/chromium_src/chrome/browser/browser_process.cc +++ b/chromium_src/chrome/browser/browser_process.cc @@ -4,13 +4,15 @@ #include "chrome/browser/browser_process.h" +#include "chrome/browser/icon_manager.h" #include "chrome/browser/printing/print_job_manager.h" #include "ui/base/l10n/l10n_util.h" BrowserProcess* g_browser_process = NULL; BrowserProcess::BrowserProcess() - : print_job_manager_(new printing::PrintJobManager) { + : print_job_manager_(new printing::PrintJobManager), + icon_manager_(new IconManager) { g_browser_process = this; } @@ -22,6 +24,12 @@ std::string BrowserProcess::GetApplicationLocale() { return l10n_util::GetApplicationLocale(""); } +IconManager* BrowserProcess::GetIconManager() { + if (!icon_manager_.get()) + icon_manager_.reset(new IconManager); + return icon_manager_.get(); +} + printing::PrintJobManager* BrowserProcess::print_job_manager() { return print_job_manager_.get(); } diff --git a/chromium_src/chrome/browser/browser_process.h b/chromium_src/chrome/browser/browser_process.h index 1459ca31a6..1c1156f452 100644 --- a/chromium_src/chrome/browser/browser_process.h +++ b/chromium_src/chrome/browser/browser_process.h @@ -15,6 +15,8 @@ #include "base/macros.h" +class IconManager; + namespace printing { class PrintJobManager; } @@ -27,11 +29,13 @@ class BrowserProcess { ~BrowserProcess(); std::string GetApplicationLocale(); + IconManager* GetIconManager(); printing::PrintJobManager* print_job_manager(); private: std::unique_ptr print_job_manager_; + std::unique_ptr icon_manager_; DISALLOW_COPY_AND_ASSIGN(BrowserProcess); }; diff --git a/chromium_src/chrome/browser/icon_loader.cc b/chromium_src/chrome/browser/icon_loader.cc new file mode 100644 index 0000000000..a0cca6379d --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_loader.h" +#include "base/bind.h" +#include "base/threading/thread_task_runner_handle.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +// static +IconLoader* IconLoader::Create(const base::FilePath& file_path, + IconSize size, + IconLoadedCallback callback) { + return new IconLoader(file_path, size, callback); +} + +void IconLoader::Start() { + target_task_runner_ = base::ThreadTaskRunnerHandle::Get(); + BrowserThread::PostTaskAndReply( + BrowserThread::FILE, FROM_HERE, + base::Bind(&IconLoader::ReadGroup, base::Unretained(this)), + base::Bind(&IconLoader::OnReadGroup, base::Unretained(this))); +} + +IconLoader::IconLoader(const base::FilePath& file_path, + IconSize size, + IconLoadedCallback callback) + : file_path_(file_path), icon_size_(size), callback_(callback) {} + +IconLoader::~IconLoader() {} + +void IconLoader::ReadGroup() { + group_ = GroupForFilepath(file_path_); +} + +void IconLoader::OnReadGroup() { + BrowserThread::PostTask( + ReadIconThreadID(), FROM_HERE, + base::Bind(&IconLoader::ReadIcon, base::Unretained(this))); +} diff --git a/chromium_src/chrome/browser/icon_loader.h b/chromium_src/chrome/browser/icon_loader.h new file mode 100644 index 0000000000..50d9ef4541 --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader.h @@ -0,0 +1,92 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_ICON_LOADER_H_ +#define CHROME_BROWSER_ICON_LOADER_H_ + +#include +#include + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/single_thread_task_runner.h" +#include "build/build_config.h" +#include "content/public/browser/browser_thread.h" +#include "ui/gfx/image/image.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// A facility to read a file containing an icon asynchronously in the IO +// thread. Returns the icon in the form of an ImageSkia. +// +//////////////////////////////////////////////////////////////////////////////// +class IconLoader { + public: + // An IconGroup is a class of files that all share the same icon. For all + // platforms but Windows, and for most files on Windows, it is the file type + // (e.g. all .mp3 files share an icon, all .html files share an icon). On + // Windows, for certain file types (.exe, .dll, etc), each file of that type + // is assumed to have a unique icon. In that case, each of those files is a + // group to itself. + using IconGroup = base::FilePath::StringType; + + enum IconSize { + SMALL = 0, // 16x16 + NORMAL, // 32x32 + LARGE, // Windows: 32x32, Linux: 48x48, Mac: Unsupported + ALL, // All sizes available + }; + + // The callback invoked when an icon has been read. The parameters are: + // - The icon that was loaded, or null if there was a failure to load it. + // - The determined group from the original requested path. + using IconLoadedCallback = + base::Callback, const IconGroup&)>; + + // Creates an IconLoader, which owns itself. If the IconLoader might outlive + // the caller, be sure to use a weak pointer in the |callback|. + static IconLoader* Create(const base::FilePath& file_path, + IconSize size, + IconLoadedCallback callback); + + // Starts the process of reading the icon. When the reading of the icon is + // complete, the IconLoadedCallback callback will be fulfilled, and the + // IconLoader will delete itself. + void Start(); + + private: + IconLoader(const base::FilePath& file_path, + IconSize size, + IconLoadedCallback callback); + + ~IconLoader(); + + // Given a file path, get the group for the given file. + static IconGroup GroupForFilepath(const base::FilePath& file_path); + + // The thread ReadIcon() should be called on. + static content::BrowserThread::ID ReadIconThreadID(); + + void ReadGroup(); + void OnReadGroup(); + void ReadIcon(); + + // The task runner object of the thread in which we notify the delegate. + scoped_refptr target_task_runner_; + + base::FilePath file_path_; + + IconGroup group_; + + IconSize icon_size_; + + std::unique_ptr image_; + + IconLoadedCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(IconLoader); +}; + +#endif // CHROME_BROWSER_ICON_LOADER_H_ diff --git a/chromium_src/chrome/browser/icon_loader_auralinux.cc b/chromium_src/chrome/browser/icon_loader_auralinux.cc new file mode 100644 index 0000000000..449d05771d --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader_auralinux.cc @@ -0,0 +1,51 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_loader.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/nix/mime_util_xdg.h" +#include "ui/views/linux_ui/linux_ui.h" + +// static +IconLoader::IconGroup IconLoader::GroupForFilepath( + const base::FilePath& file_path) { + return base::nix::GetFileMimeType(file_path); +} + +// static +content::BrowserThread::ID IconLoader::ReadIconThreadID() { + // ReadIcon() calls into views::LinuxUI and GTK2 code, so it must be on the UI + // thread. + return content::BrowserThread::UI; +} + +void IconLoader::ReadIcon() { + int size_pixels = 0; + switch (icon_size_) { + case IconLoader::SMALL: + size_pixels = 16; + break; + case IconLoader::NORMAL: + size_pixels = 32; + break; + case IconLoader::LARGE: + size_pixels = 48; + break; + default: + NOTREACHED(); + } + + views::LinuxUI* ui = views::LinuxUI::instance(); + if (ui) { + gfx::Image image = ui->GetIconForContentType(group_, size_pixels); + if (!image.IsEmpty()) + image_.reset(new gfx::Image(image)); + } + + target_task_runner_->PostTask( + FROM_HERE, base::Bind(callback_, base::Passed(&image_), group_)); + delete this; +} diff --git a/chromium_src/chrome/browser/icon_loader_mac.mm b/chromium_src/chrome/browser/icon_loader_mac.mm new file mode 100644 index 0000000000..dd94e9fe7e --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader_mac.mm @@ -0,0 +1,58 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_loader.h" + +#import + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/sys_string_conversions.h" +#include "base/threading/thread.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_skia_util_mac.h" + +// static +IconLoader::IconGroup IconLoader::GroupForFilepath( + const base::FilePath& file_path) { + return file_path.Extension(); +} + +// static +content::BrowserThread::ID IconLoader::ReadIconThreadID() { + return content::BrowserThread::FILE; +} + +void IconLoader::ReadIcon() { + NSString* group = base::SysUTF8ToNSString(group_); + NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; + NSImage* icon = [workspace iconForFileType:group]; + + if (icon_size_ == ALL) { + // The NSImage already has all sizes. + image_.reset(new gfx::Image([icon retain])); + } else { + NSSize size = NSZeroSize; + switch (icon_size_) { + case IconLoader::SMALL: + size = NSMakeSize(16, 16); + break; + case IconLoader::NORMAL: + size = NSMakeSize(32, 32); + break; + default: + NOTREACHED(); + } + gfx::ImageSkia image_skia(gfx::ImageSkiaFromResizedNSImage(icon, size)); + if (!image_skia.isNull()) { + image_skia.MakeThreadSafe(); + image_.reset(new gfx::Image(image_skia)); + } + } + + target_task_runner_->PostTask( + FROM_HERE, base::Bind(callback_, base::Passed(&image_), group_)); + delete this; +} diff --git a/chromium_src/chrome/browser/icon_loader_win.cc b/chromium_src/chrome/browser/icon_loader_win.cc new file mode 100644 index 0000000000..279c819cc4 --- /dev/null +++ b/chromium_src/chrome/browser/icon_loader_win.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_loader.h" + +#include +#include + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/display/win/dpi.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/icon_util.h" +#include "ui/gfx/image/image_skia.h" + +// static +IconLoader::IconGroup IconLoader::GroupForFilepath( + const base::FilePath& file_path) { + if (file_path.MatchesExtension(L".exe") || + file_path.MatchesExtension(L".dll") || + file_path.MatchesExtension(L".ico")) { + return file_path.value(); + } + + return file_path.Extension(); +} + +// static +content::BrowserThread::ID IconLoader::ReadIconThreadID() { + return content::BrowserThread::FILE; +} + +void IconLoader::ReadIcon() { + int size = 0; + switch (icon_size_) { + case IconLoader::SMALL: + size = SHGFI_SMALLICON; + break; + case IconLoader::NORMAL: + size = 0; + break; + case IconLoader::LARGE: + size = SHGFI_LARGEICON; + break; + default: + NOTREACHED(); + } + + image_.reset(); + + SHFILEINFO file_info = { 0 }; + if (SHGetFileInfo(group_.c_str(), FILE_ATTRIBUTE_NORMAL, &file_info, + sizeof(SHFILEINFO), + SHGFI_ICON | size | SHGFI_USEFILEATTRIBUTES)) { + std::unique_ptr bitmap( + IconUtil::CreateSkBitmapFromHICON(file_info.hIcon)); + if (bitmap.get()) { + gfx::ImageSkia image_skia(gfx::ImageSkiaRep(*bitmap, + display::win::GetDPIScale())); + image_skia.MakeThreadSafe(); + image_.reset(new gfx::Image(image_skia)); + DestroyIcon(file_info.hIcon); + } + } + + target_task_runner_->PostTask( + FROM_HERE, base::Bind(callback_, base::Passed(&image_), group_)); + delete this; +} diff --git a/chromium_src/chrome/browser/icon_manager.cc b/chromium_src/chrome/browser/icon_manager.cc new file mode 100644 index 0000000000..7b20ef323a --- /dev/null +++ b/chromium_src/chrome/browser/icon_manager.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/icon_manager.h" + +#include +#include + +#include "base/bind.h" +#include "base/task_runner.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" + +namespace { + +void RunCallbackIfNotCanceled( + const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, + const IconManager::IconRequestCallback& callback, + gfx::Image* image) { + if (is_canceled.Run()) + return; + callback.Run(image); +} + +} // namespace + +IconManager::IconManager() : weak_factory_(this) {} + +IconManager::~IconManager() { +} + +gfx::Image* IconManager::LookupIconFromFilepath(const base::FilePath& file_path, + IconLoader::IconSize size) { + auto group_it = group_cache_.find(file_path); + if (group_it == group_cache_.end()) + return nullptr; + + CacheKey key(group_it->second, size); + auto icon_it = icon_cache_.find(key); + if (icon_it == icon_cache_.end()) + return nullptr; + + return icon_it->second.get(); +} + +base::CancelableTaskTracker::TaskId IconManager::LoadIcon( + const base::FilePath& file_path, + IconLoader::IconSize size, + const IconRequestCallback& callback, + base::CancelableTaskTracker* tracker) { + base::CancelableTaskTracker::IsCanceledCallback is_canceled; + base::CancelableTaskTracker::TaskId id = + tracker->NewTrackedTaskId(&is_canceled); + IconRequestCallback callback_runner = base::Bind( + &RunCallbackIfNotCanceled, is_canceled, callback); + + IconLoader* loader = IconLoader::Create( + file_path, size, + base::Bind(&IconManager::OnIconLoaded, weak_factory_.GetWeakPtr(), + callback_runner, file_path, size)); + loader->Start(); + + return id; +} + +void IconManager::OnIconLoaded(IconRequestCallback callback, + base::FilePath file_path, + IconLoader::IconSize size, + std::unique_ptr result, + const IconLoader::IconGroup& group) { + // Cache the bitmap. Watch out: |result| may be null, which indicates a + // failure. We assume that if we have an entry in |icon_cache_| it must not be + // null. + CacheKey key(group, size); + if (result) { + callback.Run(result.get()); + icon_cache_[key] = std::move(result); + } else { + callback.Run(nullptr); + icon_cache_.erase(key); + } + + group_cache_[file_path] = group; +} + +IconManager::CacheKey::CacheKey(const IconLoader::IconGroup& group, + IconLoader::IconSize size) + : group(group), size(size) {} + +bool IconManager::CacheKey::operator<(const CacheKey &other) const { + return std::tie(group, size) < std::tie(other.group, other.size); +} diff --git a/chromium_src/chrome/browser/icon_manager.h b/chromium_src/chrome/browser/icon_manager.h new file mode 100644 index 0000000000..5cda41d469 --- /dev/null +++ b/chromium_src/chrome/browser/icon_manager.h @@ -0,0 +1,114 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Class for finding and caching Windows explorer icons. The IconManager +// lives on the UI thread but performs icon extraction work on the file thread +// to avoid blocking the UI thread with potentially expensive COM and disk +// operations. +// +// Terminology +// +// Windows files have icons associated with them that can be of two types: +// 1. "Per class": the icon used for this file is used for all files with the +// same file extension or class. Examples are PDF or MP3 files, which use +// the same icon for all files of that type. +// 2. "Per instance": the icon used for this file is embedded in the file +// itself and is unique. Executable files are typically "per instance". +// +// Files that end in the following extensions are considered "per instance": +// .exe +// .dll +// .ico +// The IconManager will do explicit icon loads on the full path of these files +// and cache the results per file. All other file types will be looked up by +// file extension and the results will be cached per extension. That way, all +// .mp3 files will share one icon, but all .exe files will have their own icon. +// +// POSIX files don't have associated icons. We query the OS by the file's +// mime type. +// +// The IconManager can be queried in two ways: +// 1. A quick, synchronous check of its caches which does not touch the disk: +// IconManager::LookupIcon() +// 2. An asynchronous icon load from a file on the file thread: +// IconManager::LoadIcon() +// +// When using the second (asynchronous) method, callers must supply a callback +// which will be run once the icon has been extracted. The icon manager will +// cache the results of the icon extraction so that subsequent lookups will be +// fast. +// +// Icon bitmaps returned should be treated as const since they may be referenced +// by other clients. Make a copy of the icon if you need to modify it. + +#ifndef CHROME_BROWSER_ICON_MANAGER_H_ +#define CHROME_BROWSER_ICON_MANAGER_H_ + +#include +#include + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/task/cancelable_task_tracker.h" +#include "chrome/browser/icon_loader.h" +#include "ui/gfx/image/image.h" + +class IconManager { + public: + IconManager(); + ~IconManager(); + + // Synchronous call to examine the internal caches for the icon. Returns the + // icon if we have already loaded it, or null if we don't have it and must + // load it via LoadIcon(). The returned bitmap is owned by the IconManager and + // must not be free'd by the caller. If the caller needs to modify the icon, + // it must make a copy and modify the copy. + gfx::Image* LookupIconFromFilepath(const base::FilePath& file_path, + IconLoader::IconSize size); + + using IconRequestCallback = base::Callback; + + // Asynchronous call to lookup and return the icon associated with file. The + // work is done on the file thread, with the callbacks running on the thread + // this function is called. + // + // Note: + // 1. This does *not* check the cache. + // 2. The returned bitmap pointer is *not* owned by callback. So callback + // should never keep it or delete it. + // 3. The gfx::Image pointer passed to the callback will be null if decoding + // failed. + base::CancelableTaskTracker::TaskId LoadIcon( + const base::FilePath& file_name, + IconLoader::IconSize size, + const IconRequestCallback& callback, + base::CancelableTaskTracker* tracker); + + private: + void OnIconLoaded(IconRequestCallback callback, + base::FilePath file_path, + IconLoader::IconSize size, + std::unique_ptr result, + const IconLoader::IconGroup& group); + + struct CacheKey { + CacheKey(const IconLoader::IconGroup& group, IconLoader::IconSize size); + + // Used as a key in the map below, so we need this comparator. + bool operator<(const CacheKey &other) const; + + IconLoader::IconGroup group; + IconLoader::IconSize size; + }; + + std::map group_cache_; + std::map> icon_cache_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(IconManager); +}; + +#endif // CHROME_BROWSER_ICON_MANAGER_H_ 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 4a6b555861..bdaa3ab0b0 100644 --- a/chromium_src/chrome/browser/printing/print_view_manager_base.cc +++ b/chromium_src/chrome/browser/printing/print_view_manager_base.cc @@ -160,6 +160,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/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc b/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc index bcf6debc4c..f5829b9e59 100644 --- a/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc +++ b/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc @@ -5,10 +5,12 @@ #include "chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h" #include "base/logging.h" +#include "base/memory/ptr_util.h" #include "chrome/renderer/pepper/pepper_flash_font_file_host.h" #include "chrome/renderer/pepper/pepper_flash_fullscreen_host.h" #include "chrome/renderer/pepper/pepper_flash_menu_host.h" #include "chrome/renderer/pepper/pepper_flash_renderer_host.h" +#include "components/pdf/renderer/pepper_pdf_host.h" #include "content/public/renderer/renderer_ppapi_host.h" #include "ppapi/host/ppapi_host.h" #include "ppapi/host/resource_host.h" @@ -79,5 +81,14 @@ std::unique_ptr ChromeRendererPepperHostFactory::CreateResourceHos } } + if (host_->GetPpapiHost()->permissions().HasPermission( + ppapi::PERMISSION_PRIVATE)) { + switch (message.type()) { + case PpapiHostMsg_PDF_Create::ID: { + return base::MakeUnique(host_, instance, resource); + } + } + } + return std::unique_ptr(); } diff --git a/chromium_src/components/pdf/common/pdf_messages.h b/chromium_src/components/pdf/common/pdf_messages.h new file mode 100644 index 0000000000..c6325be4e1 --- /dev/null +++ b/chromium_src/components/pdf/common/pdf_messages.h @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Multiply-included file, no traditional include guard. +#include + +#include "content/public/common/common_param_traits_macros.h" +#include "content/public/common/referrer.h" +#include "ipc/ipc_message_macros.h" +#include "url/gurl.h" +#include "url/ipc/url_param_traits.h" + +#define IPC_MESSAGE_START PDFMsgStart + +// Brings up SaveAs... dialog to save specified URL. +IPC_MESSAGE_ROUTED2(PDFHostMsg_PDFSaveURLAs, + GURL /* url */, + content::Referrer /* referrer */) diff --git a/chromium_src/components/pdf/renderer/pepper_pdf_host.cc b/chromium_src/components/pdf/renderer/pepper_pdf_host.cc new file mode 100644 index 0000000000..5f0e9afe54 --- /dev/null +++ b/chromium_src/components/pdf/renderer/pepper_pdf_host.cc @@ -0,0 +1,113 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/pdf/renderer/pepper_pdf_host.h" + +#include "base/memory/ptr_util.h" +#include "components/pdf/common/pdf_messages.h" +#include "content/public/common/referrer.h" +#include "content/public/renderer/pepper_plugin_instance.h" +#include "content/public/renderer/render_frame.h" +#include "content/public/renderer/renderer_ppapi_host.h" +#include "ppapi/host/dispatch_host_message.h" +#include "ppapi/proxy/ppapi_messages.h" + +namespace pdf { + +PepperPDFHost::PepperPDFHost(content::RendererPpapiHost* host, + PP_Instance instance, + PP_Resource resource) + : ppapi::host::ResourceHost(host->GetPpapiHost(), instance, resource), + host_(host) {} + +PepperPDFHost::~PepperPDFHost() {} + +int32_t PepperPDFHost::OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) { + PPAPI_BEGIN_MESSAGE_MAP(PepperPDFHost, msg) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_PDF_DidStartLoading, + OnHostMsgDidStartLoading) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_PDF_DidStopLoading, + OnHostMsgDidStopLoading) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_PDF_SaveAs, + OnHostMsgSaveAs) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_PDF_SetSelectedText, + OnHostMsgSetSelectedText) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_PDF_SetLinkUnderCursor, + OnHostMsgSetLinkUnderCursor) + PPAPI_END_MESSAGE_MAP() + return PP_ERROR_FAILED; +} + +int32_t PepperPDFHost::OnHostMsgDidStartLoading( + ppapi::host::HostMessageContext* context) { + content::RenderFrame* render_frame = GetRenderFrame(); + if (!render_frame) + return PP_ERROR_FAILED; + + render_frame->DidStartLoading(); + return PP_OK; +} + +int32_t PepperPDFHost::OnHostMsgDidStopLoading( + ppapi::host::HostMessageContext* context) { + content::RenderFrame* render_frame = GetRenderFrame(); + if (!render_frame) + return PP_ERROR_FAILED; + + render_frame->DidStopLoading(); + return PP_OK; +} + +int32_t PepperPDFHost::OnHostMsgSaveAs( + ppapi::host::HostMessageContext* context) { + content::PepperPluginInstance* instance = + host_->GetPluginInstance(pp_instance()); + if (!instance) + return PP_ERROR_FAILED; + + content::RenderFrame* render_frame = instance->GetRenderFrame(); + if (!render_frame) + return PP_ERROR_FAILED; + + GURL url = instance->GetPluginURL(); + content::Referrer referrer; + referrer.url = url; + referrer.policy = blink::WebReferrerPolicyDefault; + referrer = content::Referrer::SanitizeForRequest(url, referrer); + render_frame->Send( + new PDFHostMsg_PDFSaveURLAs(render_frame->GetRoutingID(), url, referrer)); + return PP_OK; +} + +int32_t PepperPDFHost::OnHostMsgSetSelectedText( + ppapi::host::HostMessageContext* context, + const base::string16& selected_text) { + content::PepperPluginInstance* instance = + host_->GetPluginInstance(pp_instance()); + if (!instance) + return PP_ERROR_FAILED; + instance->SetSelectedText(selected_text); + return PP_OK; +} + +int32_t PepperPDFHost::OnHostMsgSetLinkUnderCursor( + ppapi::host::HostMessageContext* context, + const std::string& url) { + content::PepperPluginInstance* instance = + host_->GetPluginInstance(pp_instance()); + if (!instance) + return PP_ERROR_FAILED; + instance->SetLinkUnderCursor(url); + return PP_OK; +} + +content::RenderFrame* PepperPDFHost::GetRenderFrame() { + content::PepperPluginInstance* instance = + host_->GetPluginInstance(pp_instance()); + return instance ? instance->GetRenderFrame() : nullptr; +} + +} // namespace pdf diff --git a/chromium_src/components/pdf/renderer/pepper_pdf_host.h b/chromium_src/components/pdf/renderer/pepper_pdf_host.h new file mode 100644 index 0000000000..1b0d35102d --- /dev/null +++ b/chromium_src/components/pdf/renderer/pepper_pdf_host.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_PDF_RENDERER_PEPPER_PDF_HOST_H_ +#define COMPONENTS_PDF_RENDERER_PEPPER_PDF_HOST_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/strings/string16.h" +#include "ppapi/host/resource_host.h" + +namespace content { +class RenderFrame; +class RendererPpapiHost; +} + +namespace pdf { + +class PdfAccessibilityTree; + +class PepperPDFHost : public ppapi::host::ResourceHost { + public: + PepperPDFHost(content::RendererPpapiHost* host, + PP_Instance instance, + PP_Resource resource); + ~PepperPDFHost() override; + + // ppapi::host::ResourceHost: + int32_t OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) override; + + private: + int32_t OnHostMsgDidStartLoading(ppapi::host::HostMessageContext* context); + int32_t OnHostMsgDidStopLoading(ppapi::host::HostMessageContext* context); + int32_t OnHostMsgSaveAs(ppapi::host::HostMessageContext* context); + int32_t OnHostMsgSetSelectedText(ppapi::host::HostMessageContext* context, + const base::string16& selected_text); + int32_t OnHostMsgSetLinkUnderCursor(ppapi::host::HostMessageContext* context, + const std::string& url); + + content::RenderFrame* GetRenderFrame(); + + content::RendererPpapiHost* const host_; + + DISALLOW_COPY_AND_ASSIGN(PepperPDFHost); +}; + +} // namespace pdf + +#endif // COMPONENTS_PDF_RENDERER_PEPPER_PDF_HOST_H_ diff --git a/common.gypi b/common.gypi index ca143b1897..7c1bf366aa 100644 --- a/common.gypi +++ b/common.gypi @@ -129,6 +129,9 @@ }], ['_target_name=="node"', { 'include_dirs': [ + '<(libchromiumcontent_src_dir)', + '<(libchromiumcontent_src_dir)/third_party/icu/source/common', + '<(libchromiumcontent_src_dir)/third_party/icu/source/i18n', '<(libchromiumcontent_src_dir)/v8', '<(libchromiumcontent_src_dir)/v8/include', ], @@ -155,59 +158,6 @@ '-ldbghelp.lib', '-lshlwapi.lib', ], - # Force referencing symbols of ICU and v8_inspector to make sure - # they are included in the final DLL. - 'conditions': [ - ['libchromiumcontent_component==0', { - 'variables': { - 'conditions': [ - ['target_arch=="ia32"', { - 'reference_symbols': [ - # ICU symbols: - '_u_errorName_58', - '_ubidi_setPara_58', - '_ucsdet_getName_58', - '_uidna_openUTS46_58', - '_ulocdata_close_58', - '_unorm_normalize_58', - '_uregex_matches_58', - '_uscript_getCode_58', - '_uspoof_open_58', - '_usearch_setPattern_58', - '?createInstance@Transliterator@icu_58@@SAPAV12@ABVUnicodeString@2@W4UTransDirection@@AAW4UErrorCode@@@Z', - '??0MeasureFormat@icu_58@@QAE@ABVLocale@1@W4UMeasureFormatWidth@@AAW4UErrorCode@@@Z', - ], - }, { - 'reference_symbols': [ - # ICU symbols: - 'u_errorName_58', - 'ubidi_setPara_58', - 'ucsdet_getName_58', - 'uidna_openUTS46_58', - 'ulocdata_close_58', - 'unorm_normalize_58', - 'uregex_matches_58', - 'uspoof_open_58', - 'usearch_setPattern_58', - '?createInstance@Transliterator@icu_58@@SAPEAV12@AEBVUnicodeString@2@W4UTransDirection@@AEAW4UErrorCode@@@Z', - '??0MeasureFormat@icu_58@@QEAA@AEBVLocale@1@W4UMeasureFormatWidth@@AEAW4UErrorCode@@@Z', - # v8_inspector symbols: - '?DOM@ReasonEnum@Paused@API@Debugger@protocol@v8_inspector@@3PEBDEB', - '?canDispatchMethod@V8InspectorSession@v8_inspector@@SA_NAEBVStringView@2@@Z', - ], - }], - ], - }, - 'msvs_settings': { - 'VCLinkerTool': { - # There is nothing like "whole-archive" on Windows, so we - # have to manually force some objets files to be included - # by referencing them. - 'ForceSymbolReferences': [ '<@(reference_symbols)' ], # '/INCLUDE' - }, - }, - }], - ], }], ['OS=="linux" and libchromiumcontent_component==0', { # Prevent the linker from stripping symbols. diff --git a/default_app/default_app.js b/default_app/default_app.js index bfb97a9ab0..2a3ce3a85e 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -15,6 +15,9 @@ exports.load = (appUrl) => { height: 600, autoHideMenuBar: true, backgroundColor: '#FFFFFF', + webPreferences: { + nodeIntegrationInWorker: true + }, useContentSize: true } if (process.platform === 'linux') { diff --git a/default_app/index.html b/default_app/index.html index 292fd119b2..41b5396360 100644 --- a/default_app/index.html +++ b/default_app/index.html @@ -113,24 +113,6 @@ - -
- +
@@ -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/main.js b/default_app/main.js index 4fd01f28e0..31c55ac95f 100644 --- a/default_app/main.js +++ b/default_app/main.js @@ -129,7 +129,7 @@ app.once('ready', () => { { label: 'Learn More', click () { - shell.openExternal('http://electron.atom.io') + shell.openExternal('https://electron.atom.io') } }, { diff --git a/default_app/renderer.js b/default_app/renderer.js new file mode 100644 index 0000000000..6196195eab --- /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/es/project/CODE_OF_CONDUCT.md b/docs-translations/es/project/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..8c8aafbc1e --- /dev/null +++ b/docs-translations/es/project/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Código de Conducta convenido para Contribuyentes + +## Nuestro compromiso + +En el interés de fomentar una comunidad abierta y acogedora, nosotros como contribuyentes y administradores nos comprometemos a hacer de la participación en nuestro proyecto y nuestra comunidad una experiencia libre de acoso para todos, independientemente de la edad, dimensión corporal, discapacidad, etnia, identidad y expresión de género, nivel de experiencia, nacionalidad, apariencia física, raza, religión, identidad u orientación sexual. + +## Nuestros estándares + +Ejemplos de comportamiento que contribuyen a crear un ambiente positivo: + +* Uso de lenguaje amable e inclusivo +* Respeto a diferentes puntos de vista y experiencias +* Aceptación de críticas constructivas +* Enfocarse en lo que es mejor para la comunidad +* Mostrar empatía a otros miembros de la comunidad + +Ejemplos de comportamiento inaceptable por participantes: + +* Uso de lenguaje o imágenes sexuales y atención sexual no deseada +* Comentarios insultantes o despectivos (*trolling*) y ataques personales o políticos +* Acoso público o privado +* Publicación de información privada de terceros sin su consentimiento, como direcciones físicas o electrónicas +* Otros tipos de conducta que pudieran considerarse inapropiadas en un entorno profesional. + +## Nuestras responsabilidades + +Los administradores del proyecto son responsables de clarificar los estándares de comportamiento aceptable y se espera que tomen medidas correctivas y apropiadas en respuesta a situaciones de conducta inaceptable. + +Los administradores del proyecto tienen el derecho y la responsabilidad de eliminar, editar o rechazar comentarios, *commits*, código, ediciones de documentación, *issues*, y otras contribuciones que no estén alineadas con este Código de Conducta, o de prohibir temporal o permanentemente a cualquier colaborador cuyo comportamiento sea inapropiado, amenazante, ofensivo o perjudicial. + +## Alcance + +Este código de conducta aplica tanto a espacios del proyecto como a espacios públicos donde un individuo esté en representación del proyecto o comunidad. Ejemplos de esto incluye el uso de la cuenta oficial de correo electrónico, publicaciones a través de las redes sociales oficiales, o presentaciones con personas designadas en eventos *online* u *offline*. La representación del proyecto puede ser clarificada explicitamente por los administradores del proyecto. + +## Aplicación + +Ejemplos de abuso, acoso u otro tipo de comportamiento inaceptable puede ser reportado al equipo del proyecto en [INSERTE CORREO AQUÍ]. Todas las quejas serán revisadas e investigadas, generando un resultado apropiado a las circunstancias. El equipo del proyecto está obligado a mantener confidencialidad de la persona que reportó el incidente. Detalles específicos acerca de las políticas de aplicación pueden ser publicadas por separado. + +Administradores que no sigan o que no hagan cumplir este Código de Conducta pueden ser eliminados de forma temporal o permanente del equipo administrador. + +## Atribución + +Este Código de Conducta es una adaptación del [Contributor Covenant][homepage], versión 1.4, disponible en [http://contributor-covenant.org/version/1/4/es/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/es/ diff --git a/docs-translations/es/project/README.md b/docs-translations/es/project/README.md new file mode 100644 index 0000000000..ed38803d42 --- /dev/null +++ b/docs-translations/es/project/README.md @@ -0,0 +1,87 @@ +[![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/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) +[![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: Traducciones disponibles: [Koreano](https://github.com/electron/electron/tree/master/docs-translations/ko-KR/project/README.md) | [Chino Simplificado](https://github.com/electron/electron/tree/master/docs-translations/zh-CN/project/README.md) | [Portugués Brasileño](https://github.com/electron/electron/tree/master/docs-translations/pt-BR/project/README.md) | [Chino Tradicional](https://github.com/electron/electron/tree/master/docs-translations/zh-TW/project/README.md) + +Electron es un framework que permite escribir aplicaciones de escritorio multiplataforma +usando JavaScript, HTML y CSS. Está basado en [Node.js](https://nodejs.org/) con +[Chromium](http://www.chromium.org). Es usado por [Atom +editor](https://github.com/atom/atom) y muchas otras [aplicaciones](https://electron.atom.io/apps). + +Sigue a [@ElectronJS](https://twitter.com/electronjs) en Twitter para estar informado de anuncios +importantes. + +Este proyecto se adhiere al [Código de Conducta convenido para Colaboradores](CODE_OF_CONDUCT.md). +Si desea participar, debes seguir este código de conducta. Por favor reporta un comportamiento +no aceptado a electron@github.com. + +## Downloads + +Para instalar binarios precompilados, usa +[`npm`](https://docs.npmjs.com/): + +```sh +# Instalación de las dependencias de desarrollo +npm install electron --save-dev + +# Instalación de `electron` de manera global a tu $PATH +npm install electron -g +``` + +Mira la [página de lanzamientos](https://github.com/electron/electron/releases) para +los prebuilt binaries, debug symbols, y más. + +### Mirrors + +- [China](https://npm.taobao.org/mirrors/electron) + +## Documentación + +Las guías y API de referencia están disponibles en el directorio +[docs](https://github.com/electron/electron/tree/master/docs). Ahí también +puedes encontrar documentos que describen cómo construir y contribuir en Electron. + +## Traducciones de la Documentación + +- [Portugués Brasileño](https://github.com/electron/electron/tree/master/docs-translations/pt-BR) +- [Koreano](https://github.com/electron/electron/tree/master/docs-translations/ko-KR) +- [Japonés](https://github.com/electron/electron/tree/master/docs-translations/jp) +- [Español](https://github.com/electron/electron/tree/master/docs-translations/es) +- [Chino Simplificado](https://github.com/electron/electron/tree/master/docs-translations/zh-CN) +- [Chino Tradicional](https://github.com/electron/electron/tree/master/docs-translations/zh-TW) +- [Turco](https://github.com/electron/electron/tree/master/docs-translations/tr-TR) +- [Thai](https://github.com/electron/electron/tree/master/docs-Translations/th-TH) +- [Ucraniano](https://github.com/electron/electron/tree/master/docs-translations/uk-UA) +- [Ruso](https://github.com/electron/electron/tree/master/docs-translations/ru-RU) +- [Francés](https://github.com/electron/electron/tree/master/docs-translations/fr-FR) + +## Inicio rápido + +Clona y ejecuta el repositorio [`electron/electron-quick-start`](https://github.com/electron/electron-quick-start) +para ver una aplicación mínima en acción. + +## Comunidad + +Puedes preguntar y interactuar con la comunidad en los siguientes lugares: +- [`electron`](http://discuss.atom.io/c/electron) Categoría en los Foros de +Atom. +- `#atom-shell` canal de IRC en Freenode +- [`Atom`](http://atom-slack.herokuapp.com/) canales en Slack +- [`electron-br`](https://electron-br.slack.com) *(Portugués Brasileño)* +- [`electron-kr`](http://www.meetup.com/electron-kr/) *(Koreano)* +- [`electron-jp`](https://electron-jp.slack.com) *(Japonés)* +- [`electron-tr`](http://electron-tr.herokuapp.com) *(Turco)* +- [`electron-id`](https://electron-id.slack.com) *(Indonés* + +Mira [awesome-electron](https://github.com/sindresorhus/awesome-electron) +donde la comunidad mantiene una lista útil de ejemplos de aplicaciones, herramientas y recursos. + +## Licencia + +[MIT](https://github.com/electron/electron/blob/master/LICENSE) + +Si usas los logos de Electron ó GitHub, asegúrate de seguir las [GitHub logo guidelines](https://github.com/logos). diff --git a/docs-translations/fr-FR/README.md b/docs-translations/fr-FR/README.md index 487751d6e2..0bf176536c 100644 --- a/docs-translations/fr-FR/README.md +++ b/docs-translations/fr-FR/README.md @@ -3,7 +3,7 @@ Le numéro de version devrait faire partie de l'URL de la page. Si ce n'est pas le cas, vous utilisez probablement la documentation d'une branche de développement qui peut contenir des changements API qui ne sont pas compatibles avec votre version d'Electron. Si c'est le cas, vous pouvez changer -de version sur la liste [versions disponibles](http://electron.atom.io/docs/), +de version sur la liste [versions disponibles](https://electron.atom.io/docs/), ou, si vous utilisez l'interface de GitHub, ouvrez la liste déroulante "Switch branches/tags" afin de sélectionner le tag de votre version. diff --git a/docs-translations/fr-FR/tutorial/quick-start.md b/docs-translations/fr-FR/tutorial/quick-start.md index 406bbfebaa..33fdc5e6fd 100644 --- a/docs-translations/fr-FR/tutorial/quick-start.md +++ b/docs-translations/fr-FR/tutorial/quick-start.md @@ -241,7 +241,7 @@ $ npm start ``` Pour plus d'exemples app, consultez la section -[list of boilerplates](http://electron.atom.io/community/#boilerplates) +[list of boilerplates](https://electron.atom.io/community/#boilerplates) Créé par la communauté impressionnante d'électrons. [share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/it-IT/README.md b/docs-translations/it-IT/README.md index 6f0d43ee4e..53030825c7 100644 --- a/docs-translations/it-IT/README.md +++ b/docs-translations/it-IT/README.md @@ -4,7 +4,7 @@ Se cos� non fosse, stai probabilmente utilizzando una documentazione facente parte di una branch di sviluppo che potrebbe contenere modifiche all'API che non sono compatibili con la tua versione di Electron. In questo caso, puoi passare a una differente versione della documentazione dalla lista di -[versioni disponibili](http://electron.atom.io/docs/) su atom.io, o nel caso tu +[versioni disponibili](https://electron.atom.io/docs/) su atom.io, o nel caso tu stia usando l'interfaccia di GitHub, apri il menu a tendina "Switch branches/tags" e seleziona il tag che corrisponde alla tua versione. diff --git a/docs-translations/jp/README.md b/docs-translations/jp/README.md index e415faa6ca..93f64c4db0 100644 --- a/docs-translations/jp/README.md +++ b/docs-translations/jp/README.md @@ -1,7 +1,7 @@ 使用している Electron のバージョンに応じたドキュメントを使うように確認してください。 ドキュメントのバージョン番号はページの URL の一部になっています。 そうでない場合、おそらくご使用の Electron のバージョンと互換性のない API 変更を含んだ development ブランチのドキュメントを使っているものと思われます。 -その場合、atom.io の [available versions](http://electron.atom.io/docs/) リストにある別のバージョンのドキュメントに切り替えることができます。また GitHub で閲覧している場合、"Switch branches/tags" ドロップダウンを開いて、バージョンに対応したタグを選ぶこともできます。 +その場合、atom.io の [available versions](https://electron.atom.io/docs/) リストにある別のバージョンのドキュメントに切り替えることができます。また GitHub で閲覧している場合、"Switch branches/tags" ドロップダウンを開いて、バージョンに対応したタグを選ぶこともできます。 _リンクになっていないリストは未翻訳のものです。_ ## FAQ diff --git a/docs-translations/jp/api/menu.md b/docs-translations/jp/api/menu.md index 22dd019ed1..73869e8b76 100644 --- a/docs-translations/jp/api/menu.md +++ b/docs-translations/jp/api/menu.md @@ -129,7 +129,7 @@ var template = [ submenu: [ { label: 'Learn More', - click: function () { require('electron').shell.openExternal('http://electron.atom.io') } + click: function () { require('electron').shell.openExternal('https://electron.atom.io') } } ] } diff --git a/docs-translations/jp/api/webview-tag.md b/docs-translations/jp/api/webview-tag.md index 301c89a0b2..cc68f1345a 100644 --- a/docs-translations/jp/api/webview-tag.md +++ b/docs-translations/jp/api/webview-tag.md @@ -148,7 +148,7 @@ Electronアプリ内でウェブページのような外部コンテンツを埋 ```html - + ``` ページで使用されるセッションを設定します。もし、`partition`が`persist:`から始まる場合、アプリ上の同じ`partition`を指定した全てのページで有効な永続セッションを使用します。 @@ -741,4 +741,4 @@ DevToolsが閉じられた際に発生します。 DevToolsにフォーカスが当たった際 / 開かれた際に発生します。 -[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 diff --git a/docs-translations/jp/tutorial/using-selenium-and-webdriver.md b/docs-translations/jp/tutorial/using-selenium-and-webdriver.md index e348a0596d..1584ab736f 100644 --- a/docs-translations/jp/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/jp/tutorial/using-selenium-and-webdriver.md @@ -156,4 +156,4 @@ Electronはリビルドせずにアプリケーションをテストするため もしくは、アプリのフォルダーを引数にしてElectronバイナリを実行します。これによって、Electronのリソースディレクトリにアプリをコピー&ペーストする必要がなくなります。 [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: http://electron.atom.io/spectron +[spectron]: https://electron.atom.io/spectron diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index 3c94b87464..136d4a38b6 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -250,10 +250,10 @@ On Windows it is * `scrollBounce` Boolean - macOS에서 스크롤 튕기기 효과 (탄성 밴딩)를 활성화 합니다. 기본값은 `false`입니다. * `blinkFeatures` String - 활성화 할 `CSSVariables,KeyboardEventKey`같이 `,`로 - 구분된 기능 문자열들의 리스트입니다. [RuntimeEnabledFeatures.in][blink-feature-string] + 구분된 기능 문자열들의 리스트입니다. [RuntimeEnabledFeatures.json5][blink-feature-string] 파일에서 찾을 수 있습니다. * `disableBlinkFeatures` String - 비활성화 할 `CSSVariables,KeyboardEventKey`같이 - `,`로 구분된 기능 문자열들의 리스트입니다. [RuntimeEnabledFeatures.in][blink-feature-string] + `,`로 구분된 기능 문자열들의 리스트입니다. [RuntimeEnabledFeatures.json5][blink-feature-string] 파일에서 찾을 수 있습니다. * `defaultFontFamily` Object - font-family의 기본 폰트를 지정합니다. * `standard` String - 기본값 `Times New Roman`. @@ -1174,6 +1174,6 @@ Returns `BrowserWindow` - 부모 윈도우. Returns `BrowserWindow[]` - 모든 자식 윈도우. -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 [window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels [quick-look]: https://en.wikipedia.org/wiki/Quick_Look diff --git a/docs-translations/ko-KR/api/clipboard.md b/docs-translations/ko-KR/api/clipboard.md index 75918c6647..ee76187ba4 100644 --- a/docs-translations/ko-KR/api/clipboard.md +++ b/docs-translations/ko-KR/api/clipboard.md @@ -102,7 +102,7 @@ Returns `Object`: ```javascript clipboard.write({ - text: 'http://electron.atom.io', + text: 'https://electron.atom.io', bookmark: 'Electron Homepage' }) ``` diff --git a/docs-translations/ko-KR/api/menu.md b/docs-translations/ko-KR/api/menu.md index 87c11eb801..505f381904 100644 --- a/docs-translations/ko-KR/api/menu.md +++ b/docs-translations/ko-KR/api/menu.md @@ -104,7 +104,7 @@ const template = [ submenu: [ { label: 'Learn More', - click () { require('electron').shell.openExternal('http://electron.atom.io') } + click () { require('electron').shell.openExternal('https://electron.atom.io') } } ] } diff --git a/docs-translations/ko-KR/api/protocol.md b/docs-translations/ko-KR/api/protocol.md index dc3bb51ba2..38a17bed14 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/api/webview-tag.md b/docs-translations/ko-KR/api/webview-tag.md index b40dc0605b..55bf5d00ac 100644 --- a/docs-translations/ko-KR/api/webview-tag.md +++ b/docs-translations/ko-KR/api/webview-tag.md @@ -166,7 +166,7 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 ```html - + ``` 페이지에서 사용하는 세션을 설정합니다. @@ -209,7 +209,7 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 ``` 활성화할 blink 기능을 지정한 `,`로 구분된 문자열의 리스트입니다. 지원하는 기능 -문자열의 전체 목록은 [RuntimeEnabledFeatures.in][blink-feature-string] 파일에서 +문자열의 전체 목록은 [RuntimeEnabledFeatures.json5][blink-feature-string] 파일에서 찾을 수 있습니다. ### `disableblinkfeatures` @@ -219,7 +219,7 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 ``` 비활성화할 blink 기능을 지정한 `,`로 구분된 문자열의 리스트입니다. 지원하는 기능 -문자열의 전체 목록은 [RuntimeEnabledFeatures.in][blink-feature-string] 파일에서 +문자열의 전체 목록은 [RuntimeEnabledFeatures.json5][blink-feature-string] 파일에서 찾을 수 있습니다. ### `guestinstance` @@ -864,4 +864,4 @@ Returns: 개발자 도구가 포커스되거나 열렸을 때 발생하는 이벤트입니다. -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 diff --git a/docs-translations/ko-KR/project/README.md b/docs-translations/ko-KR/project/README.md index eeba00d40f..23c233518d 100644 --- a/docs-translations/ko-KR/project/README.md +++ b/docs-translations/ko-KR/project/README.md @@ -1,4 +1,4 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![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/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) @@ -9,7 +9,7 @@ Electron 프레임워크는 JavaScript, HTML 그리고 CSS를 사용하여 Cross-Platform 데스크톱 애플리케이션을 개발할 수 있도록 해주는 프레임워크입니다. [Node.js](https://nodejs.org/)와 [Chromium](http://www.chromium.org)을 기반으로 만들어졌으며 [Atom Editor](https://github.com/atom/atom)에 사용되고 있습니다. -더 많은 애플리케이션은 [이곳](http://electron.atom.io/apps)에서 확인하세요. +더 많은 애플리케이션은 [이곳](https://electron.atom.io/apps)에서 확인하세요. Electron에 대한 중요한 알림을 받고 싶다면 Twitter에서 [@ElectronJS](https://twitter.com/electronjs)를 팔로우 하세요. @@ -73,7 +73,7 @@ npm install electron --save-dev - [`electron-br`](https://electron-br.slack.com) *(브라질)* 커뮤니티 - [`electron-kr`](http://www.meetup.com/electron-kr/) *(한국)* 커뮤니티 - [`electron-jp`](https://electron-jp-slackin.herokuapp.com/) *(일본)* 커뮤니티 -- [`electron-tr`](http://www.meetup.com/Electron-JS-Istanbul/) *(터키)* 커뮤니티 +- [`electron-tr`](http://electron-tr.herokuapp.com) *(터키)* 커뮤니티 - [`electron-id`](https://electron-id.slack.com) *(인도네시아)* 커뮤니티 [awesome-electron](https://github.com/sindresorhus/awesome-electron) 프로젝트에 diff --git a/docs-translations/ko-KR/tutorial/about.md b/docs-translations/ko-KR/tutorial/about.md index eb58a821b9..7a33f916e2 100644 --- a/docs-translations/ko-KR/tutorial/about.md +++ b/docs-translations/ko-KR/tutorial/about.md @@ -1,6 +1,6 @@ # Electron 에 대하여 -[Electron](http://electron.atom.io) 은 HTML, CSS 와 Javascript 로 크로스플랫폼 +[Electron](https://electron.atom.io) 은 HTML, CSS 와 Javascript 로 크로스플랫폼 데스크톱 애플리케이션을 위해 Github 에서 개발한 오픈소스 라이브러리 입니다. Electron 은 [Chromium](https://www.chromium.org/Home) 와 [Node.js](https://nodejs.org) 를 단일 실행으로 합치고 앱을 Mac, Windows 와 @@ -47,7 +47,7 @@ Chromium 이 사용하는 버전. 대부분은 동작하지만 가끔 Node.js Node.js 와 Chromium 에 대한 의존성이 강해서, Electron 은 버전관리가 까다롭고 [`semver`을 따르지 않습니다](http://semver.org). 그러므로 항상 Electron 의 특정 버전을 참조해야 합니다. [Electron 의 버전관리] -(http://electron.atom.io/docs/tutorial/electron-versioning/)를 읽거나 +(https://electron.atom.io/docs/tutorial/electron-versioning/)를 읽거나 [현재 쓰이는 버전](https://electron.atom.io/#electron-versions)을 보세요. ### LTS @@ -57,7 +57,7 @@ Node.js 와 Chromium 에 대한 의존성이 강해서, Electron 은 버전관 새버전으로 업그레이드 해야합니다. 주 버전은 `v1.0.0` 입니다. 아직 이 버전을 사용중이지 않다면, -[v1.0.0 변화에 대해 읽어보세요](http://electron.atom.io/blog/2016/05/11/electron-1-0). +[v1.0.0 변화에 대해 읽어보세요](https://electron.atom.io/blog/2016/05/11/electron-1-0). ## 중심 철학 @@ -70,7 +70,7 @@ Electron 을 작고 (파일 크기) 지속가능하게 (의존성 및 API 의 Electron 에 추가된 새로운 기능은 주로 네이티브 API 입니다. 기능은 가능한한 Node.js 모듈로 해야합니다. [커뮤니티에 의해 개발된 Electron 도구들] -(http://electron.atom.io/community)을 보세요. +(https://electron.atom.io/community)을 보세요. ## 이력 @@ -81,6 +81,6 @@ Node.js 모듈로 해야합니다. [커뮤니티에 의해 개발된 Electron | **2013년 4월**| [Atom Shell 탄생](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| | **2014년 5월** | [Atom Shell 오픈소스화](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | | **2015년 4월** | [Electron 으로 개명](https://github.com/electron/electron/pull/1389). | -| **2016년 5월** | [Electron v1.0.0 출시](http://electron.atom.io/blog/2016/05/11/electron-1-0).| -| **2016년 5월** | [Electron 앱이 Mac App Store 와 호환](http://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| -| **2016년 8월** | [Windows Store 의 Electron 앱 지원](http://electron.atom.io/docs/tutorial/windows-store-guide).| +| **2016년 5월** | [Electron v1.0.0 출시](https://electron.atom.io/blog/2016/05/11/electron-1-0).| +| **2016년 5월** | [Electron 앱이 Mac App Store 와 호환](https://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| +| **2016년 8월** | [Windows Store 의 Electron 앱 지원](https://electron.atom.io/docs/tutorial/windows-store-guide).| diff --git a/docs-translations/ko-KR/tutorial/accessibility.md b/docs-translations/ko-KR/tutorial/accessibility.md index c8521bb700..b9354edab2 100644 --- a/docs-translations/ko-KR/tutorial/accessibility.md +++ b/docs-translations/ko-KR/tutorial/accessibility.md @@ -14,7 +14,7 @@ HTML 입니다. 그러나 검사를 위한 URL 이 없기 때문에 Electron 앱 이 새 기능들은 Electron 앱에 검사 도구를 제공합니다. Spectron 으로 테스트 하기 위한 검사를 추가 하거나 Devtron 으로 개발자 도구의 것을 사용할 수 있습니다. 자세한 정보는 도구의 요약이나 -[접근성 문서](http://electron.atom.io/docs/tutorial/accessibility) 를 읽어보세요. +[접근성 문서](https://electron.atom.io/docs/tutorial/accessibility) 를 읽어보세요. ### Spectron @@ -47,5 +47,5 @@ Devtron 에서 앱의 페이지를 검사할 수 있는 접근성 탭이 생겼 통해 더 알아볼 수 있습니다. Electron 을 위한 다른 훌륭한 접근성 도구를 알고계시다면, -[접근성 문서](http://electron.atom.io/docs/tutorial/accessibility) 에 풀 +[접근성 문서](https://electron.atom.io/docs/tutorial/accessibility) 에 풀 요청과 함께 추가해 주세요. diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index 671937e113..c4004e41f7 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -243,7 +243,7 @@ $ npm start ``` 더 많은 예시 앱을 보려면 대단한 Electron 커뮤니티에 의해 만들어진 -[보일러플레이트 리스트](http://electron.atom.io/community/#boilerplates)를 +[보일러플레이트 리스트](https://electron.atom.io/community/#boilerplates)를 참고하세요. [share-data]: ../faq.md#어떻게-웹-페이지-간에-데이터를-공유할-수-있나요 diff --git a/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md b/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md index e9b229498a..d5ea5ad74b 100644 --- a/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md @@ -167,4 +167,4 @@ client 디렉터리로 복사하는 불필요한 과정을 생략할 수 있습니다. [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: http://electron.atom.io/spectron +[spectron]: https://electron.atom.io/spectron diff --git a/docs-translations/ko-KR/tutorial/windows-store-guide.md b/docs-translations/ko-KR/tutorial/windows-store-guide.md index 34d36d6bc5..23a4bbc32f 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/nl/glossary.md b/docs-translations/nl/glossary.md new file mode 100644 index 0000000000..a0923fee7e --- /dev/null +++ b/docs-translations/nl/glossary.md @@ -0,0 +1,97 @@ +# Woordenlijst + +Deze pagina definieert bepaalde terminologie die veel gebruikt wordt binnen Electrons ontwikkeling. + +### ASAR + +ASAR staat voor "Atom Shell Archive Format". Een [asar][asar] archief is een simpel `tar`-achtig formaat dat bestanden samenvoegt in een enkel bestand. Electron kan er willekeurige bestanden uitlezen zonder het hele bestand uit te pakken. + +De ASAR-indeling is in de eerste plaats gemaakt om de prestaties op Windows te verbeteren ... TODO + +### Brightray + +[Brightray][brightray] is een statische bibliotheek die [libchromiumcontent] gemakkelijker te gebruiken maakt in applicaties. Het werd speciaal gemaakt voor Electron, maar kan ook worden gebruikt om Chromiums renderer in applicaties, die niet op Electron gebaseerd zijn, in te schakelen. + +Brightray is een laag-niveau afhankelijkheid van Electron, wat geen betrekking heeft op de meerderheid van Electrons gebruikers. + +### DMG + +Een Apple Disk Image is een pakket formaat gebruikt door macOS. DMG bestanden worden gebruikt voor het verspreiden van programma installers. [electron-builder] ondersteunt `dmg` als een build target. + +### IPC + +IPC staat voor "Inter-Process Communication". Electron gebruikt IPC om geserialiseerde JSON berichten te sturen tussen het [hoofd] en het [renderer] proces. + +### libchromiumcontent + +Een enkele, gedeelde bibliotheek die de Chromium content module en al zijn afhankelijkheden(e.g., Blink, [V8], etc.) bevat. + +### main process + +Het hoofdproces(main process), vaak een bestand genaamd `main.js`, is het toegangspunt voor elke Electron applicatie. Het controleert de levensduur van de app, van begin tot einde. Het beheert ook de basis elementen zoals het menu, de menubalk, etc. Het hoofdproces is verantwoordelijk voor het maken van elk nieuw renderer proces in de app. De volledige Node API is ingebouwd. +Elke app's hoofdproces is opgegeven in het `main` attribuut in de `package.json`. Dit is hoe `electron .` weet welk bestand het moet uitvoeren bij het opstarten. + +Zie ook: [process](#process), [renderer process](#renderer-process) + +### MAS + +Acroniem voor Apple's Mac App Store. Voor meer informatie over het indienen van uw app op de MAS, zie [Mac App Store Submission Guide]. + +### native modules + +Native modules (ook wel [addons] in Node.js) zijn modules geschreven in C of C++ die kunnen ingeladen worden in Node.js of Electron met de `require()` functie en kunnen gebruikt worden net alsof ze gewone Node.js module zijn. Ze worden voornamelijk gebruikt om een een interface te voorzien tussen JavaScript, die in Node.js loopt, en C/C++. Native Node modules worden ondersteund door Electron, maar aangezien Electron zeer waarschijnlijk een andere versie van V8 gebruikt dan de binaire Node geïnstalleerd op uw systeem, moet u de locatie van Electron headers handmatig opgeven bij het bouwen van native modules. + +Zie ook: [Using Native Node Modules]. + +### NSIS + +NSIS staat voor "Nullsoft Scriptable Install System", wat een script gestuurde installer authoring tool is voor Microsoft Windows. Het is vrijgegeven onder een combinatie van vrije software licenties en is een veel gebruikt alternatief voor commercieel gepatenteerde producten zoals InstallShield. [electron-builder] ondersteunt NSIS als een build target. + +### process + +Een proces is een instantie van een computerprogramma dat wordt uitgevoerd. Electron apps die gebruik maken van het [main] en één of meer [renderer] processen voeren eigenlijk meerdere programma's tegelijk uit. + +In Node.js en Electron heeft elk lopend proces een `proces` object. Dit object is een globale die informatie en controle vooziet over het huidige proces. Als een globale is het altijd beschikbaar voor applicaties zonder gebruik van `require()`. + +Zie ook: [main process](#main-process), [renderer process](#renderer-process) + +### renderer process + +Het renderer proces is een browservenster in uw app. In tegenstelling tot het hoofdproces, kunnen er meerdere renderer processen zijn en elk van deze is een apart proces. Ze kunnen ook verborgen zijn. + +In normale browsers worden webpagina's meestal uitgevoerd in een sandbox-omgeving en hebben geen toegang tot native resources. Electron gebruikers hebben echter de bevoegdheid om gebruik te maken van de Node.js API in webpagina's die het toelaten om een lager niveau van besturingssysteem interacties te gebruiken. + +Zie ook: [process](#process), [main process](#main-process) + +### Squirrel + +Squirrel is een open-source framework dat Electron apps in staat stelt om automatisch te updaten als nieuwe versies uitkomen. Kijk naar de [autoUpdater] API voor informatie over hoe te starten met Squirrel. + +### userland + +Userland of gebruikers-land is een term die zijn oorsprong heeft in de Unix gemeenschap, waar "userland" of "userspace" verwijst naar de programma's die uitgevoerd worden buiten de kernel. Recent is de term populair geworden in de Node en npm community om het verschil aan te geven tussen de beschikbare features in de "Node core" versus pakketten gepubliceerd naar het npm register door een veel grotere "gebruiker" gemeenschap. + +Net zoals Node, is Electron gericht op het hebben van een kleine API die alle noodzakelijke functies voorziet voor het ontwikkelen van multi-platform desktop applicaties. Deze ontwerpfilosofie zorgt ervoor dat Electron een flexibele tool is zonder overdreven te beschrijven hoe het gebruikt moet worden. Userland zorgt ervoor dat gebruikers tools kunnen maken en delen die extra functionaliteit bieden bovenop wat beschikbaar is in de "core". + +### V8 + +V8 is Google's open source JavaScript-engine. Het is geschreven in C++ en wordt gebruikt in Google Chrome. V8 kan standalone draaien, of kan worden gebruikt in een C++ applicatie. + +### webview + +`Webview` tags worden gebruikt om 'gast' content (zoals in externe webpagina's) in uw Electron app te verwerken. Ze lijken op `iframe`s, maar verschillen erin dat elke webview op een apart proces loopt. Het heeft niet dezelfde rechten als uw webpagina en alle interacties tussen uw app en ingebouwde inhoud zullen asynchroon zijn. Dit houdt uw app veilig voor de ingebouwde content. + + + +[addons]: https://nodejs.org/api/addons.html +[asar]: https://github.com/electron/asar +[autoUpdater]: api/auto-updater.md +[brightray]: https://github.com/electron/brightray +[electron-builder]: https://github.com/electron-userland/electron-builder +[libchromiumcontent]: #libchromiumcontent +[Mac App Store Submission Guide]: tutorials/mac-app-store-submission-guide.md +[main]: #main-process +[renderer]: #renderer-process +[Using Native Node Modules]: tutorial/using-native-node-modules.md +[userland]: #userland +[V8]: #v8 diff --git a/docs-translations/pt-BR/README.md b/docs-translations/pt-BR/README.md index be838f6145..1ed5c45571 100644 --- a/docs-translations/pt-BR/README.md +++ b/docs-translations/pt-BR/README.md @@ -2,7 +2,7 @@ Por favor, certifique-se de que está utilizando a documentação que correspond O número da versão deve ser uma parte da URL da página. Se não for, você provavelmente está utilizando a documentação de um branch de desenvolvimento que pode conter mudanças na API que não são compatíveis com a sua versão do Electron. Se este for o caso, você pode mudar para uma versão diferente da -documentação na lista de [versões disponíveis](http://electron.atom.io/docs/) em atom.io, +documentação na lista de [versões disponíveis](https://electron.atom.io/docs/) em atom.io, ou se você estiver usando a interface do GitHub, abra o *dropdown* "Switch branches/tags" e selecione a *tag* que corresponde à sua versão. diff --git a/docs-translations/pt-BR/project/README.md b/docs-translations/pt-BR/project/README.md index 6c435edaa5..b2e8807a36 100644 --- a/docs-translations/pt-BR/project/README.md +++ b/docs-translations/pt-BR/project/README.md @@ -1,4 +1,4 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![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/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) @@ -7,7 +7,7 @@ :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) -O framework Electron permite escrever aplicações desktop multi-plataforma usando JavaScript, HTML e CSS. Baseia-se em [Node.js](https://nodejs.org/) e [Chromium](http://www.chromium.org) e é usado pelo [editor Atom](https://github.com/atom/atom) e muitas outras [aplicações](http://electron.atom.io/apps). +O framework Electron permite escrever aplicações desktop multi-plataforma usando JavaScript, HTML e CSS. Baseia-se em [Node.js](https://nodejs.org/) e [Chromium](http://www.chromium.org) e é usado pelo [editor Atom](https://github.com/atom/atom) e muitas outras [aplicações](https://electron.atom.io/apps). Siga [@ElectronJS](https://twitter.com/electronjs) no Twitter para anúncios importantes. @@ -61,7 +61,7 @@ Você pode fazer perguntas e interagir com a comunidade nos seguintes locais: - [`electron-br`](https://electron-br.slack.com) *(Brazilian Portuguese)* - [`electron-kr`](http://www.meetup.com/electron-kr/) *(Korean)* - [`electron-jp`](https://electron-jp-slackin.herokuapp.com/) *(Japanese)* -- [`electron-tr`](http://www.meetup.com/Electron-JS-Istanbul/) *(Turkish)* +- [`electron-tr`](http://electron-tr.herokuapp.com) *(Turkish)* - [`electron-id`](https://electron-id.slack.com) *(Indonesia)* Confira [awesome-electron](https://github.com/sindresorhus/awesome-electron) para uma lista mantida pela comunidade de exemplos de aplicativos úteis, ferramentas e recursos. diff --git a/docs-translations/pt-BR/tutorial/accessibility.md b/docs-translations/pt-BR/tutorial/accessibility.md index 21570ff405..f4168c6c9a 100644 --- a/docs-translations/pt-BR/tutorial/accessibility.md +++ b/docs-translations/pt-BR/tutorial/accessibility.md @@ -1,12 +1,12 @@ # Acessibilidade -Fazendo aplicações acessíveis é importante e nós estamos felizes em apresentar uma nova funcionalidade para [Devtron](http://electron.atom.io/devtron) e [Spectron](http://electron.atom.io/spectron) que dá aos desenvolvedores a oportunidade de fazer as suas aplicações melhor para todos. +Fazendo aplicações acessíveis é importante e nós estamos felizes em apresentar uma nova funcionalidade para [Devtron](https://electron.atom.io/devtron) e [Spectron](https://electron.atom.io/spectron) que dá aos desenvolvedores a oportunidade de fazer as suas aplicações melhor para todos. --- Preocupações de acessibilidade em aplicações Electron são semelhantes aos de websites, porque eles são ambos em última análise HTML. Com aplicativos Electron, no entanto, você não pode usar recursos on-line para auditorias de acessibilidade porque a sua aplicação não tem uma URL para apontar para o auditor. -Esses novos recursos trazem essas ferramentas de auditoria para a sua aplicação Electron. Você pode optar por adicionar auditorias aos seus testes com Spectron ou usá-los dentro do DevTools com Devtron. Leia a seguir para obter um resumo das ferramentas ou verifique nossa [documentação de acessibilidade](http://electron.atom.io/docs/tutorial/accessibility) para obter mais informações. +Esses novos recursos trazem essas ferramentas de auditoria para a sua aplicação Electron. Você pode optar por adicionar auditorias aos seus testes com Spectron ou usá-los dentro do DevTools com Devtron. Leia a seguir para obter um resumo das ferramentas ou verifique nossa [documentação de acessibilidade](https://electron.atom.io/docs/tutorial/accessibility) para obter mais informações. ### Spectron @@ -30,4 +30,4 @@ Em Devtron há uma nova guia de acessibilidade que permitirá auditar uma págin Ambas as ferramentas estão usando a biblioteca [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) construída pela Google for Chrome. Você pode aprender mais sobre as regras de auditoria da biblioteca de acessibilidade no [wiki do repositório](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules). -Se você souber de outras ferramentas de acessibilidade para o Electron, adicione-as à [documentação de acessibilidade](http://electron.atom.io/docs/tutorial/accessibility) através de um pull request. +Se você souber de outras ferramentas de acessibilidade para o Electron, adicione-as à [documentação de acessibilidade](https://electron.atom.io/docs/tutorial/accessibility) através de um pull request. diff --git a/docs-translations/ru-RU/README.md b/docs-translations/ru-RU/README.md index f853cb18f3..96df31da25 100644 --- a/docs-translations/ru-RU/README.md +++ b/docs-translations/ru-RU/README.md @@ -3,7 +3,7 @@ возможно, используете документацию ветки разработки, которая может содержать изменения api, которые не совместимы с вашей версией Electron. Если это так, Вы можете переключиться на другую версию документации в списке -[доступные версии](http://electron.atom.io/docs/) на [atom.io](atom.io), или +[доступные версии](https://electron.atom.io/docs/) на [atom.io](atom.io), или если Вы используете интерфейс GitHub, откройте список "переключение ветки/тега" и выберите тег, который соответствует вашей версии. diff --git a/docs-translations/ru-RU/tutorial/quick-start.md b/docs-translations/ru-RU/tutorial/quick-start.md index a0916fc5da..f485e477f7 100644 --- a/docs-translations/ru-RU/tutorial/quick-start.md +++ b/docs-translations/ru-RU/tutorial/quick-start.md @@ -25,7 +25,7 @@ __главным процессом__. Скрипт, который работа реальные ресурсы компьютера. Пользователи Electron напротив могут использовать API Node.js на страницах, что допускает более низкоуровневую работу с операционной системой. -### Разница мужду главным процессом и процессом-рендерером +### Разница между главным процессом и процессом-рендерером Главный процесс создаёт веб-страницы используя `BrowserWindow`. Каждый экземпляр `BrowserWindow` показывает веб-страницу через свой собственный процесс-рендерер. diff --git a/docs-translations/th-TH/README.md b/docs-translations/th-TH/README.md index 432c2168d1..97a6f3f868 100644 --- a/docs-translations/th-TH/README.md +++ b/docs-translations/th-TH/README.md @@ -1,26 +1,42 @@ -## คู่มือ +กรุณาตรวจสอบว่าคุณกำลังใช้คู่มือที่ตรงกับเวอร์ชั่นของ Electon ของคุณด้วย ตัวเลขเวอร์ชั่นจะมีบอกใน URL ของหน้าเพจ ถ้าไม่มีหมายความว่าคุณอาจจะใช้เอกสารของ development branch ที่ API อาจจะมีการเปลี่ยนแปลง ซึ่งไม่สามารถใช้ร่วมกับ Electron เวอร์ชั่นที่คุณใช้อยู่ได้ เพื่อที่จะดูเอกสารเวอร์ชั่นเก่า [คุณสามารถที่จะดูแท็ก](https://github.com/electron/electron/tree/v1.4.0) ใน GitHub โดยการที่คลิกที่ "เรียกดูตามกิ่ง/แท็ก" แล้วเลือกแท็กที่ตรงกับเวอร์ชั่นของคุณ +## คำถามที่ถูกถามบ่อย + +รวบรวมคำถามที่ถูกถามบ่อย กรุณาอ่านก่อนเปิด issue: + +* [คำถามที่ถูกถามบ่อยเกี่ยวกับ Electron](faq.md) + +## คู่มือ + +* [คำศัพท์เฉพาะ](glossary.md) * [แพลตฟอร์มที่รองรับ](tutorial/supported-platforms.md) +* [ความปลอดภัย](tutorial/security.md) * [การเผยแพร่แอปพลิเคชัน](tutorial/application-distribution.md) * [แนวทางการส่งแอปเข้า Mac App Store](tutorial/mac-app-store-submission-guide.md) +* [คู่มือ Windows Store](tutorial/mac-app-store-submission-guide.md) * [การบรรจุแอปพลิเคชัน](tutorial/application-packaging.md) * [การใช้โมดูลของ Node](tutorial/using-native-node-modules.md) * [การหาข้อผิดพลาดในกระบวนการหลัก](tutorial/debugging-main-process.md) * [การใช้งาน Selenium และ WebDriver](tutorial/using-selenium-and-webdriver.md) * [ส่วนเสริมของ DevTools](tutorial/devtools-extension.md) * [การใช้งานส่วนเสริม Pepper Flash](tutorial/using-pepper-flash-plugin.md) +* [การใช้งานส่วนเสริม Widevine CDM Plugin](tutorial/using-pepper-flash-plugin.md) +* [การทดสอบบน CI (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) +* [การเลนเดอร์นอกหน้าต่าง] (tutorial/offscreen-rendering.md) ## แนะนำ * [เริ่มต้นอย่างคราวๆ](tutorial/quick-start.md) * [การร่วมกันของสภาพแวดล้อมบนเดสทอป](tutorial/desktop-environment-integration.md) * [การตรวจจับเหตุการณ์ออนไลน์หรือออฟไลน์](tutorial/online-offline-events.md) +* [REPL](tutorial/repl.md) ## แหล่งอ้างอิงของ API * [สรุปความ](api/synopsis.md) * [โปรเซสออบเจค](api/process.md) * [คำสั่งสำหรับเปลี่ยนแปลงค่าของ Chrome ที่รองรับ](api/chrome-command-line-switches.md) +* [Variables สภาพแวดล้อม](api/environment-variables.md) ### การปรับแต่ง DOM: @@ -31,43 +47,48 @@ ### โมดูลสำหรับกระบวนการหลัก : * [app](api/app.md) -* [auto-updater](api/auto-updater.md) -* [browser-window](api/browser-window.md) -* [content-tracing](api/content-tracing.md) +* [autoUpdater](api/auto-updater.md) +* [BrowserWindow](api/browser-window.md) +* [contentTracing](api/content-tracing.md) * [dialog](api/dialog.md) -* [global-shortcut](api/global-shortcut.md) -* [ipc (main process)](api/ipc-main-process.md) -* [menu](api/menu.md) -* [menu-item](api/menu-item.md) -* [power-monitor](api/power-monitor.md) -* [power-save-blocker](api/power-save-blocker.md) +* [globalShortcut](api/global-shortcut.md) +* [ipcMain](api/ipc-main.md) +* [Menu](api/menu.md) +* [MenuItem](api/menu-item.md) +* [powerMonitor](api/power-monitor.md) +* [powerSaveBlocker](api/power-save-blocker.md) * [protocol](api/protocol.md) * [session](api/session.md) -* [web-contents](api/web-contents.md) -* [tray](api/tray.md) +* [systemPreferences](api/system-preferences.md) +* [Tray](api/tray.md) +* [webContents](api/web-contents.md) ### โมดูลสำหรับกระบวนการ Renderer (เว็บเพจ): -* [ipc (renderer)](api/ipc-renderer.md) +* [desktopCapturer](api/desktop-capturer.md) +* [ipcRenderer](api/ipc-renderer.md) * [remote](api/remote.md) -* [web-frame](api/web-frame.md) +* [webFrame](api/web-frame.md) -### Modules for Both Processes: +### โมดูลสำหรับทั้งสองกระบวนการ: * [clipboard](api/clipboard.md) -* [crash-reporter](api/crash-reporter.md) -* [native-image](api/native-image.md) +* [crashReporter](api/crash-reporter.md) +* [nativeImage](api/native-image.md) * [screen](api/screen.md) * [shell](api/shell.md) ## การพัฒนา * [ลักษณะการเขียนโค้ด](development/coding-style.md) +* [การใช้ clang-format สำหรับโค้ด C++](development/clang-format.md) * [โครงสร้างไดเรคทอรี่ของซอร์สโค้ด](development/source-code-directory-structure.md) * [ความแตกต่างทางเทคนิคจาก NW.js (หรือ node-webkit)](development/atom-shell-vs-node-webkit.md) * [ภาพรวมการสร้างระบบ](development/build-system-overview.md) * [ขั้นตอนการสร้าง (macOS)](development/build-instructions-osx.md) * [ขั้นตอนการสร้าง (Windows)](development/build-instructions-windows.md) * [ขั้นตอนการสร้าง (Linux)](development/build-instructions-linux.md) -* [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) +* [ขั้นตอนการแก้บัค (macOS)](development/debugging-instructions-macos.md) +* [ขั้นตอนการแก้บัค (Windows)](development/debug-instructions-windows.md) * [การติดตั้งเซิร์ฟเวอร์ Symbol Server ใน debugger](development/setting-up-symbol-server.md) +* [ลักษณะการแก้เอกสาร](styleguide.md) diff --git a/docs-translations/th-TH/faq.md b/docs-translations/th-TH/faq.md new file mode 100644 index 0000000000..4a4ddc0a84 --- /dev/null +++ b/docs-translations/th-TH/faq.md @@ -0,0 +1,142 @@ +# คำถามที่ถูกถามบ่อยเกี่ยวกับ Electron (Electron FAQ) + +## เมื่อไหร่ Electron จะอัพเกรดไปเวอร์ชั่นล่าสุดของ Chrome ? + +โดยส่วยมาก Chrome เวอร์ชั่นใน Electron จะโดนอัพเกรดโดยประมาณหนี่งถึงสองอาทิตย์หลังจากมีเวอร์ชั่นใหม่ของ Chrome ที่เสถียร + +Chrome เวอร์ชั่นที่เสถียรเท่านั้นที่จะถูกใช้ ถ้ามีการแก้บัคที่สำคัญในช่องทางเบต้าหรือพัฒนา เราจะนำมันเข้ามาใช้ด้วย + +สำหรับข้อมูลเพิ่มเติม โปรดดูที่ [บทนำความปลอดภัย](tutorial/security.md) + +## เมื่อไหร่ Electron จะอัพเกรดไปเวอร์ชั่นล่าสุดของ Node.js ? + +เมื่อเวอร์ชั่นใหม่ของ Node.js ถูกปล่อยออกมา เราจะรอโดยประมาณหนึ่งเดือนก่อนที่จะอัพเกรด Node.js ที่อยู่ใน Electron เพื่อที่ว่าเราจะได้ลดความเสี่ยงถึงผลกระทบของบัคใน Node.js เวอร์ชั่นใหม่ซึ่งเกิดขึ้นบ่อยมาก + +ความสามารถใหม่ของ Node.js โดยส่วนมากจะมากับการอัพเกรด V8 เนื่องจาก Electron นั้นใช้ V8 ที่มาพร้อมกับ Chrome browser อยู่แล้ว ทำให้ Electron มีความสามารถใหม่ของ JavaScript ที่มาพร้อมกับ Node.js เวอร์ชั่นใหม่อยู่แล้ว + +## วิธีการแบ่งข้อมูลระหว่างเว็ปเพจ + +ในการที่จะแบ่งข้อมูลนั้น (the renderer processes) วิธีการที่เรียบง่ายที่สุดคือการใช้ APIs ของ HTML5 ซี่งใช้ได้อยู่แล้วในเว็ปบราวเซอร์ ทางเลือกอื่นๆที่ดีคือ [Storage API][storage], [`localStorage`][local-storage], +[`sessionStorage`][session-storage], และ [IndexedDB][indexed-db]. + +หรือคุณจะสามารถใช้ระบบ IPC ซึ่งสำหรับ Electron มันจะเก็บ objects ในโปรเซสหลักในรูปของ global variable แล้วจึงเรียกมันจากตัว renderer ผ่านทาง `remote` ของ `electron` โมดูล + +```javascript +// ในโปรเซสหลัก +global.sharedObject = { + someProperty: 'default value' +} +``` + +```javascript +// ในเพจหนึ่ง +require('electron').remote.getGlobal('sharedObject').someProperty = 'new value' +``` + +```javascript +// ในเพจสอง +console.log(require('electron').remote.getGlobal('sharedObject').someProperty) +``` + +## หน้าต่างของแอพฉันหายไปหลังจากไม่กี่นาที + +เหตุการณ์นี้เกิดขึ้นมื่อ variable ที่ใช้เก็บค่าหน้าต่างโดนหน่วยความจำเก็บกวาด + +* [การจัดการหน่วยความจำ][memory-management] +* [ขอบเขตของตัวแปร][variable-scope] + +วิธีการแก้แบบรวดเร็ว: เปลี่ยนตัวแปรให้เป็น global ด้วยการเปลี่ยนจากโค้ดนี้ + +```javascript +const {app, Tray} = require('electron') +app.on('ready', () => { + const tray = new Tray('/path/to/icon.png') + tray.setTitle('hello world') +}) +``` + +เป็น : + +```javascript +const {app, Tray} = require('electron') +let tray = null +app.on('ready', () => { + tray = new Tray('/path/to/icon.png') + tray.setTitle('hello world') +}) +``` + +## ไม่สามารถใช้ jQuery/RequireJS/Meteor/AngularJS ใน Electron + +เนื่องจากการรวบรวม Node.js เข้าไปใน Electron จึงทำให้เกิดการใส่อักขระเพิ่มเตืมลงไปใน DOM เช่น `module`, `export`, `require` + +มันทำให้เกิดปัญหากับ library อื่นๆที่ต้องการจะใช้อักขระตัวเดียวกัน + +ในการแก้ปัญหานี้ คุณจะต้องปิด node ใน Electron: + +```javascript +// ในโปรเซสหลัก +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({ + webPreferences: { + nodeIntegration: false + } +}) +win.show() +``` + +แต่ถ้าคุณยังต้องการที่จะใช้ Node.js และ API ของ Electron คุณจะค้องเปลี่ยนชื่อของอักขระในเพจดังนี้ : + +```html + + + + +``` + +## `require('electron').xxx` is undefined. + +ในตอนที่คุณใช้โมดูลที่มาพร้อมกับ Electron คุณอาจจะเจอปัญหาดังกล่าว: + +``` +> require('electron').webFrame.setZoomFactor(1.0); +Uncaught TypeError: Cannot read property 'setZoomLevel' of undefined +``` + +มันเปิดมาจากคุณได้ลง [module npm `electron`][electron-module] ในเครื่องไม่ว่าจะเป็น locally หรือ globally ซึ่งมันจะทับโมดูลที่มาพร้อมกับ Electron + +เพื่อตรวจสอบว่าคุณกำลังใช้โมดูลที่ถูกต้อง คุณสามารถที่จะส่ง command ที่จะปริ้น path ของ `electron` ได้: + +```javascript +console.log(require.resolve('electron')) +``` + +แล้วก้เช็คว่าผลลัพท์อยู่ในรูปของ: + +``` +"/path/to/Electron.app/Contents/Resources/atom.asar/renderer/api/lib/exports/electron.js" +``` + +ถ้าผลลัพท์ที่ได้จากการส่ง command อยู่ในรูปแบบ `node_modules/electron/index.js` คุณจะต้องลบโมดูล `electron` ใน npm หรือไม่ก็เปลี่ยนชื่อมัน + +```bash +npm uninstall electron +npm uninstall -g electron +``` + +ถ้าหากว่าคุณกำลังใช้โมดูลที่มาพร้อมกับ Electron แล้วยังเกิดข้อผิดผลาดดังกล่าว มีความเป็นไปได้สูงว่าคุณกำลังใช้โมดูลในโปรเซสที่ผิด + +ยกตัวอย่างเช่น `electron.app` จะสามารถใช้ได้ในโปรเซสหลักเท่านั้น แต่ว่าในขณะเดียวกัน `electron.webFrame` นั้นใช้ได้ในโปรเซส renderer เท่านั้น + +[memory-management]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management +[variable-scope]: https://msdn.microsoft.com/library/bzt2dkta(v=vs.94).aspx +[electron-module]: https://www.npmjs.com/package/electron +[storage]: https://developer.mozilla.org/en-US/docs/Web/API/Storage +[local-storage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage +[session-storage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage +[indexed-db]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API diff --git a/docs-translations/th-TH/glossary.md b/docs-translations/th-TH/glossary.md new file mode 100644 index 0000000000..9164f75ef1 --- /dev/null +++ b/docs-translations/th-TH/glossary.md @@ -0,0 +1,157 @@ +# อภิธานศัพท์ (Glossary) + +เพจนี้จะบ่งบอกคำศัพท์ที่ถูกใช่บ่อยๆในการพัฒนา Electron + +### ASAR + +[ASAR (Atom Shell Archive Format)][asar] เป็น extension ของไฟล์ +มันมีลักษณะความคล้ายครีง `tar` ที่รวมหลายไฟล์ลงไปในไฟล์เดียว +Electron สามารถอ่านไฟล์ ASAR ได้เลยโดยที่ไม่ต้อง unpack + +กำลังทำ: ไฟล์ประเภท ASAR นั้น สร้างมาเพื่อเพิ่มประสิทธิถาพบนระบบปฎิบัติการ Windows + +### Brightray + +[Brightray][brightray] เป็น static library ที่ทำให้ [libchromiumcontent] ใช้ได้ง่ายขึ้น +ในแอพพิเคชั่น มันถูกสร้างขึ้นมาเพื่อ Electron โดยเฉพาะ +แต่ว่ามันสามารถเปิดใช้ในแอพที่ใช้ Chromium's render ซึ่งไม่ถูกสร้างจาก Electron ได้ + +Brightray เป็น dependency ของ Electron ที่ไม่ค่อยได้ใช้งานสำหรับผู้ใช้ Electron ทั่วไป + +### DMG + +DMG เป็นรูปแบบไฟล์ที่ใช้กับ Apple Disk Image + +โดยส่วนมากไฟล์ DMG จะถูกใช้ในการแจกแจงตัวติดตั้งของแอพพิเคชั่น +คุณสามารถใช้ [electron-builder] ในการสร้างไฟล์ขึ้นมาได้ + +### IPC + +IPC ย่อมาจาก Inter-Process Communication ซึ่ง Electron ใช้ IPC +ในการที่จะส่งข้อความ JSON ระหว่าง [โปรเซสหลัก][main] และ [ตัวเรนเดอร์][renderer] + +### libchromiumcontent + +เป็น library รวมที่มี Chromium Content โมดูลและ dependencies ต่างๆเข้าไปด้วย +(อาทิเช่น Blink, [V8], ฯลฯ) + +### โปรเซสหลัก + +โปรเซสหลักในส่วนมากจะเป็นไฟล์ `main.js` ซึ่งเป็นจุดเริ่มต้นของทุกๆแอพของ Electron +โปรเซสหลักเป็นตัวที่กำหนดการทำงานของแอพตั้งแต่ต้นจนจบ มันจะควบคุมองค์ประกอบของแอพเช่น +Menu, Menu Bar, Dock, Tray, ฯลฯ โปรเซสหลักนั้นจะรับผิดชอบการสร้างตัวเรนเดอร์ใหม่ในแอพ + +Node API นั้นมาพร้อมกับ Electron + +`package.json` จะสามารถใช้ระบุโปรเซสหลักของทุกๆแอพบน Electron ได้ +นี้คือเหตุผลที่ `electron .` รู้ว่าไฟล์ไหนควรจะรันตอนเริ่มต้น + +เพิ่มเติม: [process](#process), [renderer process](#renderer-process) + +### MAS + +MAS เป็นตัวย่อของ Mac App Store +ในการที่จะส่งไฟล์ลง MAS นั้นโปรดดู [การแนะนำการส่ง Mac App Store](Mac App Store Submission Guide) + +### native modules + +Native โมดูล (อีกชื่อคือ [addons]) คือโมดูลที่เขียนด้วย C หรือ C++ +ที่สามารถจะโหลดอยู่ใน Node.js หรือ Electron โดยการใช้ฟังค์ชั่น require() +และใช้แบบเดียวกับโมดูล Node.js ธรรมดา + +โดยส่วนมากมันถูกใช้เป็นอินเตอร์เฟสระหว่าง JavaScript และ Node.js ผ่านทาง C/C++ + +Native Node โมดูลจะได้รับการสนับสนุนจาก Electron +แต่ว่ามีความเป็นไปได้ว่า Electron นั้นจะใช้ V8 คนละเวอร์ชั่นกับ Node ที่มีอยู่ในระบบ +คุณจะต้องเป็นคนเลือก Electron's header ด้วยตนเอง + +เพิ่มเติม: [วิธีการใช้ Native Node โมดูล][Using Native Node Modules] + +## NSIS + +NSIS ย่อมาจาก Nullsoft Scriptable Install System คือตัวติดตั้งที่ขับเคลื่อนด้วยสคริปท์ +สำหรับระบบปฎิบัติการ Windows มันได้รับการจดลิขสิทธ์สำหรับใช้ทุกๆคน +มันถูกใช้แทนสินค้าเชิงพาณิชย์เช่น InstallShield +[electron-builder] นั้นรองรับการสร้าง NSIS + +### process + +โปรเซสคือ instance ของโปรแกรมที่กำลังทำงานอยู่ +Electron แอพนั้นใช้ [โปรเซสหลัก][main] และ [ตัวเรนเดอร์][renderer] หนึ่งตัวขึ้นไปที่รัน +หลายๆโปรเกรมพร้อมๆกัน + +ใน Node.js และ Electron นั้น แต่ละโปรเซสที่กำลังรันอยู่จะมี `process` object +Object ตัวนี้เป็น global ที่ให้ข้อมูลและการควบคุมของตัวโปรเซสนั้นๆ + +เพราะว่ามันเป็น global ดังนั้นจึงไม่จำเป็นที่จะเรียก `require()` เพื่อใช้งาน + +เพิ่มเติม: [โปรเซสหลัก](#main-process), [ตัวเรนเดอร์](#renderer-process) + +### renderer process + +ตัวเรนเดอร์ คือ หน้าต่างบราวเซอร์ของแอพพิเคชั่นของคุณ +มันแตกต่างจากโปรเซสหลักโดยที่มันจะสามารถมีอยู่พร้อมกันหลายตัวได้ +และแต่ละตัวนั้นใช้ โปรเซส ที่ต่างกันออกไป +นอกจากนั้นมันยังสามารถซ่อนได้อีกด้วย + +ใน บราวเซอร์ ทั่วๆไป +เว็ปเพจจะได้รับการรันด้วยสภาพแวดล้อมจำกัดและจะไม่สามารถใช้ทรัพยากร native ได้ + +แต่ว่าสำหับผู้ใช้ Electron นั้นเราสามารถให้ +เว็ปเพจมีการปฏิสัมพันธ์กับระบบปฎิบัติการได้โดยผ่านทาง API ของ Node.js + +เพิ่มเติม: [โปรเซสหลัก](#main-process), [ตัวเรนเดอร์](#renderer-process) + +### Squirrel + +Squirrel เป็น open-source framework ที่ทำให้แอพ Electron นั้น +อัพเดทได้อย่างอัตโนมัดเมื่อมีเวอร์ชั่นใหม่เข้ามา + +เพิ่มเติม: [autoUpdater] API เพื่อการใช้งาน Squirrel เบื้องต้น + +### userland + +"Userland" แต่แรกทีมาจากสังคม unix ซึ่งหมายถึงโปรแกรมที่รันข้างนอก kernel ของระบบปฎิบัติการ +ภายหลังนี้ "userland" ได้รับความสนใจจากสังคม Node และ npm เพื่อใช้ในการอธิบายความแตกต่าง +ของ feature ที่พร้อมไช้งานใน "node core" กับ แพ็คเกจที่ได้รับการแจกจ่ายถายใน npm registry +ซึ่งมาจากเหล่าคนใช้ที่กว้างขวาง + +Electron นั้นมีความคล้ายคลึงกับ Node ตรงที่มี API ที่ไม่เยอะแต่ว่ามีความสามารถเพียงพอ +ในการพัฒนา multi-platform แอพพิเคชั่น + +ด้วยปรัชญาการออกแบบนี้เองที่ทำให้ Electron นั้นเป็นเครื่องมือที่มีความยืดหยุ่นโดยที่ไม่กำหนดให้ผู้ใช้ +ใช้งานได้เพียงตามที่ออกแบบไว้ + +Userland ได้เปิดโอกาสให้ผู้ใช้สามารถสร้างและแบ่งปันเครื่องมือนอกเหนือจากอะไรก็ตามที่มีอยู่ใน "core" + +### V8 + +V8 เป็น JavaScripe engine ของ Google ที่เป็น open source +มันถูกเขียนขึ้นด้วย C++ และถูกใช้ใน Google Chrome + +Google Chrome เป็น open source บราวส์เซอร์ของ Google + +V8 สามารถรันแยกเองต่างหากได้ หรือจะสามารถนำไปใช้กับแอพพิเคชั่น C++ อะไรก็ได้ + +### webview + +`webview` เป็นแท็กที่ใช้ในการใส่ข้อมูลสำหรับคนใช้ทั่วไป (เช่นเว็ปภายนอก) ใน Electron แอพของคุณ +มันมีความคล้ายครึงกับ `iframe` แต่ว่าต่างกันโดยที่ webview รันโดยโปรเซสคนละตัว +มันไม่มี permission เหมือนกับเว็ปเพจของคุณและการมีปฏิสัมพันธ์ระหว่าง +แอพของคุงกับสิ่งที่ฝังอยู่จะเป็นไปโดยราบลื่นโดยที่ไม่ต้องซิ้งค์ (asynchronous) + +ด้วยเหตุนี้เองทำให้ แอพของคุณปลอกภัยจากสิ่งที่ถูกฝัง + + +[addons]: https://nodejs.org/api/addons.html +[asar]: https://github.com/electron/asar +[autoUpdater]: api/auto-updater.md +[brightray]: https://github.com/electron/brightray +[electron-builder]: https://github.com/electron-userland/electron-builder +[libchromiumcontent]: #libchromiumcontent +[Mac App Store Submission Guide]: tutorials/mac-app-store-submission-guide.md +[main]: #main-process +[renderer]: #renderer-process +[Using Native Node Modules]: tutorial/using-native-node-modules.md +[userland]: #userland +[V8]: #v8 diff --git a/docs-translations/th-TH/styleguide.md b/docs-translations/th-TH/styleguide.md new file mode 100644 index 0000000000..85caeea5dd --- /dev/null +++ b/docs-translations/th-TH/styleguide.md @@ -0,0 +1,213 @@ +# ลักษณะเอกสาร Electron + +นี้คือกฎในการเขียนเอกสารประกอบ Electron + +## หัวข้อ + +* ทุกๆเพจจะมี `#` อยู่ข้างบนสุดของเอกสาร +* ทุกๆบทจะต้องมี `##` ในหัวข้อ +* ทุกๆบทย่อยจะเพิ่ม `#` ลงไปในหัวข้อตามความลึกของบทย่อย + +ยกตัวอย่างเช่น `การเริ่มต้น`: + +```markdown +# การเริ่มต้น + +... + +## โปรเซสหลัก + +... + +## โปรเซส render + +... + +## การรันแอพ + +... + +``` + +สำหรับการอ้างอิงของ API จะไม่ใช้กฎนี้ + +## กฎของ Markdown + +* ใช้ `bash` แทน `cmd` ในการเขียน code blocks (เพราะตัวไฮไลท์ syntax) +* บรรทัดควรที่จะเริ่มต้นใหม่ที่ 80 คอลัมน์ +* ไม่ควรใส่ลิสต์ที่มีมากกว่าสองชั้น (เพราะตัว render ของ markdown) +* ทุก `js` แลพ `javascript` code blocks จะไฮไลท์ด้วย +[standard-markdown](http://npm.im/standard-markdown). + +## การใช้คำ + +* ใช้ 'จะ' แทนที่ 'ควรจะ' ตอนอธิบายผลลัทธ์ + +## การอ้างอิงของ API + +กฎดังต่อไปนี้จะใช้สำหรับเอกสาร API เท่านั้น + +### หัวข้อเพจ + +ทุกๆเพจจะใช้ชื่อของ object ที่ได้รับจาก `require('electron')` เป็นหัวข้อ อาทิเช่น `BrowserWindow`, `autoUpdater` และ `session` + +ข้างล่างหัวข้อจะเป็นคำอธิบายหนึ่งบรรทัดเริ่มต้นด้วย `>` + +```markdown +# session + +> คำอธิบาย +``` + +### โมดูล methods และ events + +สำหรับโมดูลที่ไม่ใช่คราสนั้น methods และ events ของมันจะต้องอยู่ในลิสต์ภายใต้บท `## Methods` และ `## Events` + +ยกตัวอย่างเช่น `autoUpdater`: + +```markdown +# autoUpdater + +## Events + +### Event: 'error' + +## Methods + +### `autoUpdater.setFeedURL(url[, requestHeaders])` +``` + +### คราส (Classes) + +* คราส API หรือ คราสที่เป็นส่วนของโมดูลจะต้องอยู่ในบท `## Class: TheClassName` +* หนึ่งเพจสามารถมีหลายคราสได้ +* Constructors จะต้องอยู่ใน `###` +* [Static Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) จะต้องอยู่ในบทของ `### Static Method` +* [Instance Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Prototype_methods) จะต้องอยู่ในบทของ `### Instance Methods` +* ทุกๆ methods ที่มีค่า รีเทิร์น(return) จะต้องเริ่มต้นคำอธิบายด้วย "Returns `[TYPE]` - คำอธิบายค่ารีเทิร์น" + * ถ้า method รีเทิร์น `Object` โครงสร้างจองมันจะอธิบายได้ด้วยการใช้ ','(โคล่อน) ตามด้วยบรรทัดใหม่ จากนั้นก็ ลิสต์ของคุณสมบัติที่ไม่เรียงกัน (เหมือนกับ parametres ของฟังค์ชั่น) +* Instance ของ Events จะต้องอยู่ในบทของ `### Instance Events` +* Instance ของ Properties จะต้องอยู่ในบทของ `### Instance Properties` + * จะเริ่มต้นด้วย "A [Type ของ คุณสมบัติ]" + +ยกตัวอย่างเช่น `Session` และ `Cookies` คราส: + +```markdown +# session + +## Methods + +### session.fromPartition(partition) + +## Properties + +### session.defaultSession + +## Class: Session + +### Instance Events + +#### Event: 'will-download' + +### Instance Methods + +#### `ses.getCacheSize(callback)` + +### Instance Properties + +#### `ses.cookies` + +## Class: Cookies + +### Instance Methods + +#### `cookies.get(filter, callback)` +``` + +### Methods + +บท methods จะอยู่ในรูปแบบดังนี้: + +```markdown +### `objectName.methodName(required[, optional]))` + +* `required` String - A parameter description. +* `optional` Integer (optional) - Another parameter description. + +... +``` + +หัวข้อจะสามารถอยู่ในรูปของ `###` หรือ `####` (ตามว่าเป็น method ของโมดูลหรือของคราส) + +สำหรับโมดูล `objectName` คือชื่อของโมดูล แต่ว่าสำหรับคราสนั้น ชื่อจะต้องเป็น instance ของคราสและจะต้องไม่ซ้ำกับชื่อโมดูล + +ยกตัวอย่างเช่น method ของคราส `Session` ที่อยู่ใน `session` โมดูลจะต้องใช้ `ses` เหมือนกับ `objectName`. + +หากมี arguments เพิ่มเตินนั้นจะได้รับการบันทึกภายใน `[]` รวมถึงคอมม่ถ้าหากว่า arguement นี้ตามด้วย argument เสริมอื่นๆ + +``` +required[, optional] +``` + +ข้างล่าง method จะเป็นข้อคาวมโดยละเอียดเกี่ยวกับ arguments อื่นๆ, ชนิค (Type) ของ argument จะโดนบันทีกตาม type ทั่วไป + +* [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) +* [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) +* [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) +* [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +* [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) +* หรือ type พิเศษเช่น Electron [`WebContent`](api/web-contents.md) + +หาก argument หรือ method มีเฉพาะเจาะจงกับ platform นั้น ให้ใช้ตัว italic (เอียง) ลิสต์ตาม datatype +โดยที่ตัวลิสต์นั้นจะจำกัดอยู่ที่ `macOS`, `Windows`, หรือ `Linux` เท่านั้น + +```markdown +* `animate` Boolean (optional) _macOS_ _Windows_ - เล่นแอนนิเมชัั่น. +``` + +Arguments ชนิด `Array` จะต้องบอกว่า array นั้นจะใส่อะไรลงไปตามข้อมูลด้านล่าง + +ข้อความอธิบาย arguments ของ ฟังค์ชั่น (function) จะต้องบอกเจาะจงถึงวิธีการใช้งานและชนิดของ parametres ที่ฟังค์ชั่นนั้นรับ + +### อีเว้น (Events) + +บทอีเว้นจะต้องอยู่ในรูปแบบดังนี้: + +```markdown +### Event: 'wake-up' + +Returns: + +* `time` String + +... +``` + +หัวข้อจะสามารถอยู่ในรูปของ `###` หรือ `####` (ตามว่าเป็นอีเว้นของโมดูลหรือของคราส) + +แบบเอกสาร arguments ของอีเว้นจะใช้กฎเดียวกับ methods + +### คุณสมบัติ (Properties) + +บทคุณสมบัติจะต้องอยู่ในรูปแบบดังนี้: + +```markdown +### session.defaultSession + +... +``` + +หัวข้อจะสามารถอยู่ในรูปของ `###` หรือ `####` (ตามว่าเป็นคุณสมบัติของโมดูลหรือของคราส) + +## การแปลเอกสาร + +เอกสารของ Electron ที่ถูกแปลแล้ว จะอยู่ในโฟลเดอร์ `docs-translation` + +ในการสร้างเซ็ตของภาษาใหม่ (เพิ่มในบางส่วน) + +* สร้างที่อยู่ย่อยตามตัวย่อของภาษา +* แปลภาษา +* อัพเดท `README.md` ภายในเพื่อเป็นสารลิงค์ไฟล์ต่างๆที่แปลแล้ว +* เพิ่มลิ้งค์ลง [README](https://github.com/electron/electron#documentation-translations) หลักของ Electron + +โน้ต: ไฟล์ที่อยู่ใน `docs-translations` จะมีแค่ที่แปลแล้วเท่านั้น ไฟล์ต้นตำรับภาษาอังกริษไม่ควรจะโดนก้อปปี้ลงไปด้วย diff --git a/docs-translations/th-TH/tutorial/about.md b/docs-translations/th-TH/tutorial/about.md new file mode 100644 index 0000000000..310682e33b --- /dev/null +++ b/docs-translations/th-TH/tutorial/about.md @@ -0,0 +1,57 @@ +# เกี่ยวกับ Electron + +[Electron](https://electron.atom.io) เป็นโอเพ่นซอร์สไลบรารี พัฒนา โดย GitHub สำหรับการสร้างการใช้งานเดสก์ทอปข้ามแพลตฟอร์มกับ HTML, CSS และ JavaScript Electron เกิดขึ้นได้โดยการ่วม [Chromium](https://www.chromium.org/Home) กับ [Node.js](https://nodejs.org) เข้าด้วยกันเป็นหนึงruntimeและปพลิเคชันสามารถบรรจุสำหรับ Mac, Windows และ Linux + +Electron began in 2013 as the framework on which [Atom](https://atom.io), GitHub's hackable text editor, would be built. The two were open sourced in the Spring of 2014. + +It has since become a popular tool used by open source developers, startups, and established companies. [See who is building on Electron](/apps). + +Read on to learn more about the contributors and releases of Electron or get started building with Electron in the [Quick Start Guide](quick-start.md). + +## Core Team and Contributors + +Electron is maintained by a team at GitHub as well as a group of [active contributors](https://github.com/electron/electron/graphs/contributors) from the community. Some of the contributors are individuals and some work at larger companies who are developing on Electron. We're happy to add frequent contributors to the project as maintainers. Read more about [contributing to Electron](https://github.com/electron/electron/blob/master/CONTRIBUTING.md). + +## Releases + +[Electron releases](https://github.com/electron/electron/releases) frequently. We release when there are significant bug fixes, new APIs or are updating versions of Chromium or Node.js. + +### Updating Dependencies + +Electron's version of Chromium is usually updated within one or two weeks after a new stable Chromium version is released, depending on the effort involved in the upgrade. + +When a new version of Node.js is released, Electron usually waits about a month before upgrading in order to bring in a more stable version. + +ใน Electron, Node.js and Chromium share a single V8 instance—usually the version that Chromium is using. Most of the time this _just works_ but sometimes it means patching Node.js. + + +### Versioning + +Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](https://electron.atom.io/docs/tutorial/electron-versioning/) or see the [versions currently in use](https://electron.atom.io/#electron-versions). + +### LTS + +Long term support of older versions of Electron does not currently exist. If your current version of Electron works for you, you can stay on it for as long as you'd like. If you want to make use of new features as they come in you should upgrade to a newer version. + +A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](https://electron.atom.io/blog/2016/05/11/electron-1-0). + +## Core Philosophy + +In order to keep Electron small (file size) and sustainable (the spread of dependencies and APIs) the project limits the scope of the core project. + +For instance, Electron uses just the rendering library from Chromium rather than all of Chromium. This makes it easier to upgrade Chromium but also means some browser features found in Google Chrome do not exist in Electron. + +New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](https://electron.atom.io/community). + +## History + +Below are milestones in Electron's history. + +| :calendar: | :tada: | +| --- | --- | +| **April 2013**| [Atom Shell is started](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| +| **May 2014** | [Atom Shell is open sourced](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | +| **April 2015** | [Atom Shell is re-named Electron](https://github.com/electron/electron/pull/1389). | +| **May 2016** | [Electron releases `v1.0.0`](https://electron.atom.io/blog/2016/05/11/electron-1-0).| +| **May 2016** | [Electron apps compatible with Mac App Store](https://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| +| **August 2016** | [Windows Store support for Electron apps](https://electron.atom.io/docs/tutorial/windows-store-guide).| diff --git a/docs-translations/th-TH/tutorial/accessibility.md b/docs-translations/th-TH/tutorial/accessibility.md new file mode 100644 index 0000000000..0fda6e6a95 --- /dev/null +++ b/docs-translations/th-TH/tutorial/accessibility.md @@ -0,0 +1,40 @@ +# การเข้าถึง (Accessibility) + +การที่จะทำให้แอพพิเคชั่นนั้นเข้าถึงได้เป็นเรื่องที่สำคัญมาก และ เรามีความสุขที่จะต้อนรับความสามารถใหม่ของเราสู่ [Devtron](https://electron.atom.io/devtron) และ [Spectron](https://electron.atom.io/spectron) ซึ่งได้ให้โอกาสผู้พัฒนาในการที่จะสร้างแอพพิเคชั่นที่ดีขึ้นเพื่อทุกๆคน + +--- + +ความกังวลเกี่ยวกับการเข้าถึงของแอพพิเคชั่น Electron นั้นมีความคล้ายครึงกับความกังวลของเว็ปไซต์ เพราะว่าทั้งสองนั้นเป็น HTML ด้วยกัน ในขณะเดียวกันนั้น แต่ว่าในแอพ Electron คุณไม่สามารถใช้ทรัพยากรณ์ออนไลน์ได้เพราะว่าแอพของคุณนั้นไม่มี URL ที่สามารถเข้าถึงได้ + +ความสามารถใหม่ๆนี้นำอุปกรณ์การแก้ไขต่างๆเข้ามาใส่แอพ Electron ของคุณ คุณสามารถเลือกที่จะแก้ไขบททดสองของคุณได้ด้วย Spectron หรือว่าใช้มันใน DevTools ด้วย Devtron + +กรุณาอ่านต่อเพื่อบทสรุปของอุปกรณ์หรือดู [เอกสารการเข้าถึง](https://electron.atom.io/docs/tutorial/accessibility) ของเราสำหรับข้อมูลเพิ่มเติม + +### Spectron + +ในการทดสอบเฟรมเวิร์ค Spectron นั้น +คุณจะใช้วิธีการแก้ไขทุกๆหน้าต่าง และ แท็ก `` ในแอพพิเคชั่นของคุณได้ + +ยกตัวอย่างเช่น: + +```javascript +app.client.autidAccessibility().then(function (audit) { + if (audit.failed) { + console.error(audit.message) + } +}) +``` + +คุณสามารถอ่านข้อมูลเพิ่มเติมสำหรับได้ที่ [เอกสาร Spectron](https://github.com/electron/spectron#accessibility-testing) + +### Devtron + +ใน Devtron นั้น จะมีแท็ปการเข้าถึง ซึ่งจะทำให้คุณสามารถจัดการเพจในแอพของคุณได้ + +![devtron screenshot](https://cloud.githubusercontent.com/assets/1305617/17156618/9f9bcd72-533f-11e6-880d-389115f40a2a.png) + +ทั้งสองเครื่องมือใช้ [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) ซึ่งเป็น library ที่สร้างขึ้นโดย Google เพื่อ Chrome + +คุณสามารถศึกษาเพิ่มเติมเกี่ยวกับมันได้ที่ [รีโปนี้](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) + +ถ้าคุณมีความรู้เกี่ยวกับอุปกรณือื่นๆที่สามารถใช้กับ Electron ได้ โปรดใส่มันเพิ่มใน [เอกสารการเข้าถึง](https://electron.atom.io/docs/tutorial/accessibility) ด้วยการขอดึงจาก Electron (pull request) diff --git a/docs-translations/th-TH/tutorial/quick-start.md b/docs-translations/th-TH/tutorial/quick-start.md new file mode 100644 index 0000000000..291d22d639 --- /dev/null +++ b/docs-translations/th-TH/tutorial/quick-start.md @@ -0,0 +1,244 @@ +# การเริ่มต้นอย่างรวดเร็ว + +Electronช่วยให้คุณสามารถสร้างโปรแกรมคอมพิวเตอร์ด้วย JavaScript โดย +ให้รันไทม์ที่สอดคล้องกับ APIs ระดับระบบปฏิบัติการ You could see it +as a variant of the Node.js runtime that is focused on desktop applications +instead of web servers. + +This doesn't mean Electron is a JavaScript binding to graphical user interface +(GUI) libraries. Instead, Electron uses web pages as its GUI, so you could also +see it as a minimal Chromium browser, controlled by JavaScript. + +### Main Process + +In Electron, the process that runs `package.json`'s `main` script is called +__the main process__. The script that runs in the main process can display a GUI +by creating web pages. + +### Renderer Process + +Since Electron uses Chromium for displaying web pages, Chromium's +multi-process architecture is also used. Each web page in Electron runs in +its own process, which is called __the renderer process__. + +In normal browsers, web pages usually run in a sandboxed environment and are not +allowed access to native resources. Electron users, however, have the power to +use Node.js APIs in web pages allowing lower level operating system +interactions. + +### Differences Between Main Process and Renderer Process + +The main process creates web pages by creating `BrowserWindow` instances. Each +`BrowserWindow` instance runs the web page in its own renderer process. When a +`BrowserWindow` instance is destroyed, the corresponding renderer process +is also terminated. + +The main process manages all web pages and their corresponding renderer +processes. Each renderer process is isolated and only cares about the web page +running in it. + +In web pages, calling native GUI related APIs is not allowed because managing +native GUI resources in web pages is very dangerous and it is easy to leak +resources. If you want to perform GUI operations in a web page, the renderer +process of the web page must communicate with the main process to request that +the main process perform those operations. + +In Electron, we have several ways to communicate between the main process and +renderer processes. Like [`ipcRenderer`](../api/ipc-renderer.md) and +[`ipcMain`](../api/ipc-main.md) modules for sending messages, and the +[remote](../api/remote.md) module for RPC style communication. There is also +an FAQ entry on [how to share data between web pages][share-data]. + +## Write your First Electron App + +Generally, an Electron app is structured like this: + +```text +your-app/ +├── package.json +├── main.js +└── index.html +``` + +The format of `package.json` is exactly the same as that of Node's modules, and +the script specified by the `main` field is the startup script of your app, +which will run the main process. An example of your `package.json` might look +like this: + +```json +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +__Note__: If the `main` field is not present in `package.json`, Electron will +attempt to load an `index.js`. + +The `main.js` should create windows and handle system events, a typical +example being: + +```javascript +const {app, BrowserWindow} = require('electron') +const path = require('path') +const url = require('url') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let win + +function createWindow () { + // Create the browser window. + win = new BrowserWindow({width: 800, height: 600}) + + // and load the index.html of the app. + win.loadURL(url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true + })) + + // Open the DevTools. + win.webContents.openDevTools() + + // Emitted when the window is closed. + 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 + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow) + +// Quit when all windows are closed. +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() + } +}) + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. +``` + +Finally the `index.html` is the web page you want to show: + +```html + + + + + Hello World! + + +

Hello World!

+ We are using node , + Chrome , + and Electron . + + +``` + +## Run your app + +Once you've created your initial `main.js`, `index.html`, and `package.json` files, +you'll probably want to try running your app locally to test it and make sure it's +working as expected. + +### `electron` + +[`electron`](https://github.com/electron-userland/electron-prebuilt) is +an `npm` module that contains pre-compiled versions of Electron. + +If you've installed it globally with `npm`, then you will only need to run the +following in your app's source directory: + +```bash +electron . +``` + +If you've installed it locally, then run: + +#### macOS / Linux + +```bash +$ ./node_modules/.bin/electron . +``` + +#### Windows + +```bash +$ .\node_modules\.bin\electron . +``` + +### Manually Downloaded Electron Binary + +If you downloaded Electron manually, you can also use the included +binary to execute your app directly. + +#### Windows + +```bash +$ .\electron\electron.exe your-app\ +``` + +#### Linux + +```bash +$ ./electron/electron your-app/ +``` + +#### macOS + +```bash +$ ./Electron.app/Contents/MacOS/Electron your-app/ +``` + +`Electron.app` here is part of the Electron's release package, you can download +it from [here](https://github.com/electron/electron/releases). + +### Run as a distribution + +After you're done writing your app, you can create a distribution by +following the [Application Distribution](./application-distribution.md) guide +and then executing the packaged app. + +### Try this Example + +Clone and run the code in this tutorial by using the [`electron/electron-quick-start`](https://github.com/electron/electron-quick-start) +repository. + +**Note**: Running this requires [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which includes [npm](https://npmjs.org)) on your system. + +```bash +# Clone the repository +$ git clone https://github.com/electron/electron-quick-start +# Go into the repository +$ cd electron-quick-start +# Install dependencies +$ npm install +# Run the app +$ npm start +``` + +For more example apps, see the +[list of boilerplates](https://electron.atom.io/community/#boilerplates) +created by the awesome electron community. + +[share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/tr-TR/README.md b/docs-translations/tr-TR/README.md index 526ea279aa..ac11a02f4d 100644 --- a/docs-translations/tr-TR/README.md +++ b/docs-translations/tr-TR/README.md @@ -1,16 +1,16 @@ Lütfen kullandığınız dokümanın Electron versiyonunuzla aynı olduğundan emin olun. Versiyon numarası okuduğunuz dokümanın URL'sindekiyle aynı olmalı. Eğer aynı değilse, muhtemelen geliştirme aşamasındaki API değişikliklerini içerebilecek dokümantasyonudur. -Eğer öyleyse, atom.io üzerinden [mevcut sürümler](http://electron.atom.io/docs/)e göz atabilirsiniz ya da eğer GitHub arayüzünü kullanıyorsanız "Switch branches/tags" açılır menüsünden versiyonunuza uygun olanı seçebilirsiniz. +Eğer öyleyse, atom.io üzerinden [mevcut sürümler](https://electron.atom.io/docs/)e göz atabilirsiniz ya da eğer GitHub arayüzünü kullanıyorsanız "Switch branches/tags" açılır menüsünden versiyonunuza uygun olanı seçebilirsiniz. ## 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 0000000000..f1998ba09b --- /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://electron-tr.herokuapp.com) *(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 0000000000..319f8af787 --- /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 0000000000..1941060df2 --- /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 0000000000..4cc131b103 --- /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/uk-UA/README.md b/docs-translations/uk-UA/README.md index b229da2fd5..414c2d9ecc 100644 --- a/docs-translations/uk-UA/README.md +++ b/docs-translations/uk-UA/README.md @@ -3,7 +3,7 @@ використовуєте документацію із development гілки, яка може містити зміни в API, які не сумісні з вашою версією Electron. Якщо це так, тоді Ви можете переключитись на іншу версію документації -із списку [доступних версій](http://electron.atom.io/docs/) на atom.io, +із списку [доступних версій](https://electron.atom.io/docs/) на atom.io, або якщо ви використовуєте інтеррфейс GitHub, тоді відкрийте список "Switch branches/tags" і виберіть потрібну вам версію із списку тегів. diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index 64c89cdcc5..706738d60d 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -12,8 +12,8 @@ * [术语表](glossary.md) * [支持平台](tutorial/supported-platforms.md) -* [安全性](tutorial/security.md) 未翻译 -* [Electron 版本管理](tutorial/electron-versioning.md) 未翻译 +* [安全性](tutorial/security.md) +* [Electron 版本管理](tutorial/electron-versioning.md) * [分发应用](tutorial/application-distribution.md) * [提交应用到 Mac App Store](tutorial/mac-app-store-submission-guide.md) * [Windows 商店提交指引](tutorial/windows-store-guide.md) @@ -24,15 +24,16 @@ * [使用开发人员工具扩展](tutorial/devtools-extension.md) * [使用 Pepper Flash 插件](tutorial/using-pepper-flash-plugin.md) * [使用 Widevine CDM 插件](tutorial/using-widevine-cdm-plugin.md) -* [通过自动化持续集成系统(CI)进行测试 (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) 未翻译 -* [离屏渲染](tutorial/offscreen-rendering.md) 未翻译 +* [通过自动化持续集成系统(CI)进行测试 (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) +* [离屏渲染](tutorial/offscreen-rendering.md) +* [快捷键](tutorial/keyboard-shortcuts.md) ## 教程 * [快速入门](tutorial/quick-start.md) * [桌面环境集成](tutorial/desktop-environment-integration.md) * [在线/离线事件探测](tutorial/online-offline-events.md) -* [应答式编译器(REPL)](tutorial/repl.md) 未翻译 +* [交互式解释器(REPL)](tutorial/repl.md) ## API文档 @@ -85,14 +86,17 @@ ## 开发 * [代码规范](development/coding-style.md) -* [在 C++ 代码中使用 clang格式化工具](development/clang-format.md) 未翻译 +* [在 C++ 代码中使用 clang-format 工具](development/clang-format.md) * [源码目录结构](development/source-code-directory-structure.md) * [与 NW.js(原 node-webkit)在技术上的差异](development/atom-shell-vs-node-webkit.md) * [构建系统概览](development/build-system-overview.md) * [构建步骤(macOS)](development/build-instructions-osx.md) * [构建步骤(Windows)](development/build-instructions-windows.md) * [构建步骤(Linux)](development/build-instructions-linux.md) -* [调试步骤 (macOS)](development/debug-instructions-macos.md) 未翻译 -* [调试步骤 (Windows)](development/debug-instructions-windows.md) 未翻译 +* [调试步骤 (macOS)](development/debug-instructions-macos.md) +* [调试步骤 (Windows)](development/debug-instructions-windows.md) * [在调试中使用 Symbol Server](development/setting-up-symbol-server.md) -* [文档风格规范](styleguide.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 0b1e2cd3f4..0e6d44b742 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 99429119f3..04680d548b 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/auto-updater.md b/docs-translations/zh-CN/api/auto-updater.md index 845b4c24d9..75a816484b 100644 --- a/docs-translations/zh-CN/api/auto-updater.md +++ b/docs-translations/zh-CN/api/auto-updater.md @@ -1,6 +1,20 @@ # autoUpdater -这个模块提供了一个到 `Squirrel` 自动更新框架的接口。 +> 使应用程序能够自动更新。 + +进程: [Main](../glossary.md#main-process) + +`autoUpdater` 模块提供了来自 +[Squirrel](https://github.com/Squirrel) 框架的一个接口。 + +你可以快速启动多平台发布服务器来分发您的 +应用程序通过使用下列项目中的一个: + +- [nuts][nuts]: *针对你的应用程序的智能发布服务器,使用 GitHub 作为后端。 使用 Squirrel 自动更新(Mac 和 Windows)* +- [electron-release-server][electron-release-server]: *一个功能齐全, + electron 应用的自托管发布服务器,兼容 auto-updater* +- [squirrel-updates-server][squirrel-updates-server]: *对于使用 GitHub 版本的 Squirrel.Mac 和 Squirrel.Windows 的一个简单的 node.js 服务器* +- [squirrel-release-server][squirrel-release-server]: *一个简单的 Squirrel.Windows 的 PHP 应用程序,它从文件夹读取更新,并支持增量更新* ## 平台相关的提示 @@ -9,11 +23,21 @@ ### macOS 在 macOS 上,`autoUpdater` 模块依靠的是内置的 [Squirrel.Mac][squirrel-mac],这意味着你不需要依靠其他的设置就能使用。关于 -更新服务器的配置,你可以通过阅读 [Server Support][server-support] 这篇文章来了解。 +更新服务器的配置,你可以通过阅读 [Server Support][server-support] 这篇文章来了解。注意 [App +Transport Security](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35) (ATS) 适用于所有请求作为的一部分 +更新过程。需要禁用 ATS 的应用程序可以在应用程序的 plist 添加 +`NSAllowsArbitraryLoads` 属性。 + +**注意:** 你的应用程序必须签署 macOS 自动更新。 +这是 `Squirrel.Mac` 的要求。 ### Windows -在 Windows 上,你必须使用安装程序将你的应用装到用户的计算机上,所以比较推荐的方法是用 [grunt-electron-installer][installer] 这个模块来自动生成一个 Windows 安装向导。 +在 Windows 上,你必须使用安装程序将你的应用装到用户的计算机上,所以比较推荐的方法是用 [electron-winstaller][installer-lib], [electron-builder][electron-builder-lib] 或者 [grunt-electron-installer][installer] 模块来自动生成一个 Windows 安装向导。 + +当使用 [electron-winstaller][installer-lib] 或者 [electron-builder][electron-builder-lib] + +当使用 [electron-winstaller][installer-lib] 或者 [electron-builder][electron-builder-lib] 确保你不尝试更新你的应用程序 [the first time it runs](https://github.com/electron/windows-installer#handling-squirrel-events)(另见 [this issue for more info](https://github.com/electron/electron/issues/7155))。 建议使用 [electron-squirrel-startup](https://github.com/mongodb-js/electron-squirrel-startup) 获取应用程序的桌面快捷方式。 Squirrel 自动生成的安装向导会生成一个带 [Application User Model ID][app-user-model-id] 的快捷方式。 Application User Model ID 的格式是 `com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`, 比如 @@ -67,11 +91,16 @@ Linux 下没有任何的自动更新支持,所以我们推荐用各个 Linux `autoUpdater` 对象有以下的方法: -### `autoUpdater.setFeedURL(url)` +### `autoUpdater.setFeedURL(url[, requestHeaders])` * `url` String +* `requestHeaders` Object _macOS_ (optional) - HTTP请求头。 -设置检查更新的 `url`,并且初始化自动更新。这个 `url` 一旦设置就无法更改。 +设置检查更新的 `url`,并且初始化自动更新。 + +### `autoUpdater.getFeedURL()` + +返回 `String` - 当前更新提要 URL。 ### `autoUpdater.checkForUpdates()` @@ -81,8 +110,18 @@ Linux 下没有任何的自动更新支持,所以我们推荐用各个 Linux 在下载完成后,重启当前的应用并且安装更新。这个方法应该仅在 `update-downloaded` 事件触发后被调用。 +**注意:** `autoUpdater.quitAndInstall()` 将先关闭所有应用程序窗口 +并且只在 `app` 上发出 `before-quit` 事件。这不同于 +从正常退出的事件序列。 + [squirrel-mac]: https://github.com/Squirrel/Squirrel.Mac [server-support]: https://github.com/Squirrel/Squirrel.Mac#server-support [squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows -[installer]: https://github.com/atom/grunt-electron-installer +[installer]: https://github.com/electron/grunt-electron-installer +[installer-lib]: https://github.com/electron/windows-installer +[electron-builder-lib]: https://github.com/electron-userland/electron-builder [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[electron-release-server]: https://github.com/ArekSredzki/electron-release-server +[squirrel-updates-server]: https://github.com/Aluxian/squirrel-updates-server +[nuts]: https://github.com/GitbookIO/nuts +[squirrel-release-server]: https://github.com/Arcath/squirrel-release-server diff --git a/docs-translations/zh-CN/api/browser-window.md b/docs-translations/zh-CN/api/browser-window.md index 9faa908503..45fff19a78 100644 --- a/docs-translations/zh-CN/api/browser-window.md +++ b/docs-translations/zh-CN/api/browser-window.md @@ -1,32 +1,122 @@ # BrowserWindow - `BrowserWindow` 类让你有创建一个浏览器窗口的权力。例如: +> 创建和控制浏览器窗口。 + +进程: [Main](../glossary.md#main-process) ```javascript // In the main process. -const BrowserWindow = require('electron').BrowserWindow +const {BrowserWindow} = require('electron') -// Or in the renderer process. -// const BrowserWindow = require('electron').remote.BrowserWindow +// Or use `remote` from the renderer process. +// const {BrowserWindow} = require('electron').remote -var win = new BrowserWindow({ width: 800, height: 600, show: false }) -win.on('closed', function () { +let win = new BrowserWindow({width: 800, height: 600}) +win.on('closed', () => { win = null }) +// Load a remote URL win.loadURL('https://github.com') -win.show() + +// Or load a local HTML file +win.loadURL(`file://${__dirname}/app/index.html`) ``` -你也可以不通过chrome创建窗口,使用 -[Frameless Window](frameless-window.md) API. +## Frameless window + +不通过chrome创建窗口,或者一个任意形状的透明窗口, +你可以使用 [Frameless Window](frameless-window.md) API。 + +## Showing window gracefully + +When loading a page in the window directly, users may see the page load incrementally, which is not a good experience for a native app. To make the window display +without visual flash, there are two solutions for different situations. + +## 优雅地显示窗口 + +当在窗口中直接加载页面时,用户可能会看到增量加载页面,这不是一个好的原生应用程序的体验。使窗口显示 +没有可视闪烁,有两种解决方案适用于不同的情况。 + +### 使用 `ready-to-show` 事件 + +在加载页面时,进程第一次完成绘制时,渲染器会发出 `ready-to-show` 事件 +,在此事件后显示窗口将没有可视闪烁: + +```javascript +const {BrowserWindow} = require('electron') +let win = new BrowserWindow({show: false}) +win.once('ready-to-show', () => { + win.show() +}) +``` + +这个事件通常发生在 `did-finish-load` 事件之后,但是 +页面有许多远程资源,它可能会在 `did-finish-load` 之前发出 +事件。 + +### 设置 `backgroundColor` + +对于一个复杂的应用程序,`ready-to-show` 事件可能发出太晚,使 +应用程序感觉缓慢。在这种情况下,建议立即显示窗口, +并使用接近应用程序的背景 `backgroundColor`: + +```javascript +const {BrowserWindow} = require('electron') + +let win = new BrowserWindow({backgroundColor: '#2e2c29'}) +win.loadURL('https://github.com') +``` + +请注意,即使是使用 `ready-to-show` 事件的应用程序,仍建议使用 +设置 `backgroundColor` 使应用程序感觉更原生。 + +## Parent 和 child 窗口 + +使用 `parent` 选项,你可以创建 child 窗口: + +```javascript +const {BrowserWindow} = require('electron') + +let top = new BrowserWindow() +let child = new BrowserWindow({parent: top}) +child.show() +top.show() +``` + +`child` 窗口将总是显示在 `top` 窗口的顶部。 + +### Modal 窗口 + +模态窗口是禁用父窗口的子窗口,以创建模态 +窗口,你必须设置 `parent` 和 `modal` 选项: + +```javascript +const {BrowserWindow} = require('electron') + +let child = new BrowserWindow({parent: top, modal: true, show: false}) +child.loadURL('https://github.com') +child.once('ready-to-show', () => { + child.show() +}) +``` + +### 平台通知 + +* 在 macOS 上,modal 窗口将显示为附加到父窗口的工作表。 +* 在 macOS 上,子窗口将保持与父窗口的相对位置 +   当父窗口移动时,而在 Windows 和 Linux 子窗口不会 +   移动。 +* 在Windows上,不支持动态更改父窗口。 +* 在Linux上,模态窗口的类型将更改为 `dialog`。 +* 在Linux上,许多桌面环境不支持隐藏模态窗口。 ## Class: BrowserWindow `BrowserWindow` 是一个 -[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). +[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)。 -通过 `options` 可以创建一个具有本质属性的 `BrowserWindow` . +通过 `options` 可以创建一个具有原生属性的 `BrowserWindow`。 ### `new BrowserWindow([options])` @@ -748,4 +838,9 @@ windows上句柄类型为 `HWND` ,macOS `NSView*` , Linux `Window`. 忽略窗口的所有鼠标事件. -[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 + +[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 +[window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels +[chrome-content-scripts]: https://developer.chrome.com/extensions/content_scripts#execution-environment diff --git a/docs-translations/zh-CN/api/content-tracing.md b/docs-translations/zh-CN/api/content-tracing.md index f30a616b3f..6b675d09a7 100644 --- a/docs-translations/zh-CN/api/content-tracing.md +++ b/docs-translations/zh-CN/api/content-tracing.md @@ -1,29 +1,38 @@ # contentTracing -`content-tracing` 模块是用来收集由底层的Chromium content 模块 产生的搜索数据. 这个模块不具备web接口,所有需要我们在chrome浏览器中添加 `chrome://tracing/` 来加载生成文件从而查看结果. +> 从 Chromium 的 content 模块收集跟踪数据,以查找性能 +瓶颈和运行缓慢的原因。 + +进程: [Main](../glossary.md#main-process) + +这个模块不具备web接口,所有需要我们在 Chrome 浏览器中打开 `chrome://tracing/` 来加载生成文件从而查看结果。 + +**注意:** 你不应该使用这个模块,直到应用程序发出 `ready` 事件。 ```javascript -const contentTracing = require('electron').contentTracing +const {app, contentTracing} = require('electron') -const options = { - categoryFilter: '*', - traceOptions: 'record-until-full,enable-sampling' -} +app.on('ready', () => { + const options = { + categoryFilter: '*', + traceOptions: 'record-until-full,enable-sampling' + } -contentTracing.startRecording(options, function () { - console.log('Tracing started') + contentTracing.startRecording(options, () => { + console.log('Tracing started') - setTimeout(function () { - contentTracing.stopRecording('', function (path) { - console.log('Tracing data recorded to ' + path) - }) - }, 5000) + setTimeout(() => { + contentTracing.stopRecording('', (path) => { + console.log('Tracing data recorded to ' + path) + }) + }, 5000) + }) }) ``` ## 方法 - `content-tracing` 模块的方法如下: + `content-tracing` 模块的方法如下: ### `contentTracing.getCategories(callback)` @@ -31,7 +40,7 @@ contentTracing.startRecording(options, function () { 获得一组分类组. 分类组可以更改为新的代码路径。 -一旦所有的子进程都接受到了`getCategories`方法请求, 分类组将调用 `callback`. +一旦所有的子进程都接受到了 `getCategories` 方法请求, 分类组将调用 `callback`。 ### `contentTracing.startRecording(options, callback)` @@ -40,11 +49,11 @@ contentTracing.startRecording(options, function () { * `traceOptions` String * `callback` Function -开始向所有进程进行记录.(recording) +开始向所有进程进行记录。 -一旦收到可以开始记录的请求,记录将会立马启动并且在子进程是异步记录听的. 当所有的子进程都收到 `startRecording` 请求的时候,`callback` 将会被调用. +一旦收到可以开始记录的请求,记录将会立马启动并且在子进程是异步记录听的. 当所有的子进程都收到 `startRecording` 请求的时候,`callback` 将会被调用。 -`categoryFilter`是一个过滤器,它用来控制那些分类组应该被用来查找.过滤器应当有一个可选的 `-` 前缀来排除匹配的分类组.不允许同一个列表既是包含又是排斥. +`categoryFilter`是一个过滤器,它用来控制那些分类组应该被用来查找.过滤器应当有一个可选的 `-` 前缀来排除匹配的分类组。不允许同一个列表既是包含又是排斥。 例子: @@ -52,7 +61,7 @@ contentTracing.startRecording(options, function () { * `test_MyTest*,test_OtherStuff`, * `"-excluded_category1,-excluded_category2` -`traceOptions` 控制着哪种查找应该被启动,这是一个用逗号分隔的列表.可用参数如下: +`traceOptions` 控制着哪种查找应该被启动,这是一个用逗号分隔的列表。可用参数如下: * `record-until-full` * `record-continuously` @@ -60,25 +69,26 @@ contentTracing.startRecording(options, function () { * `enable-sampling` * `enable-systrace` -前3个参数是来查找记录模块,并且以后都互斥.如果在`traceOptions` 中超过一个跟踪 +前3个参数是来查找记录模块,并且以后都互斥。如果在 `traceOptions` 中超过一个跟踪 记录模式,那最后一个的优先级最高.如果没有指明跟踪 -记录模式,那么它默认为 `record-until-full`. +记录模式,那么它默认为 `record-until-full`。 在 `traceOptions` 中的参数被解析应用之前,查找参数初始化默认为 (`record_mode` 设置为 -`record-until-full`, `enable_sampling` 和 `enable_systrace` 设置为 `false`). +`record-until-full`, `enable_sampling` 和 `enable_systrace` 设置为 `false`)。 ### `contentTracing.stopRecording(resultFilePath, callback)` * `resultFilePath` String * `callback` Function + * `resultFilePath` String -停止对所有子进程的记录. +停止对所有子进程的记录。 -子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.这有利于在通过 IPC 发送查找数据之前减小查找时的运行开销,这样做很有价值.因此,发送查找数据,我们应当异步通知所有子进程来截取任何待查找的数据. +子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程。这有利于在通过 IPC 发送查找数据之前减小查找时的运行开销,这样做很有价值.因此,发送查找数据,我们应当异步通知所有子进程来截取任何待查找的数据。 -一旦所有子进程接收到了 `stopRecording` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件. +一旦所有子进程接收到了 `stopRecording` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件。 -如果 `resultFilePath` 不为空,那么将把查找数据写入其中,否则写入一个临时文件.实际文件路径如果不为空,则将调用 `callback` . +如果 `resultFilePath` 不为空,那么将把查找数据写入其中,否则写入一个临时文件。实际文件路径如果不为空,则将调用 `callback`。 ### `contentTracing.startMonitoring(options, callback)` @@ -87,31 +97,34 @@ contentTracing.startRecording(options, function () { * `traceOptions` String * `callback` Function -开始向所有进程进行监听.(monitoring) +开始向所有进程进行监听。 -一旦收到可以开始监听的请求,记录将会立马启动并且在子进程是异步记监听的. 当所有的子进程都收到 `startMonitoring` 请求的时候,`callback` 将会被调用. +一旦收到可以开始监听的请求,记录将会立马启动并且在子进程是异步记监听的。当所有的子进程都收到 `startMonitoring` 请求的时候,`callback` 将会被调用。 ### `contentTracing.stopMonitoring(callback)` * `callback` Function -停止对所有子进程的监听. +停止对所有子进程的监听。 -一旦所有子进程接收到了 `stopMonitoring` 请求,将调用 `callback` . +一旦所有子进程接收到了 `stopMonitoring` 请求,将调用 `callback`。 ### `contentTracing.captureMonitoringSnapshot(resultFilePath, callback)` * `resultFilePath` String * `callback` Function + * `resultFilePath` String -获取当前监听的查找数据. +获取当前监听的查找数据。 -子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.因为如果直接通过 IPC 来发送查找数据的代价昂贵,我们宁愿避免不必要的查找运行开销.因此,为了停止查找,我们应当异步通知所有子进程来截取任何待查找的数据. +子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.因为如果直接通过 IPC 来发送查找数据的代价昂贵,我们宁愿避免不必要的查找运行开销。因此,为了停止查找,我们应当异步通知所有子进程来截取任何待查找的数据。 -一旦所有子进程接收到了 `captureMonitoringSnapshot` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件. +一旦所有子进程接收到了 `captureMonitoringSnapshot` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件。 ### `contentTracing.getTraceBufferUsage(callback)` * `callback` Function + * `value` Number + * `percentage` Number -通过查找 buffer 进程来获取百分比最大使用量.当确定了TraceBufferUsage 的值确定的时候,就调用 `callback` . +通过查找 buffer 进程来获取百分比最大使用量.当确定了TraceBufferUsage 的值确定的时候,就调用 `callback`。 diff --git a/docs-translations/zh-CN/api/dialog.md b/docs-translations/zh-CN/api/dialog.md index 366d7b8ff3..41ec256721 100644 --- a/docs-translations/zh-CN/api/dialog.md +++ b/docs-translations/zh-CN/api/dialog.md @@ -1,6 +1,8 @@ # dialog -`dialog` 模块提供了api来展示原生的系统对话框,例如打开文件框,alert框,所以web应用可以给用户带来跟系统应用相同的体验. +> 显示用于打开和保存文件,alert框等的原生的系统对话框 + +进程: [Main](../glossary.md#main-process) 对话框例子,展示了选择文件和目录: @@ -9,62 +11,95 @@ const {dialog} = require('electron') console.log(dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'multiSelections']})) ``` -**macOS 上的注意事项**: 如果你想像sheets一样展示对话框,只需要在`browserWindow` 参数中提供一个 `BrowserWindow` 的引用对象. +对话框从 Electron 的主线程打开。如果要从渲染器进程使用对话框 +对象,记得使用 remote 访问它: + +```javascript +const {dialog} = require('electron').remote +console.log(dialog) +``` ## 方法 -`dialog` 模块有以下方法: +`dialog` 模块有以下方法: ### `dialog.showOpenDialog([browserWindow, ]options[, callback])` * `browserWindow` BrowserWindow (可选) * `options` Object - * `title` String - * `defaultPath` String - * `filters` Array - * `properties` Array - 包含了对话框的特性值, 可以包含 `openFile`, `openDirectory`, `multiSelections` and - `createDirectory` + * `title` String (可选) + * `defaultPath` String (可选) + * `buttonLabel` String (可选) - Custom label for the confirmation button, when + left empty the default label will be used. + * `filters` [FileFilter[]](structures/file-filter.md) (optional) + * `properties` String[] (可选) - Contains which features the dialog should + use. The following values are supported: + * `openFile` - Allow files to be selected. + * `openDirectory` - Allow directories to be selected. + * `multiSelections` - Allow multiple paths to be selected. + * `showHiddenFiles` - Show hidden files in dialog. + * `createDirectory` _macOS_ - Allow creating new directories from dialog. + * `promptToCreate` _Windows_ - Prompt for creation if the file path entered + in the dialog does not exist. This does not actually create the file at + the path but allows non-existent paths to be returned that should be + created by the application. + * `normalizeAccessKeys` Boolean (可选) - 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 (可选) + * `filePaths` String[] - An array of file paths chosen by the user -成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`. +成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`。 -`filters` 当需要限定用户的行为的时候,指定一个文件数组给用户展示或选择. 例如: +`browserWindow` 参数允许对话框将自身附加到父窗口,使其成为模态。 + +`filters` 当需要限定用户的行为的时候,指定一个文件数组给用户展示或选择。例如: ```javascript { filters: [ - { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, - { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] }, - { name: 'Custom File Type', extensions: ['as'] }, - { name: 'All Files', extensions: ['*'] } + {name: 'Images', extensions: ['jpg', 'png', 'gif']}, + {name: 'Movies', extensions: ['mkv', 'avi', 'mp4']}, + {name: 'Custom File Type', extensions: ['as']}, + {name: 'All Files', extensions: ['*']} ] } ``` -`extensions` 数组应当只包含扩展名,不应该包含通配符或'.'号 (例如 -`'png'` 正确,但是 `'.png'` 和 `'*.png'` 不正确). 展示全部文件的话, 使用 -`'*'` 通配符 (不支持其他通配符). +`extensions` 数组应当只包含扩展名,不应该包含通配符或 '.' 号 (例如 +`'png'` 正确,但是 `'.png'` 和 `'*.png'` 不正确)。展示全部文件的话,使用 +`'*'` 通配符 (不支持其他通配符)。 -如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示. +如果 `callback` 被调用,将异步调用 API ,并且结果将用过 `callback(filenames)` 展示。 **注意:** 在 Windows 和 Linux ,一个打开的 dialog 不能既是文件选择框又是目录选择框, 所以如果在这些平台上设置 `properties` 的值为 -`['openFile', 'openDirectory']` , 将展示一个目录选择框. +`['openFile', 'openDirectory']` ,将展示一个目录选择框。 ### `dialog.showSaveDialog([browserWindow, ]options[, callback])` * `browserWindow` BrowserWindow (可选) * `options` Object - * `title` String - * `defaultPath` String - * `filters` Array + * `title` String (可选) + * `defaultPath` String (可选) + * `buttonLabel` String (可选) - Custom label for the confirmation button, when + left empty the default label will be used. + * `filters` [FileFilter[]](structures/file-filter.md) (optional) * `callback` Function (可选) + * `filename` String -成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`. +成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`。 + +`browserWindow` 参数允许对话框将自身附加到父窗口,使其成为模态。 `filters` 指定展示一个文件类型数组, 例子 -`dialog.showOpenDialog` . +`dialog.showOpenDialog` 。 -如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示. +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示。 ### `dialog.showMessageBox([browserWindow, ]options[, callback])` @@ -72,22 +107,40 @@ console.log(dialog.showOpenDialog({properties: ['openFile', 'openDirectory', 'mu * `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 + * `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` -展示 message box, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值. +返回 `Integer`,如果提供了回调,它会返回点击的按钮的索引或者 undefined 。 -如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(response)` 展示. +显示 message box 时, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值。 + +`browserWindow` 参数允许对话框将自身附加到父窗口,使其成为模态。 + +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(response)` 展示。 ### `dialog.showErrorBox(title, content)` +* `title` String - 错误框中的标题 +* `content` String - 错误框中的内容 + 展示一个传统的包含错误信息的对话框. -在 `app` 模块触发 `ready` 事件之前,这个 api 可以被安全调用,通常它被用来在启动的早期阶段报告错误. 在 Linux 上,如果在 `app` 模块触发 `ready` 事件之前调用,message 将会被触发显示stderr,并且没有实际GUI 框显示. +在 `app` 模块触发 `ready` 事件之前,这个 api 可以被安全调用,通常它被用来在启动的早期阶段报告错误. 在 Linux 上,如果在 `app` 模块触发 `ready` 事件之前调用,message 将会被触发显示 stderr ,并且没有实际 GUI 框显示. + +## Sheets + +在 macOS 上,如果你想像 sheets 一样展示对话框,只需要在`browserWindow` 参数中提供一个 `BrowserWindow` 的引用对象.,如果没有则为模态窗口。 + +你可以调用 `BrowserWindow.getCurrentWindow().setSheetOffset(offset)` 来改变 +sheets 的窗口框架的偏移量。 diff --git a/docs-translations/zh-CN/api/download-item.md b/docs-translations/zh-CN/api/download-item.md index 3e53bbf6ca..d35605bbb1 100644 --- a/docs-translations/zh-CN/api/download-item.md +++ b/docs-translations/zh-CN/api/download-item.md @@ -37,15 +37,20 @@ win.webContents.session.on('will-download', (event, item, webContents) => { ### 事件: 'updated' -当`downloadItem`获得更新时触发。 +* `event` Event +* `state` String + * `progressing` - 下载中。 + * `interrupted` - 下载被中断且可恢复。 + +当`downloadItem`获得更新且未终止时触发。 ### 事件: 'done' * `event` Event * `state` String - * `completed` - 下载成功完成。 + * `completed` - 下载成功。 * `cancelled` - 下载被取消。 - * `interrupted` - 与文件服务器错误的中断连接。 + * `interrupted` - 下载被中断且不可恢复。 当下载处于一个终止状态时触发。这包括了一个完成的下载,一个被取消的下载(via `downloadItem.cancel()`), 和一个被意外中断的下载(无法恢复)。 @@ -65,10 +70,18 @@ win.webContents.session.on('will-download', (event, item, webContents) => { 暂停下载。 +### `downloadItem.isPause()` + +返回一个`Boolean`表示是否暂定下载。 + ### `downloadItem.resume()` 恢复被暂停的下载。 +### `downloadItem.canResume()` + +返回一个`Boolean`表示是否可以恢复被暂停的下载。 + ### `downloadItem.cancel()` 取消下载操作。 @@ -103,3 +116,12 @@ win.webContents.session.on('will-download', (event, item, webContents) => { ### `downloadItem.getContentDisposition()` 以`String`形式返回响应头(response header)中的`Content-Disposition`域。 + +### `downloadItem.getState()` + +以`String`形式返回该下载项的目前状态。 + +* `progressing` - 下载中。 +* `completed` - 下载成功。 +* `cancelled` - 下载被取消。 +* `interrupted` - 下载被中断。 diff --git a/docs-translations/zh-CN/api/global-shortcut.md b/docs-translations/zh-CN/api/global-shortcut.md index c8da091a2d..15037c73c0 100644 --- a/docs-translations/zh-CN/api/global-shortcut.md +++ b/docs-translations/zh-CN/api/global-shortcut.md @@ -1,18 +1,21 @@ -# global-shortcut +# globalShortcut -`global-shortcut` 模块可以便捷的为您设置(注册/注销)各种自定义操作的快捷键. +> 当应用程序没有键盘焦点时检测键盘事件。 -**Note**: 使用此模块注册的快捷键是系统全局的(QQ截图那种), 不要在应用模块(app module)响应 `ready` -消息前使用此模块(注册快捷键). +进程: [Main](../glossary.md#main-process) + +`globalShortcut` 模块可以便捷的为你设置(注册/注销)各种自定义操作的快捷键。 + +**注意:** 使用此模块注册的快捷键是系统全局的(QQ截图那种), 不要在应用模块(app module)响应 `ready` +消息前使用此模块(注册快捷键)。 ```javascript -var app = require('app') -var globalShortcut = require('global-shortcut') +const {app, globalShortcut} = require('electron') -app.on('ready', function () { - // Register a 'ctrl+x' shortcut listener. - var ret = globalShortcut.register('ctrl+x', function () { - console.log('ctrl+x is pressed') +app.on('ready', () => { + // Register a 'CommandOrControl+X' shortcut listener. + const ret = globalShortcut.register('CommandOrControl+X', () => { + console.log('CommandOrControl+X is pressed') }) if (!ret) { @@ -20,12 +23,12 @@ app.on('ready', function () { } // Check whether a shortcut is registered. - console.log(globalShortcut.isRegistered('ctrl+x')) + console.log(globalShortcut.isRegistered('CommandOrControl+X')) }) -app.on('will-quit', function () { +app.on('will-quit', () => { // Unregister a shortcut. - globalShortcut.unregister('ctrl+x') + globalShortcut.unregister('CommandOrControl+X') // Unregister all shortcuts. globalShortcut.unregisterAll() @@ -34,27 +37,35 @@ app.on('will-quit', function () { ## Methods -`global-shortcut` 模块包含以下函数: +`globalShortcut` 模块包含以下函数: ### `globalShortcut.register(accelerator, callback)` * `accelerator` [Accelerator](accelerator.md) * `callback` Function -注册 `accelerator` 快捷键. 当用户按下注册的快捷键时将会调用 `callback` 函数. +注册 `accelerator` 快捷键。当用户按下注册的快捷键时将会调用 `callback` 函数。 + +当 accelerator 已经被其他应用程序占用时,此调用将 +默默地失败。这种行为是操作系统的意图,因为它们没有 +想要应用程序争取全局快捷键。 ### `globalShortcut.isRegistered(accelerator)` * `accelerator` [Accelerator](accelerator.md) -查询 `accelerator` 快捷键是否已经被注册过了,将会返回 `true`(已被注册) 或 `false`(未注册). +返回 `Boolean` - 查询 `accelerator` 快捷键是否已经被注册过了,将会返回 `true` 或 `false`。 + +当 accelerator 已经被其他应用程序占用时,此调用将 +默默地失败。这种行为是操作系统的意图,因为它们没有 +想要应用程序争取全局快捷键。 ### `globalShortcut.unregister(accelerator)` * `accelerator` [Accelerator](accelerator.md) -注销全局快捷键 `accelerator`. +注销全局快捷键 `accelerator`。 ### `globalShortcut.unregisterAll()` -注销本应用注册的所有全局快捷键. +注销本应用程序注册的所有全局快捷键。 diff --git a/docs-translations/zh-CN/api/ipc-main.md b/docs-translations/zh-CN/api/ipc-main.md index c3b4b90eb3..e84a9f4f4e 100644 --- a/docs-translations/zh-CN/api/ipc-main.md +++ b/docs-translations/zh-CN/api/ipc-main.md @@ -1,28 +1,32 @@ # ipcMain +> 从主进程到渲染器进程异步通信。 + +进程:[Main](../glossary.md#main-process) + `ipcMain` 模块是类 -[EventEmitter](https://nodejs.org/api/events.html) 的实例.当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息.从渲染进程发送过来的消息将触发事件. +[EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) 的实例。当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息。从渲染进程发送过来的消息将触发事件。 ## 发送消息 -同样也可以从主进程向渲染进程发送消息,查看更多 [webContents.send][web-contents-send] . +同样也可以从主进程向渲染进程发送消息,查看更多 [webContents.send][web-contents-send] 。 -* 发送消息,事件名为 `channel`. -* 回应同步消息, 你可以设置 `event.returnValue`. +* 发送消息,事件名为 `channel`。 +* 回应同步消息, 你可以设置 `event.returnValue`。 * 回应异步消息, 你可以使用 - `event.sender.send(...)`. + `event.sender.send(...)`。 -一个例子,在主进程和渲染进程之间发送和处理消息: +一个例子,在主进程和渲染进程之间发送和处理消息: ```javascript // In main process. -const ipcMain = require('electron').ipcMain -ipcMain.on('asynchronous-message', function (event, arg) { +const {ipcMain} = require('electron') +ipcMain.on('asynchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.sender.send('asynchronous-reply', 'pong') }) -ipcMain.on('synchronous-message', function (event, arg) { +ipcMain.on('synchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.returnValue = 'pong' }) @@ -30,56 +34,56 @@ ipcMain.on('synchronous-message', function (event, arg) { ```javascript // In renderer process (web page). -const ipcRenderer = require('electron').ipcRenderer +const {ipcRenderer} = require('electron') console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong" -ipcRenderer.on('asynchronous-reply', function (event, arg) { +ipcRenderer.on('asynchronous-reply', (event, arg) => { console.log(arg) // prints "pong" }) ipcRenderer.send('asynchronous-message', 'ping') ``` -## 监听消息 +## 方法 -`ipcMain` 模块有如下监听事件方法: +`ipcMain` 模块有如下监听事件方法: ### `ipcMain.on(channel, listener)` * `channel` String * `listener` Function -监听 `channel`, 当新消息到达,将通过 `listener(event, args...)` 调用 `listener`. +监听 `channel`, 当新消息到达,将通过 `listener(event, args...)` 调用 `listener`。 ### `ipcMain.once(channel, listener)` * `channel` String * `listener` Function -为事件添加一个一次性用的`listener` 函数.这个 `listener` 只有在下次的消息到达 `channel` 时被请求调用,之后就被删除了. +为事件添加一个一次性用的`listener` 函数。这个 `listener` 只有在下次的消息到达 `channel` 时被请求调用,之后就被删除了。 ### `ipcMain.removeListener(channel, listener)` * `channel` String * `listener` Function -为特定的 `channel` 从监听队列中删除特定的 `listener` 监听者. +为特定的 `channel` 从监听队列中删除特定的 `listener` 监听者。 ### `ipcMain.removeAllListeners([channel])` * `channel` String (可选) -删除所有监听者,或特指的 `channel` 的所有监听者. +删除所有监听者,或特指的 `channel` 的所有监听者。 ## 事件对象 -传递给 `callback` 的 `event` 对象有如下方法: +传递给 `callback` 的 `event` 对象有如下方法: ### `event.returnValue` -将此设置为在一个同步消息中返回的值. +将此设置为在一个同步消息中返回的值。 ### `event.sender` -返回发送消息的 `webContents` ,你可以调用 `event.sender.send` 来回复异步消息,更多信息 [webContents.send][web-contents-send]. +返回发送消息的 `webContents` ,你可以调用 `event.sender.send` 来回复异步消息,更多信息 [webContents.send][web-contents-send]。 -[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- \ No newline at end of file +[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- diff --git a/docs-translations/zh-CN/api/menu-item.md b/docs-translations/zh-CN/api/menu-item.md index 6d5d59d934..75c3b935fd 100644 --- a/docs-translations/zh-CN/api/menu-item.md +++ b/docs-translations/zh-CN/api/menu-item.md @@ -1,28 +1,37 @@ # 菜单项 -菜单项模块允许你向应用或[menu][1]添加选项。 -查看[menu][1]例子。 +## 类:菜单项 -## 类:MenuItem -使用下面的方法创建一个新的 `MenuItem` +> 向原生的应用菜单和 context 菜单添加菜单项。 + +进程: [Main](../glossary.md#main-process) + +查看 [`Menu`](menu.md) 的示。 + +### `new MenuItem(options)` -###new MenuItem(options) * `options` Object - * `click` Function - 当菜单项被点击的时候,使用 `click(menuItem,browserWindow)` 调用 - * `role` String - 定义菜单项的行为,在指定 `click` 属性时将会被忽略 - * `type` String - 取值 `normal`,`separator`,`checkbox`or`radio` - * `label` String - * `sublabel` String - * `accelerator` [Accelerator][2] - * `icon` [NativeImage][3] - * `enabled` Boolean - * `visible` Boolean - * `checked` Boolean - * `submenu` Menu - 应当作为 `submenu` 菜单项的特定类型,当它作为 `type: 'submenu'` 菜单项的特定类型时可以忽略。如果它的值不是 `Menu`,将自动转为 `Menu.buildFromTemplate`。 - * `id` String - 标志一个菜单的唯一性。如果被定义使用,它将被用作这个菜单项的参考位置属性。 - * `position` String - 定义给定的菜单的具体指定位置信息。 + * `click` Function (可选) - 当菜单项被点击的时候,使用 `click(menuItem,browserWindow)` 调用。 + * `menuItem` MenuItem + * `browserWindow` BrowserWindow + * `event` Event + * `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,菜单项将显示为灰色不可点击。 + * `visible` Boolean (可选) - 如果为 false,菜单项将完全隐藏。 + * `checked` Boolean (可选) - 只为 `checkbox` 或 `radio` 类型的菜单项。 + * `submenu` (MenuItemConstructorOptions[] | Menu) (可选) - 应当作为 `submenu` 菜单项的特定类型,当它作为 `type: 'submenu'` 菜单项的特定类型时可以忽略。如果它的值不是 `Menu`,将自动转为 `Menu.buildFromTemplate`。 + * `id` String (可选) - 菜单的唯一标识。如果被定义使用,它将被用作这个菜单项的参考位置属性。 + * `position` String (可选) - 定义菜单的具体指定位置信息。 -在创建菜单项时,如果有匹配的方法,建议指定 `role` 属性,不需要人为操作它的行为,这样菜单使用可以给用户最好的体验。 +### Roles +Roles 允许菜单项有预定义的行为。最好为每个菜单项指定一个行为,而不是自己实现一个 `click` 函数中的行为。内置的 `role` 行为将提供最好的原生体验。 + +当使用 `role` 时,`label` 和 `accelerator` 的值是可选的,会针对每个平台设置默认值。 `role`属性值可以为: @@ -32,9 +41,21 @@ * `cut` * `copy` * `paste` +* `pasteandmatchstyle` * `selectall` +* `delete` * `minimize` - 最小化当前窗口 * `close` - 关闭当前窗口 +* `quit`- 退出应用程序 +* `reload` - 正常重新加载当前窗口 +* `forcereload` - 忽略缓存并重新加载当前窗口 +* `toggledevtools` - 在当前窗口中切换开发者工具 +* `togglefullscreen`- 在当前窗口中切换全屏模式 +* `resetzoom` - 将对焦页面的缩放级别重置为原始大小 +* `zoomin` - 将聚焦页面缩小10% +* `zoomout` - 将聚焦页面放大10% +* `editMenu` - 完整的默认 "Edit" 编辑菜单(拷贝,黏贴,等) +* `windowMenu` - 完整的默认 "Window" 窗口菜单(最小化,关闭,等) 在 macOS 上,`role` 还可以有以下值: @@ -42,16 +63,44 @@ * `hide` - 匹配 `hide` 行为 * `hideothers` - 匹配 `hideOtherApplications` 行为 * `unhide` - 匹配 `unhideAllApplications` 行为 +* `startspeaking` - 匹配 `startSpeaking` 行为 +* `stopspeaking` - 匹配 `stopSpeaking` 行为 * `front` - 匹配 `arrangeInFront` 行为 +* `zoom` - 匹配 `performZoom` 行为 * `window` - "Window" 菜单项 * `help` - "Help" 菜单项 * `services` - "Services" 菜单项 +当在 macOS 上指定 `role' 时,`label` 和 `accelerator` 是影响MenuItem的唯一的选项 +所有其他选项将被忽略。 +### 实例属性 +`MenuItem` 对象拥有以下属性: +#### `menuItem.enabled` +一个布尔值表示是否启用该项,此属性可以动态改变。 - [1]:https://github.com/heyunjiang/electron/blob/master/docs-translations/zh-CN/api/menu.md - [2]:https://github.com/heyunjiang/electron/blob/master/docs/api/accelerator.md - [3]:https://github.com/heyunjiang/electron/blob/master/docs/api/native-image.md \ No newline at end of file +#### `menuItem.visible` + +一个布尔值表示是否可见,此属性可以动态改变。 + +#### `menuItem.checked` + +一个布尔值表示是否选中该项,此属性可以动态改变。 + +`checkbox` 菜单项将在选中和未选中切换 `checked` 属性。 + +`radio` 菜单项将在选中切换 `checked` 属性,并且 +将关闭同一菜单中所有相邻项目的属性。 + +您可以为其他行为添加一个 `click` 函数。 + +#### `menuItem.label` + +一个表示菜单项可见标签的字符串 + +#### `menuItem.click` + +当 MenuItem 接收到点击事件时触发的函数 diff --git a/docs-translations/zh-CN/api/menu.md b/docs-translations/zh-CN/api/menu.md index 07736d4bbd..ebccfe5ee9 100644 --- a/docs-translations/zh-CN/api/menu.md +++ b/docs-translations/zh-CN/api/menu.md @@ -1,269 +1,226 @@ -# 菜单 +# 类:菜单 -`menu` 类可以用来创建原生菜单,它可用作应用菜单和 -[context 菜单](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/ContextMenus). +> 创建原生的应用菜单和 context 菜单。 -这个模块是一个主进程的模块,并且可以通过 `remote` 模块给渲染进程调用. +进程: [Main](../glossary.md#main-process) -每个菜单有一个或几个菜单项 [menu items](menu-item.md),并且每个菜单项可以有子菜单. +### `new Menu()` -下面这个例子是在网页(渲染进程)中通过 [remote](remote.md) 模块动态创建的菜单,并且右键显示: +创建一个新的菜单。 -```html - - -``` +#### `Menu.setApplicationMenu(menu)` -例子,在渲染进程中使用模板api创建应用菜单: +* `menu` Menu + +在 macOS 上设置应用菜单 `menu`。 +在 windows 和 linux,是为每个窗口都在其顶部设置菜单 `menu`。 + +设置为 `null` 时,将在 Windows 和 Linux 上删除菜单条,但在 macOS 系统中无效。 + +**注意:** 这个API必须在 `app` 模块的 `ready` 事件后调用。 + +#### `Menu.getApplicationMenu()` + +返回 `Menu` - 应用程序菜单,设置、 `null` 、或未设置。 + +#### `Menu.sendActionToFirstResponder(action)` _macOS_ + +* `action` String + +发送 `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) . + +#### `Menu.buildFromTemplate(template)` + +* `template` MenuItemConstructorOptions[] + +返回 `Menu` + +一般来说,`template` 只是用来创建 [MenuItem](menu-item.md) 的数组 `参数`。 + +你也可以向 `template` 元素添加其它东西,并且他们会变成已经有的菜单项的属性。 + +### 实例方法 + +`menu` 对象有如下实例方法 + +#### `menu.popup([browserWindow, options])` + +* `browserWindow` BrowserWindow (可选) - 默认为当前激活的窗口. +* `options` Object (可选) + * `x` Number (可选) - 默认为当前光标所在的位置. + * `y` Number (**必须** 如果x设置了) - 默认为当前光标所在的位置. + * `async` Boolean (可选) - 设置为 `true` 时,调用这个方法会立即返回。设置为 `false` 时,当菜单被选择或者被关闭时才会返回。默认为 `false`。 + * `positioningItem` Number (可选) _macOS_ - 指定坐标鼠标位置下面的菜单项的索引. 默认为 + -1. + +在 `browserWindow` 中弹出菜单. + +#### `menu.closePopup([browserWindow])` + +* `browserWindow` BrowserWindow (可选) - 默认为当前激活的窗口. + +在 `browserWindow` 关闭菜单. + +#### `menu.append(menuItem)` + +* `menuItem` MenuItem + +添加菜单项。 + +#### `menu.insert(pos, menuItem)` + +* `pos` Integer +* `menuItem` MenuItem + +在指定位置添加菜单项。 + +### 实例属性 + +`menu` 对象拥有以下属性: + +#### `menu.items()` + +获取一个菜单项数组。 + +## 例子 + +`Menu` 类只能在主进程中使用,但你也可以 +在渲染过程中通过 [`remote`](remote.md) 模块使用它。 + +### 主进程 + +在主进程中创建应用程序菜单的示例 +简单模板API: ```javascript -var template = [ +const {app, Menu} = require('electron') + +const template = [ { label: 'Edit', submenu: [ - { - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - role: 'undo' - }, - { - label: 'Redo', - accelerator: 'Shift+CmdOrCtrl+Z', - role: 'redo' - }, - { - type: 'separator' - }, - { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - role: 'cut' - }, - { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - role: 'copy' - }, - { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - role: 'paste' - }, - { - label: 'Select All', - accelerator: 'CmdOrCtrl+A', - role: 'selectall' - } + {role: 'undo'}, + {role: 'redo'}, + {type: 'separator'}, + {role: 'cut'}, + {role: 'copy'}, + {role: 'paste'}, + {role: 'pasteandmatchstyle'}, + {role: 'delete'}, + {role: 'selectall'} ] }, { label: 'View', submenu: [ - { - label: 'Reload', - accelerator: 'CmdOrCtrl+R', - click: function (item, focusedWindow) { - if (focusedWindow) focusedWindow.reload() - } - }, - { - label: 'Toggle Full Screen', - accelerator: (function () { - return (process.platform === 'darwin') ? 'Ctrl+Command+F' : 'F11' - })(), - click: function (item, focusedWindow) { - if (focusedWindow) focusedWindow.setFullScreen(!focusedWindow.isFullScreen()) - } - }, - { - label: 'Toggle Developer Tools', - accelerator: (function () { - if (process.platform === 'darwin') { - return 'Alt+Command+I' - } else { - return 'Ctrl+Shift+I' - } - })(), - click: function (item, focusedWindow) { - if (focusedWindow) focusedWindow.toggleDevTools() - } - } + {role: 'reload'}, + {role: 'forcereload'}, + {role: 'toggledevtools'}, + {type: 'separator'}, + {role: 'resetzoom'}, + {role: 'zoomin'}, + {role: 'zoomout'}, + {type: 'separator'}, + {role: 'togglefullscreen'} ] }, { - label: 'Window', role: 'window', submenu: [ - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - } + {role: 'minimize'}, + {role: 'close'} ] }, { - label: 'Help', role: 'help', submenu: [ { label: 'Learn More', - click: function () { require('electron').shell.openExternal('http://electron.atom.io') } + click () { require('electron').shell.openExternal('https://electron.atom.io') } } ] } ] if (process.platform === 'darwin') { - var name = require('electron').remote.app.getName() template.unshift({ - label: name, + label: app.getName(), submenu: [ - { - label: 'About ' + name, - role: 'about' - }, - { - type: 'separator' - }, - { - label: 'Services', - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - label: 'Hide ' + name, - accelerator: 'Command+H', - role: 'hide' - }, - { - label: 'Hide Others', - accelerator: 'Command+Alt+H', - role: 'hideothers' - }, - { - label: 'Show All', - role: 'unhide' - }, - { - type: 'separator' - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: function () { app.quit() } - } + {role: 'about'}, + {type: 'separator'}, + {role: 'services', submenu: []}, + {type: 'separator'}, + {role: 'hide'}, + {role: 'hideothers'}, + {role: 'unhide'}, + {type: 'separator'}, + {role: 'quit'} ] }) - // Window menu. - template[3].submenu.push( + + // Edit menu + template[1].submenu.push( + {type: 'separator'}, { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' + label: 'Speech', + submenu: [ + {role: 'startspeaking'}, + {role: 'stopspeaking'} + ] } ) + + // Window menu + template[3].submenu = [ + {role: 'close'}, + {role: 'minimize'}, + {role: 'zoom'}, + {type: 'separator'}, + {role: 'front'} + ] } -var menu = Menu.buildFromTemplate(template) +const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu) ``` -## 类: Menu +### 渲染进程 -### `new Menu()` +以下是通过使用 [`remote`](remote.md) 模块在网页(渲染进程)中动态创建菜单的示例 +,并在用户点击右键时显示: -创建一个新的菜单. +```html + + +``` ## macOS Application 上的菜单的注意事项 -相对于windows 和 linux, macOS 上的应用菜单是完全不同的style,这里是一些注意事项,来让你的菜单项更原生化. +相对于 windows 和 linux, macOS 上的应用菜单是完全不同的 style,这里是一些注意事项,来让你的菜单项更原生化。 ### 标准菜单 -在 macOS 上,有很多系统定义的标准菜单,例如 `Services` and -`Windows` 菜单.为了让你的应用更标准化,你可以为你的菜单的 `role` 设置值,然后 electron 将会识别他们并且让你的菜单更标准: +在 macOS 上,有很多系统定义的标准菜单,例如 `Services` and +`Windows` 菜单.为了让你的应用更标准化,你可以为你的菜单的 `role` 设置值,然后 Electron 将会识别他们并且让你的菜单更标准: * `window` * `help` @@ -271,29 +228,29 @@ Menu.setApplicationMenu(menu) ### 标准菜单项行为 -macOS 为一些菜单项提供了标准的行为方法,例如 `About xxx`, -`Hide xxx`, and `Hide Others`. 为了让你的菜单项的行为更标准化,你应该为菜单项设置 `role` 属性. +macOS 为一些菜单项提供了标准的行为方法,例如 `About xxx`, +`Hide xxx`,和 `Hide Others`. 为了让你的菜单项的行为更标准化,你应该为菜单项设置 `role` 属性。 ### 主菜单名 -在 macOS ,无论你设置的什么标签,应用菜单的第一个菜单项的标签始终未你的应用名字.想要改变它的话,你必须通过修改应用绑定的 `Info.plist` 文件来修改应用名字.更多信息参考[About Information -Property List Files][AboutInformationPropertyListFiles] . +在 macOS ,无论你设置的什么标签,应用菜单的第一个菜单项的标签始终未你的应用名字。想要改变它的话,你必须通过修改应用绑定的 `Info.plist` 文件来修改应用名字,更多信息参考 [About Information +Property List Files][AboutInformationPropertyListFiles] 。 ## 为制定浏览器窗口设置菜单 (*Linux* *Windows*) -浏览器窗口的[`setMenu` 方法][setMenu] 能够设置菜单为特定浏览器窗口的类型. +浏览器窗口的 [`setMenu` 方法][setMenu] 能够设置菜单为特定浏览器窗口的类型。 ## 菜单项位置 -当通过 `Menu.buildFromTemplate` 创建菜单的时候,你可以使用 `position` and `id` 来放置菜单项. +当通过 `Menu.buildFromTemplate` 创建菜单的时候,你可以使用 `position` and `id` 来放置菜单项。 -`MenuItem` 的属性 `position` 格式为 `[placement]=[id]`,`placement` 取值为 `before`, `after`, 或 `endof` 和 `id`, `id` 是菜单已经存在的菜单项的唯一 ID: +`MenuItem` 的属性 `position` 格式为 `[placement]=[id]`,`placement` 取值为 `before`, `after`, 或 `endof` 和 `id`, `id` 是菜单已经存在的菜单项的唯一 ID: -* `before` - 在对应引用id菜单项之前插入. 如果引用的菜单项不存在,则将其插在菜单末尾. -* `after` - 在对应引用id菜单项之后插入. 如果引用的菜单项不存在,则将其插在菜单末尾. -* `endof` - 在逻辑上包含对应引用id菜单项的集合末尾插入. 如果引用的菜单项不存在, 则将使用给定的id创建一个新的集合,并且这个菜单项将插入. +* `before` - 在对应引用id菜单项之前插入。如果引用的菜单项不存在,则将其插在菜单末尾。 +* `after` - 在对应引用id菜单项之后插入。如果引用的菜单项不存在,则将其插在菜单末尾。 +* `endof` - 在逻辑上包含对应引用id菜单项的集合末尾插入。如果引用的菜单项不存在, 则将使用给定的id创建一个新的集合,并且这个菜单项将插入。 -当一个菜档项插入成功了,所有的没有插入的菜单项将一个接一个地在后面插入.所以如果你想在同一个位置插入一组菜单项,只需要为这组菜单项的第一个指定位置. +当一个菜档项插入成功了,所有的没有插入的菜单项将一个接一个地在后面插入。所以如果你想在同一个位置插入一组菜单项,只需要为这组菜单项的第一个指定位置。 ### 例子 @@ -346,5 +303,4 @@ Property List Files][AboutInformationPropertyListFiles] . ``` [AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html -[setMenu]: -https://github.com/electron/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows +[setMenu]: https://github.com/electron/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows diff --git a/docs-translations/zh-CN/api/power-monitor.md b/docs-translations/zh-CN/api/power-monitor.md index 7395a8b3b4..39bbaa6efe 100644 --- a/docs-translations/zh-CN/api/power-monitor.md +++ b/docs-translations/zh-CN/api/power-monitor.md @@ -1,12 +1,19 @@ # powerMonitor -`power-monitor`模块是用来监听能源区改变的.只能在主进程中使用.在 `app` 模块的 `ready` 事件触发之后就不能使用这个模块了. +> 监视电源状态更改。 + +进程: [Main](../glossary.md#main-process) + +在 `app` 模块的 `ready` 事件触发之后就不能使用这个模块了。 例如: ```javascript -app.on('ready', function () { - require('electron').powerMonitor.on('suspend', function () { +const electron = require('electron') +const {app} = electron + +app.on('ready', () => { + electron.powerMonitor.on('suspend', () => { console.log('The system is going to sleep') }) }) @@ -14,23 +21,20 @@ app.on('ready', function () { ## 事件 -`power-monitor` 模块可以触发下列事件: +`powerMonitor` 模块可以触发下列事件: ### Event: 'suspend' -在系统挂起的时候触发. +在系统挂起的时候触发。 ### Event: 'resume' -在系统恢复继续工作的时候触发. -Emitted when system is resuming. +在系统恢复继续工作的时候触发。 ### Event: 'on-ac' -在系统使用交流电的时候触发. -Emitted when the system changes to AC power. +在系统使用交流电的时候触发。 ### Event: 'on-battery' -在系统使用电池电源的时候触发. -Emitted when system changes to battery power. \ No newline at end of file +在系统使用电池电源的时候触发。 diff --git a/docs-translations/zh-CN/api/power-save-blocker.md b/docs-translations/zh-CN/api/power-save-blocker.md index 67b1396d86..da6ffbdb80 100644 --- a/docs-translations/zh-CN/api/power-save-blocker.md +++ b/docs-translations/zh-CN/api/power-save-blocker.md @@ -1,13 +1,15 @@ # powerSaveBlocker -`powerSaveBlocker` 模块是用来阻止应用系统进入睡眠模式的,因此这允许应用保持系统和屏幕继续工作. +> 阻止系统进入低功耗(睡眠)模式。 + +进程: [Main](../glossary.md#main-process) 例如: ```javascript -const powerSaveBlocker = require('electron').powerSaveBlocker +const {powerSaveBlocker} = require('electron') -var id = powerSaveBlocker.start('prevent-display-sleep') +const id = powerSaveBlocker.start('prevent-display-sleep') console.log(powerSaveBlocker.isStarted(id)) powerSaveBlocker.stop(id) @@ -15,34 +17,36 @@ powerSaveBlocker.stop(id) ## 方法 -`powerSaveBlocker` 模块有如下方法: +`powerSaveBlocker` 模块有如下方法: ### `powerSaveBlocker.start(type)` -* `type` String - 强行保存阻塞类型. - * `prevent-app-suspension` - 阻止应用挂起. - 保持系统活跃,但是允许屏幕不亮. 用例: - 下载文件或者播放音频. - * `prevent-display-sleep`- 阻止应用进入休眠. 保持系统和屏幕活跃,屏幕一直亮. 用例: 播放音频. +* `type` String - 强行保存阻塞类型。 + * `prevent-app-suspension` - 阻止应用挂起。 + 保持系统活跃,但是允许屏幕不亮。例如: + 下载文件或者播放音频。 + * `prevent-display-sleep`- 阻止应用进入休眠。保持系统和屏幕活跃,屏幕一直亮。例如:播放音频。 -开始阻止系统进入睡眠模式.返回一个整数,这个整数标识了保持活跃的blocker. +返回 `Integer` - 分配给此阻断器的 blocker ID -**注意:** `prevent-display-sleep` 有更高的优先级 -`prevent-app-suspension`. 只有最高优先级生效. 换句话说, `prevent-display-sleep` 优先级永远高于 -`prevent-app-suspension`. +开始阻止系统进入睡眠模式。返回一个整数,这个整数标识了保持活跃的blocker ID。 -例如, A 请求调用了 `prevent-app-suspension`, B请求调用了 `prevent-display-sleep`. `prevent-display-sleep` -将一直工作,直到B停止调用. 在那之后, `prevent-app-suspension` -才起效. +**注意:** `prevent-display-sleep` 有更高的优先级 +`prevent-app-suspension`。只有最高优先级生效,换句话说, `prevent-display-sleep` 优先级永远高于 +`prevent-app-suspension`。 + +例如, A 请求调用了 `prevent-app-suspension`,B请求调用了 `prevent-display-sleep`。`prevent-display-sleep` +将一直工作,直到B停止调用。在那之后,`prevent-app-suspension` +才起效。 ### `powerSaveBlocker.stop(id)` -* `id` Integer - 通过 `powerSaveBlocker.start` 返回的保持活跃的 blocker id. +* `id` Integer - 通过 `powerSaveBlocker.start` 返回保持活跃的 blocker id. -让指定blocker 停止活跃. +让指定 blocker 停止活跃。 ### `powerSaveBlocker.isStarted(id)` -* `id` Integer - 通过 `powerSaveBlocker.start` 返回的保持活跃的 blocker id. +* `id` Integer - 通过 `powerSaveBlocker.start` 返回保持活跃的 blocker id. -返回 boolean, 是否对应的 `powerSaveBlocker` 已经启动. \ No newline at end of file +返回 boolean,对应的 `powerSaveBlocker` 是否已经启动。 diff --git a/docs-translations/zh-CN/api/protocol.md b/docs-translations/zh-CN/api/protocol.md index 44c5a0a05f..e188bc0fcd 100644 --- a/docs-translations/zh-CN/api/protocol.md +++ b/docs-translations/zh-CN/api/protocol.md @@ -1,8 +1,10 @@ # 协议 -`protocol` 模块可以注册一个自定义协议,或者使用一个已经存在的协议. +> 注册一个自定义协议,或者使用一个已经存在的协议。 -例子,使用一个与 `file://` 功能相似的协议 : +进程: [Main](../glossary.md#main-process) + +例如使用一个与 `file://` 功能相似的协议: ```javascript const {app, protocol} = require('electron') @@ -18,70 +20,112 @@ app.on('ready', () => { }) ``` -**注意:** 这个模块只有在 `app` 模块的 `ready` 事件触发之后才可使用. +**注意:** 这个模块只有在 `app` 模块的 `ready` 事件触发之后才可使用。 ## 方法 -`protocol` 模块有如下方法: +`protocol` 模块有如下方法: -### `protocol.registerStandardSchemes(schemes)` +### `protocol.registerStandardSchemes(schemes[, options])` -* `schemes` Array - 将一个自定义的方案注册为标准的方案. +* `schemes` String[] - 将一个自定义的方案注册为标准的方案。 +* `options` Object (可选) + * `secure` Boolean (可选) - `true` 将方案注册为安全。 + 默认值 `false`。 一个标准的 `scheme` 遵循 RFC 3986 的 -[generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3) 标准. 这包含了 `file:` 和 `filesystem:`. +[generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3) 标准。例如 `http` 和 +`https` 是标准方案,而 `file` 不是。 + +注册一个 `scheme` 作为标准,将允许相对和绝对的资源 +在服务时正确解析。 否则该方案将表现得像 +`file` 协议,但无法解析相对 URLs。 + +例如,当你加载以下页面与自定义协议无法 +注册为标准 `scheme`,图像将不会加载,因为 +非标准方案无法识别相对 URLs: + +```html + + + +``` + +注册方案作为标准将允许通过访问文件 +[FileSystem API][file-system-api]。 否则渲染器将抛出一个安全性 +错误。 + +默认情况下 web storage apis(localStorage,sessionStorage,webSQL,indexedDB,cookies) +对于非标准方案禁用。所以一般来说如果你想注册一个 +自定义协议替换 `http` 协议,您必须将其注册为标准方案: + +```javascript +const {app, protocol} = require('electron') + +protocol.registerStandardSchemes(['atom']) +app.on('ready', () => { + protocol.registerHttpProtocol('atom', '...') +}) +``` + +**注意:** 这个方法只有在 `app` 模块的 `ready` 事件触发之后才可使用。 ### `protocol.registerServiceWorkerSchemes(schemes)` -* `schemes` Array - 将一个自定义的方案注册为处理 service workers. +* `schemes` String[] - 将一个自定义的方案注册为处理 service workers。 ### `protocol.registerFileProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `filePath` String (可选) * `completion` Function (可选) + * `error` Error -注册一个协议,用来发送响应文件.当通过这个协议来发起一个请求的时候,将使用 `handler(request, callback)` 来调用 -`handler` .当 `scheme` 被成功注册或者完成(错误)时失败,将使用 `completion(null)` 调用 `completion`. - -* `request` Object - * `url` String - * `referrer` String - * `method` String - * `uploadData` Array (可选) -* `callback` Function - -`uploadData` 是一个 `data` 对象数组: - -* `data` Object - * `bytes` Buffer - 被发送的内容. - * `file` String - 上传的文件路径. +注册一个协议,用来发送响应文件。当通过这个协议来发起一个请求的时候,将使用 `handler(request,callback)` 来调用 +`handler`。当 `scheme` 被成功注册或者完成(错误)时失败,将使用 `completion(null)` 调用 `completion`。 为了处理请求,调用 `callback` 时需要使用文件路径或者一个带 `path` 参数的对象, 例如 `callback(filePath)` 或 -`callback({path: filePath})`. +`callback({path: filePath})`。 -当不使用任何参数调用 `callback` 时,你可以指定一个数字或一个带有 `error` 参数的对象,来标识 `request` 失败.你可以使用的 error number 可以参考 -[net error list][net-error]. +当不使用任何参数调用 `callback` 时,你可以指定一个数字或一个带有 `error` 参数的对象,来标识 `request` 失败。你可以使用的 error number 可以参考 +[net error list][net-error]。 -默认 `scheme` 会被注册为一个 `http:` 协议,它与遵循 "generic URI syntax" 规则的协议解析不同,例如 `file:` ,所以你或许应该调用 `protocol.registerStandardSchemes` 来创建一个标准的 scheme. +默认 `scheme` 会被注册为一个 `http:` 协议,它与遵循 "generic URI syntax" 规则的协议解析不同,例如 `file:`,所以你或许应该调用 `protocol.registerStandardSchemes` 来创建一个标准的 scheme。 ### `protocol.registerBufferProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `buffer` (Buffer | [MimeTypedBuffer](structures/mime-typed-buffer.md)) (optional) * `completion` Function (可选) + * `error` Error -注册一个 `scheme` 协议,用来发送响应 `Buffer` . +注册一个 `scheme` 协议,用来发送响应 `Buffer`。 -这个方法的用法类似 `registerFileProtocol`,除非使用一个 `Buffer` 对象,或一个有 `data`, -`mimeType`, 和 `charset` 属性的对象来调用 `callback` . +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `Buffer` 对象,或一个有 `data`、 +`mimeType` 和 `charset` 属性的对象来调用 `callback`。 例子: ```javascript -protocol.registerBufferProtocol('atom', function (request, callback) { +const {protocol} = require('electron') + +protocol.registerBufferProtocol('atom', (request, callback) => { callback({mimeType: 'text/html', data: new Buffer('
Response
')}) -}, function (error) { +}, (error) => { if (error) console.error('Failed to register protocol') }) ``` @@ -90,90 +134,143 @@ protocol.registerBufferProtocol('atom', function (request, callback) { * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `data` String (可选) * `completion` Function (可选) + * `error` Error -注册一个 `scheme` 协议,用来发送响应 `String` . +注册一个 `scheme` 协议,用来发送响应 `String`。 -这个方法的用法类似 `registerFileProtocol`,除非使用一个 `String` 对象,或一个有 `data`, -`mimeType`, 和 `charset` 属性的对象来调用 `callback` . +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `String` 对象,或一个有 `data`、 +`mimeType` 和 `charset` 属性的对象来调用 `callback`。 ### `protocol.registerHttpProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (可选) + * `uploadData` Object (可选) + * `contentType` String - MIME type of the content. + * `data` String - Content to be sent. * `completion` Function (可选) + * `error` Error 注册一个 `scheme` 协议,用来发送 HTTP 请求作为响应. -这个方法的用法类似 `registerFileProtocol`,除非使用一个 `redirectRequest` 对象,或一个有 `url`, `method`, -`referrer`, `uploadData` 和 `session` 属性的对象来调用 `callback` . +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `redirectRequest` 对象,或一个有 `url`、 `method`、 +`referrer`、 `uploadData` 和 `session` 属性的对象来调用 `callback`。 -* `redirectRequest` Object - * `url` String - * `method` String - * `session` Object (可选) - * `uploadData` Object (可选) +默认这个 HTTP 请求会使用当前 session。如果你想使用不同的session值,你应该设置 `session` 为 `null`。 -默认这个 HTTP 请求会使用当前 session .如果你想使用不同的session值,你应该设置 `session` 为 `null`. - -POST 请求应当包含 `uploadData` 对象. - -* `uploadData` object - * `contentType` String - 内容的 MIME type. - * `data` String - 被发送的内容. +POST 请求应当包含 `uploadData` 对象。 ### `protocol.unregisterProtocol(scheme[, completion])` * `scheme` String * `completion` Function (可选) + * `error` Error -注销自定义协议 `scheme`. +注销自定义协议 `scheme`。 ### `protocol.isProtocolHandled(scheme, callback)` * `scheme` String * `callback` Function + * `error` Error -将使用一个布尔值来调用 `callback` ,这个布尔值标识了是否已经存在 `scheme` 的句柄了. +将使用一个布尔值来调用 `callback` ,这个布尔值标识了是否已经存在 `scheme` 的句柄了。 ### `protocol.interceptFileProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `filePath` String * `completion` Function (可选) + * `error` Error -拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应文件. +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应文件。 ### `protocol.interceptStringProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `data` String (可选) * `completion` Function (可选) + * `error` Error -拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `String`. +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `String`。 ### `protocol.interceptBufferProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `buffer` Buffer (可选) * `completion` Function (可选) + * `error` Error -拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `Buffer`. +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `Buffer`。 ### `protocol.interceptHttpProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function -* `completion` Function (optional) + * `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` [UploadData[]](structures/upload-data.md) + * `callback` Function + * `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (可选) + * `uploadData` Object (可选) + * `contentType` String - MIME type of the content. + * `data` String - Content to be sent. +* `completion` Function (可选) + * `error` Error -拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送新的响应 HTTP 请求. -Intercepts `scheme` protocol and uses `handler` as the protocol's new handler -which sends a new HTTP request as a response. +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送新的响应 HTTP 请求。 ### `protocol.uninterceptProtocol(scheme[, completion])` * `scheme` String -* `completion` Function -取消对 `scheme` 的拦截,使用它的原始句柄进行处理. +* `completion` Function (可选) + * `error` Error + +取消对 `scheme` 的拦截,使用它的原始句柄进行处理。 [net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h +[file-system-api]: https://developer.mozilla.org/en-US/docs/Web/API/LocalFileSystem diff --git a/docs-translations/zh-CN/api/session.md b/docs-translations/zh-CN/api/session.md index 927b9c16ab..be02c26bc2 100644 --- a/docs-translations/zh-CN/api/session.md +++ b/docs-translations/zh-CN/api/session.md @@ -1,46 +1,64 @@ # session -`session` 模块可以用来创建一个新的 `Session` 对象. +> 管理浏览器会话,Cookie,缓存,代理设置等。 -你也可以通过使用 [`webContents`](web-contents.md) 的属性 `session` 来使用一个已有页面的 `session` ,`webContents` 是[`BrowserWindow`](browser-window.md) 的属性. +进程: [Main](../glossary.md#main-process) + +`session` 模块可以用来创建一个新的 `Session` 对象。 + +你也可以通过使用 [`webContents`](web-contents.md) 的属性 `session` 来使用一个已有页面的 `session` ,`webContents` 是[`BrowserWindow`](browser-window.md) 的属性。 ```javascript -const BrowserWindow = require('electron').BrowserWindow +const {BrowserWindow} = require('electron') -var win = new BrowserWindow({ width: 800, height: 600 }) +let win = new BrowserWindow({width: 800, height: 600}) win.loadURL('http://github.com') -var ses = win.webContents.session +const ses = win.webContents.session +console.log(ses.getUserAgent()) ``` ## 方法 -`session` 模块有如下方法: +`session` 模块有如下方法: -### session.fromPartition(partition) +### `session.fromPartition(partition[, options])` * `partition` String +* `options` Object + * `cache` Boolean - 是否启用缓存。 -从字符串 `partition` 返回一个新的 `Session` 实例. +从字符串 `partition` 返回一个新的 `Session` 实例。 -如果 `partition` 以 `persist:` 开头,那么这个page将使用一个持久的 session,这个 session 将对应用的所有 page 可用.如果没前缀,这个 page 将使用一个历史 session.如果 `partition` 为空,那么将返回应用的默认 session . +返回 `Session` - 一个来自 `partition` 字符串的会话实例。当存在时 +`Session` 与同一个 `partition`,它会被返回;否则一个新 +`Session` 实例将使用 `options` 创建。 + +如果 `partition` 以 `persist:` 开头,那么这个 page 将使用一个持久的 session,这个 session 将对应用的所有 page 可用。如果没前缀,这个 page 将使用一个历史 session。如果 `partition` 为空,那么将返回应用的默认 session。 + +要用 `options` 创建一个 `Session`,你必须确保 `Session` 与 +`partition` 从来没有被使用过。没有办法改变现有 `Session` 对象的 `options'。 ## 属性 -`session` 模块有如下属性: +`session` 模块有如下属性: ### session.defaultSession -返回应用的默认 session 对象. +返回应用的默认 `Session` 对象。 ## Class: Session -可以在 `session` 模块中创建一个 `Session` 对象 : +> 获取和设置会话的属性。 + +进程: [Main](../glossary.md#main-process) + +可以在 `session` 模块中创建一个 `Session` 对象: ```javascript -const session = require('electron').session - -var ses = session.fromPartition('persist:name') +const {session} = require('electron') +const ses = session.fromPartition('persist:name') +console.log(ses.getUserAgent()) ``` ### 实例事件 @@ -55,12 +73,13 @@ var ses = session.fromPartition('persist:name') 当 Electron 将要从 `webContents` 下载 `item` 时触发. -调用 `event.preventDefault()` 可以取消下载,并且在进程的下个 tick中,这个 `item` 也不可用. +调用 `event.preventDefault()` 可以取消下载,并且在进程的下个 tick 中,这个 `item` 也不可用。 ```javascript -session.defaultSession.on('will-download', function (event, item, webContents) { +const {session} = require('electron') +session.defaultSession.on('will-download', (event, item, webContents) => { event.preventDefault() - require('request')(item.getURL(), function (data) { + require('request')(item.getURL(), (data) => { require('fs').writeFileSync('/somewhere', data) }) }) @@ -68,7 +87,7 @@ session.defaultSession.on('will-download', function (event, item, webContents) { ### 实例方法 -实例 `Session` 有以下方法: +实例 `Session` 有以下方法: #### `ses.cookies` diff --git a/docs-translations/zh-CN/api/shell.md b/docs-translations/zh-CN/api/shell.md index 1f36046b1e..22fa0d1cd1 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 0000000000..2fa0c799a1 --- /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 0000000000..f4184e8faf --- /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 0000000000..546cefb42d --- /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 0000000000..79f21e1080 --- /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 0000000000..f16b5acdb6 --- /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 0000000000..1ab3db1741 --- /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 0000000000..f5f5b9866b --- /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 0000000000..014350a60f --- /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 0000000000..07627e78c9 --- /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 0000000000..f17d72e0a4 --- /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 0000000000..d77e07dedf --- /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 0000000000..08e5cd47a4 --- /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 0000000000..69b87cbdf9 --- /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 0000000000..0cd000699e --- /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 0000000000..7ec853f163 --- /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 0000000000..28a9ed8ae1 --- /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 0000000000..0dd3c4ff0f --- /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 0000000000..ae01a07f32 --- /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 0000000000..e7b272d099 --- /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 0000000000..1d9c8b1f5a --- /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 0000000000..61a28de879 --- /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 0000000000..259195852a --- /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 0000000000..be93cacb49 --- /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 0000000000..8e5c07725a --- /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 0000000000..d62b0a8ba7 --- /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 0000000000..8a21973014 --- /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 0000000000..4fe162311f --- /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/system-preferences.md b/docs-translations/zh-CN/api/system-preferences.md index 7182e33e76..7ac9b5a7c0 100644 --- a/docs-translations/zh-CN/api/system-preferences.md +++ b/docs-translations/zh-CN/api/system-preferences.md @@ -1,6 +1,6 @@ # systemPreferences -> 获取系统偏好设置. +> 获取系统偏好设置。 可使用的进程: [主进程](../tutorial/quick-start.md#main-process) @@ -15,20 +15,20 @@ console.log(systemPreferences.isDarkMode()) ### Event: 'accent-color-changed' _Windows_ -返回: +返回: * `event` Event * `newColor` String - 用户给系统颜色设置的新的 RGBA 色值。 ### Event: 'color-changed' _Windows_ -返回: +返回: * `event` Event ### Event: 'inverted-color-scheme-changed' _Windows_ -返回: +返回: * `event` Event * `invertedColorScheme` Boolean - 如果一个反色的配色方案正在被使用,比如一个高对比度的主题,则返回 `true` ,否则返回 `false` 。 @@ -37,7 +37,7 @@ console.log(systemPreferences.isDarkMode()) ### `systemPreferences.isDarkMode()` _macOS_ -返回 `Boolean` - 系统是否处于深色模式. +返回 `Boolean` - 系统是否处于深色模式。 ### `systemPreferences.isSwipeTrackingFromScrollEventsEnabled()` _macOS_ diff --git a/docs-translations/zh-CN/api/tray.md b/docs-translations/zh-CN/api/tray.md index 80b8dc12f7..8ecbcac7b5 100644 --- a/docs-translations/zh-CN/api/tray.md +++ b/docs-translations/zh-CN/api/tray.md @@ -1,205 +1,231 @@ -# Tray +# Class:Tray -> 通过 `Tray` 向系统的通知区添加一个带有右键菜单的图标. +> 将图标和上下文菜单添加到系统的通知区域。 + +可使用的进程: [主进程](../tutorial/quick-start.md#main-process) + +`Tray` 是一个 [事件发出者][event-emitter]。 ```javascript -const electron = require('electron') -const app = electron.app -const Menu = electron.Menu -const Tray = electron.Tray +const {app, Menu, Tray} = require('electron') -var appIcon = null -app.on('ready', function () { - appIcon = new Tray('/path/to/my/icon') - var contextMenu = Menu.buildFromTemplate([ - { label: 'Item1', type: 'radio' }, - { label: 'Item2', type: 'radio' }, - { label: 'Item3', type: 'radio', checked: true }, - { label: 'Item4', type: 'radio' } +let tray = null +app.on('ready', () => { + tray = new Tray('/path/to/my/icon') + const contextMenu = Menu.buildFromTemplate([ + {label: 'Item1', type: 'radio'}, + {label: 'Item2', type: 'radio'}, + {label: 'Item3', type: 'radio', checked: true}, + {label: 'Item4', type: 'radio'} ]) - appIcon.setToolTip('This is my application.') - appIcon.setContextMenu(contextMenu) + tray.setToolTip('This is my application.') + tray.setContextMenu(contextMenu) }) - ``` __平台限制:__ -* 在 Linux, 如果支持应用指示器则使用它,否则使用 `GtkStatusIcon` 代替. -* 在 Linux ,配置了只有有了应用指示器的支持, 你必须安装 `libappindicator1` 来让 tray icon 执行. -* 应用指示器只有在它拥有 context menu 时才会显示. -* 当在linux 上使用了应用指示器,将忽略点击事件. -* 在 Linux,为了让单独的 `MenuItem` 起效,需要再次调用 `setContextMenu` .例如: +* 在 Linux, 如果支持应用指示器则使用它,否则使用 `GtkStatusIcon` 代替。 +* 在 Linux ,配置了只有有了应用指示器的支持, 你必须安装 `libappindicator1` 来让 tray icon 执行。 +* 应用指示器只有在它拥有 context menu 时才会显示。 +* 当在linux 上使用了应用指示器,将忽略点击事件。 +* 在 Linux,为了让单独的 `MenuItem` 起效,需要再次调用 `setContextMenu` 。例如: ```javascript -contextMenu.items[2].checked = false -appIcon.setContextMenu(contextMenu) +const {app, Menu, Tray} = require('electron') + +let appIcon = null +app.on('ready', () => { + appIcon = new Tray('/path/to/my/icon') + const contextMenu = Menu.buildFromTemplate([ + {label: 'Item1', type: 'radio'}, + {label: 'Item2', type: 'radio'} + ]) + + // Make a change to the context menu + contextMenu.items[1].checked = false + + // Call this again for Linux because we modified the context menu + appIcon.setContextMenu(contextMenu) +}) ``` -如果想在所有平台保持完全相同的行为,不应该依赖点击事件,而是一直将一个 context menu 添加到 tray icon. -## Class: Tray - -`Tray` 是一个 [事件发出者][event-emitter]. +如果想在所有平台保持完全相同的行为,不应该依赖点击事件,而是一直将一个 context menu 添加到 tray icon。 ### `new Tray(image)` -* `image` [NativeImage](native-image.md) +* `image` ([NativeImage](native-image.md) | String) -创建一个与 `image` 相关的 icon. +创建一个与 `image` 相关的 icon。 -## 事件 +### Instance Events -`Tray` 模块可发出下列事件: +`Tray` 模块可发出下列事件: -**注意:** 一些事件只能在特定的os中运行,已经标明. - -### Event: 'click' +#### Event: 'click' * `event` Event * `altKey` Boolean * `shiftKey` Boolean * `ctrlKey` Boolean * `metaKey` Boolean -* `bounds` Object - tray icon 的 bounds. - * `x` Integer - * `y` Integer - * `width` Integer - * `height` Integer +* `bounds` [Rectangle](structures/rectangle.md) - tray icon 的 bounds -当tray icon被点击的时候发出事件. +当 tray icon 被点击的时候发出事件。 -__注意:__ `bounds` 只在 macOS 和 Windows 上起效. - -### Event: 'right-click' _macOS_ _Windows_ +#### Event: 'right-click' _macOS_ _Windows_ * `event` Event * `altKey` Boolean * `shiftKey` Boolean * `ctrlKey` Boolean * `metaKey` Boolean -* `bounds` Object - tray icon 的 bounds. - * `x` Integer - * `y` Integer - * `width` Integer - * `height` Integer +* `bounds` [Rectangle](structures/rectangle.md) - tray icon 的 bounds -当tray icon被鼠标右键点击的时候发出事件. +当 tray icon 被鼠标右键点击的时候发出事件。 -### Event: 'double-click' _macOS_ _Windows_ +#### Event: 'double-click' _macOS_ _Windows_ * `event` Event * `altKey` Boolean * `shiftKey` Boolean * `ctrlKey` Boolean * `metaKey` Boolean -* `bounds` Object - tray icon 的 bounds. - * `x` Integer - * `y` Integer - * `width` Integer - * `height` Integer +* `bounds` [Rectangle](structures/rectangle.md) - tray icon 的 bounds -当tray icon被双击的时候发出事件. +当 tray icon 被双击的时候发出事件。 -### Event: 'balloon-show' _Windows_ +#### Event: 'balloon-show' _Windows_ -当tray 气泡显示的时候发出事件. +当 tray 气泡显示的时候发出事件。 -### Event: 'balloon-click' _Windows_ +#### Event: 'balloon-click' _Windows_ -当tray 气泡被点击的时候发出事件. +当 tray 气泡被点击的时候发出事件。 -### Event: 'balloon-closed' _Windows_ +#### Event: 'balloon-closed' _Windows_ -当tray 气泡关闭的时候发出事件,因为超时或人为关闭. +当 tray 气泡关闭的时候发出事件,因为超时或人为关闭。 -### Event: 'drop' _macOS_ +#### Event: 'drop' _macOS_ -当tray icon上的任何可拖动项被删除的时候发出事件. +当 tray icon 上的任何可拖动项被删除的时候发出事件。 -### Event: 'drop-files' _macOS_ +#### Event: 'drop-files' _macOS_ * `event` * `files` Array - 已删除文件的路径. -当tray icon上的可拖动文件被删除的时候发出事件. +当 tray icon 上的可拖动文件被删除的时候发出事件。 -### Event: 'drag-enter' _macOS_ +#### Event: 'drag-enter' _macOS_ -当一个拖动操作进入tray icon的时候发出事件. +当一个拖动操作进入 tray icon 的时候发出事件。 -### Event: 'drag-leave' _macOS_ +#### Event: 'drag-leave' _macOS_ -当一个拖动操作离开tray icon的时候发出事件. -Emitted when a drag operation exits the tray icon. +当一个拖动操作离开 tray icon 的时候发出事件。 -### Event: 'drag-end' _macOS_ +#### Event: 'drag-end' _macOS_ -当一个拖动操作在tray icon上或其它地方停止拖动的时候发出事件. +当一个拖动操作在 tray icon 上或其它地方停止拖动的时候发出事件。 -## 方法 +### 方法 -`Tray` 模块有以下方法: +`Tray` 模块有以下方法: -**Note:** 一些方法只能在特定的os中运行,已经标明. +#### `tray.destroy()` -### `Tray.destroy()` +立刻删除 tray icon。 -立刻删除 tray icon. +#### `tray.setImage(image)` -### `Tray.setImage(image)` +* `image` ([NativeImage](native-image.md) | String) + +让 `image` 与 tray icon 关联起来。 + +#### `tray.setPressedImage(image)` _macOS_ * `image` [NativeImage](native-image.md) -让 `image` 与 tray icon 关联起来. +当在 macOS 上按压 tray icon 的时候,让 `image` 与 tray icon 关联起来。 -### `Tray.setPressedImage(image)` _macOS_ - -* `image` [NativeImage](native-image.md) - -当在 macOS 上按压 tray icon 的时候, 让 `image` 与 tray icon 关联起来. - -### `Tray.setToolTip(toolTip)` +#### `tray.setToolTip(toolTip)` * `toolTip` String -为 tray icon 设置 hover text. +为 tray icon 设置 hover text。 -### `Tray.setTitle(title)` _macOS_ +#### `tray.setTitle(title)` _macOS_ * `title` String -在状态栏沿着 tray icon 设置标题. +在状态栏沿着 tray icon 设置标题。 -### `Tray.setHighlightMode(highlight)` _macOS_ +#### `tray.setHighlightMode(mode)` _macOS_ -* `highlight` Boolean +* `mode` String - Highlight mode with one of the following values: + * `selection` - Highlight the tray icon when it is clicked and also when + its context menu is open. This is the default. + * `always` - Always highlight the tray icon. + * `never` - Never highlight the tray icon. -当 tray icon 被点击的时候,是否设置它的背景色变为高亮(blue).默认为 true. +设置 tray icon 的背景色变为高亮(blue)。 -### `Tray.displayBalloon(options)` _Windows_ +**注意:** 你可以使用 `highlightMode` 和一个 [`BrowserWindow`](browser-window.md) +通过在窗口可见性时切换 `'never'` 和 `'always'` 模式变化。 + +```javascript +const {BrowserWindow, Tray} = require('electron') + +const win = new BrowserWindow({width: 800, height: 600}) +const tray = new Tray('/path/to/my/icon') + +tray.on('click', () => { + win.isVisible() ? win.hide() : win.show() +}) +win.on('show', () => { + tray.setHighlightMode('always') +}) +win.on('hide', () => { + tray.setHighlightMode('never') +}) +``` + +#### `tray.displayBalloon(options)` _Windows_ * `options` Object - * `icon` [NativeImage](native-image.md) - * `title` String - * `content` String + * `icon` ([NativeImage](native-image.md) | String) - (可选) + * `title` String - (可选) + * `content` String - (可选) -展示一个 tray balloon. +展示一个 tray balloon。 -### `Tray.popUpContextMenu([menu, position])` _macOS_ _Windows_ +#### `tray.popUpContextMenu([menu, position])` _macOS_ _Windows_ * `menu` Menu (optional) * `position` Object (可选) - 上托位置. * `x` Integer * `y` Integer -从 tray icon 上托出 context menu . 当划过 `menu` 的时候, `menu` 显示,代替 tray 的 context menu . +从 tray icon 上托出 context menu。当划过 `menu` 的时候, `menu` 显示,代替 tray 的 context menu。 -`position` 只在 windows 上可用,默认为 (0, 0) . +`position` 只在 windows 上可用,默认为 (0, 0)。 -### `Tray.setContextMenu(menu)` +#### `tray.setContextMenu(menu)` * `menu` Menu -为这个 icon 设置 context menu . +为这个 icon 设置 context menu。 -[event-emitter]: http://nodejs.org/api/events.html#events_class_events_eventemitter +#### `tray.getBounds()` _macOS_ _Windows_ + +返回 [`Rectangle`](structures/rectangle.md) + +这个 tray icon 的 `bounds` 对象。 + +#### `tray.isDestroyed()` + +返回 `Boolean` - tray icon 是否销毁。 + +[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter diff --git a/docs-translations/zh-CN/api/web-contents.md b/docs-translations/zh-CN/api/web-contents.md index 838fefc5cb..dd5c926126 100644 --- a/docs-translations/zh-CN/api/web-contents.md +++ b/docs-translations/zh-CN/api/web-contents.md @@ -1,30 +1,63 @@ # webContents +> 渲染和控制网页。 + +可使用的进程: [主进程](../tutorial/quick-start.md#main-process) + `webContents` 是一个 [事件发出者](http://nodejs.org/api/events.html#events_class_events_eventemitter). - 它负责渲染并控制网页,也是 [`BrowserWindow`](browser-window.md) 对象的属性.一个使用 `webContents` 的例子: ```javascript -const BrowserWindow = require('electron').BrowserWindow +const {BrowserWindow} = require('electron') -var win = new BrowserWindow({width: 800, height: 1500}) +let win = new BrowserWindow({width: 800, height: 1500}) win.loadURL('http://github.com') -var webContents = win.webContents +let contents = win.webContents +console.log(contents) ``` -## 事件 +## 方法 -`webContents` 对象可发出下列事件: +这些方法可以从 `webContents` 模块访问: -### Event: 'did-finish-load' +```javascript +const {webContents} = require('electron') +console.log(webContents) +``` -当导航完成时发出事件,`onload` 事件也完成. +### `webContents.getAllWebContents()` -### Event: 'did-fail-load' +返回 `WebContents[]` - 所有 `WebContents` 实例的数组。这将包含Web内容 +适用于所有 windows,webviews,打开的 devtools 和 devtools 扩展背景页面。 -返回: +### `webContents.getFocusedWebContents()` + +返回 `WebContents` - 在此应用程序中焦点的 Web 内容,否则返回`null`。 + +### `webContents.fromId(id)` + +* `id` Integer + +返回 `WebContents` - 一个给定 ID 的 WebContents 实例。 + +## Class: WebContents + +> 渲染和控制浏览器窗口实例的内容。 + +可使用的进程: [主进程](../tutorial/quick-start.md#main-process) + +### 实例事件 + + +#### Event: 'did-finish-load' + +当导航完成时,发出 `onload` 事件。 + +#### Event: 'did-fail-load' + +返回: * `event` Event * `errorCode` Integer @@ -32,28 +65,28 @@ var webContents = win.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' -返回: +返回: * `event` Event * `isMainFrame` Boolean -当一个 frame 导航完成的时候发出事件. +当一个 frame 导航完成的时候发出事件。 -### Event: 'did-start-loading' +#### Event: 'did-start-loading' -当 tab 的spinner 开始 spinning的时候. +当 tab 的spinner 开始 spinning的时候。 -### Event: 'did-stop-loading' +#### Event: 'did-stop-loading' -当 tab 的spinner 结束 spinning的时候. +当 tab 的spinner 结束 spinning的时候。 -### Event: 'did-get-response-details' +#### Event: 'did-get-response-details' -返回: +返回: * `event` Event * `status` Boolean @@ -65,12 +98,12 @@ var webContents = win.webContents * `headers` Object * `resourceType` String -当有关请求资源的详细信息可用的时候发出事件. -`status` 标识了 socket链接来下载资源. +当有关请求资源的详细信息可用的时候发出事件。 +`status` 标识了 socket 链接来下载资源。 -### Event: 'did-get-redirect-request' +#### Event: 'did-get-redirect-request' -返回: +返回: * `event` Event * `oldURL` String @@ -81,143 +114,182 @@ var webContents = win.webContents * `referrer` String * `headers` Object -当在请求资源时收到重定向的时候发出事件. +当在请求资源时收到重定向的时候发出事件。 -### Event: 'dom-ready' +#### Event: 'dom-ready' -返回: +返回: * `event` Event -当指定 frame 中的 文档加载完成的时候发出事件. +当指定 frame 中的 文档加载完成的时候发出事件。 -### Event: 'page-favicon-updated' +#### Event: 'page-favicon-updated' -返回: +返回: * `event` Event * `favicons` Array - Array of URLs -当 page 收到图标 url 的时候发出事件. +当 page 收到图标 url 的时候发出事件。 -### Event: 'new-window' +#### Event: 'new-window' -返回: +返回: * `event` Event * `url` String * `frameName` String * `disposition` String - 可为 `default`, `foreground-tab`, `background-tab`, - `new-window` 和 `other`. -* `options` Object - 创建新的 `BrowserWindow`时使用的参数. + `new-window`, `save-to-disk` 和 `other` +* `options` Object - 创建新的 `BrowserWindow`时使用的参数。 +* `additionalFeatures` String[] - 非标准功能(功能未处理 +   由 Chromium 或 Electron )赋予 `window.open()`。 -当 page 请求打开指定 url 窗口的时候发出事件.这可以是通过 `window.open` 或一个外部连接如 `` 发出的请求. +当 page 请求打开指定 url 窗口的时候发出事件.这可以是通过 `window.open` 或一个外部连接如 `` 发出的请求。 -默认指定 `url` 的 `BrowserWindow` 会被创建. +默认指定 `url` 的 `BrowserWindow` 会被创建。 -调用 `event.preventDefault()` 可以用来阻止打开窗口. +调用 `event.preventDefault()` 可以用来阻止打开窗口。 -### Event: 'will-navigate' +调用 `event.preventDefault()` 将阻止 Electron 自动创建 +新 `BrowserWindow`。 如果调用 `event.preventDefault()` 并手动创建一个新的 +`BrowserWindow`,那么你必须设置 `event.newGuest` 来引用新的 `BrowserWindow` +实例,如果不这样做可能会导致意外的行为。例如: -返回: +```javascript +myBrowserWindow.webContents.on('new-window', (event, url) => { + event.preventDefault() + const win = new BrowserWindow({show: false}) + win.once('ready-to-show', () => win.show()) + win.loadURL(url) + event.newGuest = win +}) +``` + +#### Event: 'will-navigate' + +返回: * `event` Event * `url` String -当用户或 page 想要开始导航的时候发出事件.它可在当 `window.location` 对象改变或用户点击 page 中的链接的时候发生. +当用户或 page 想要开始导航的时候发出事件。它可在当 `window.location` 对象改变或用户点击 page 中的链接的时候发生。 -当使用 api(如 `webContents.loadURL` 和 `webContents.back`) 以编程方式来启动导航的时候,这个事件将不会发出. +当使用 api(如 `webContents.loadURL` 和 `webContents.back`) 以编程方式来启动导航的时候,这个事件将不会发出。 -它也不会在页内跳转发生, 例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 事件可以达到目的. +它也不会在页内跳转发生,例如点击锚链接或更新 `window.location.hash`。使用 `did-navigate-in-page` 事件可以达到目的。 -调用 `event.preventDefault()` 可以阻止导航. +调用 `event.preventDefault()` 可以阻止导航。 -### Event: 'did-navigate' +#### Event: 'did-navigate' -返回: +返回: * `event` Event * `url` String -当一个导航结束时候发出事件. +当一个导航结束时候发出事件。 -页内跳转时不会发出这个事件,例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 事件可以达到目的. +页内跳转时不会发出这个事件,例如点击锚链接或更新 `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` 事件发生. +当页内导航发生的时候,page 的url 改变,但是不会跳出界面。例如当点击锚链接时或者 DOM 的 `hashchange` 事件发生。 -### Event: 'crashed' +#### Event: 'crashed' -当渲染进程崩溃的时候发出事件. +返回: -### Event: 'plugin-crashed' +* `event` Event +* `killed` Boolean -返回: +当渲染进程崩溃的时候发出事件。 + +#### Event: 'plugin-crashed' + +返回: * `event` Event * `name` String * `version` String -当插件进程崩溃时候发出事件. +当插件进程崩溃时候发出事件。 -### Event: 'destroyed' +#### Event: 'destroyed' -当 `webContents` 被删除的时候发出事件. +当 `webContents` 被删除的时候发出事件。 -### Event: 'devtools-opened' +#### Event: 'before-input-event' -当开发者工具栏打开的时候发出事件. +返回: -### Event: 'devtools-closed' +* `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-focused' +#### Event: 'devtools-opened' -当开发者工具栏获得焦点或打开的时候发出事件. +当开发者工具栏打开的时候发出事件。 -### Event: 'certificate-error' +#### Event: 'devtools-closed' -返回: +当开发者工具栏关闭时候发出事件。 + +#### Event: 'devtools-focused' + +当开发者工具栏获得焦点或打开的时候发出事件。 + +#### 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). +使用方法类似 [`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). +使用方法类似 [`app` 的 `select-client-certificate` 事件](app.md#event-select-client-certificate)。 -### Event: 'login' +#### Event: 'login' -返回: +返回: * `event` Event * `request` Object @@ -231,49 +303,67 @@ var webContents = win.webContents * `port` Integer * `realm` String * `callback` Function + * `username` String + * `password` String 当 `webContents` 想做基本验证的时候发出事件. -使用方法类似 [the `login` event of `app`](app.md#event-login). +使用方法类似 [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' +当使用 [`webContents.findInPage`] 进行页内查找并且找到可用值得时候发出事件。 -当媒体开始播放的时候发出事件. +#### Event: 'media-started-playing' -### Event: 'media-paused' +当媒体开始播放的时候发出事件。 -当媒体停止播放的时候发出事件. +#### Event: 'media-paused' -### Event: 'did-change-theme-color' +当媒体停止播放的时候发出事件。 -当page 的主题色时候发出事件.这通常由于引入了一个 meta 标签 : +#### Event: 'did-change-theme-color' + +当page 的主题色时候发出事件。这通常由于引入了一个 meta 标签: ```html ``` -### 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`, @@ -284,276 +374,498 @@ var webContents = win.webContents `cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, `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` 必须包含协议前缀, -比如 `http://` 或 `file://`. 如果加载想要忽略 http 缓存,可以使用 `pragma` 头来达到目的. +在窗口中加载 `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` 事件会触发. +初始化一个指定 `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` - 当前页是否正在加载资源。 -### `webContents.reload()` +#### `contents.isLoadingMainFrame()` -重载当前页. +Returns `Boolean` - 是否主框架(而不仅是 iframe 或者在帧内)仍在加载中。 -### `webContents.reloadIgnoringCache()` +#### `contents.isWaitingForResponse()` -重载当前页,忽略缓存. +Returns `Boolean` - 当前页是否正在等待主要资源的第一次响应。 -### `webContents.canGoBack()` +#### `contents.stop()` -返回一个布尔值,标识浏览器是否能回到前一个page. +停止还为开始的导航。 -### `webContents.canGoForward()` +#### `contents.reload()` -返回一个布尔值,标识浏览器是否能前往下一个page. +重载当前页。 -### `webContents.canGoToOffset(offset)` +#### `contents.reloadIgnoringCache()` + +重载当前页,忽略缓存。 + +#### `contents.canGoBack()` + +Returns `Boolean` - 浏览器是否能回到前一个页面。 + +#### `contents.canGoForward()` + +Returns `Boolean` - 浏览器是否能前往下一个页面。 + +#### `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. +为当前页插入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 -浏览器窗口中的一些 HTML API ,例如 `requestFullScreen`,只能被用户手势请求.设置 `userGesture` 为 `true` 可以取消这个限制. +评估页面的 `code` 代码。 -### `webContents.setAudioMuted(muted)` +浏览器窗口中的一些 HTML API ,例如 `requestFullScreen`,只能被用户手势请求。设置 `userGesture` 为 `true` 可以取消这个限制。 + +如果执行的代码的结果是一个 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` 到获得了焦点的元素. +插入 `text` 到获得了焦点的元素。 -### `webContents.findInPage(text[, options])` +#### `contents.findInPage(text[, options])` -* `text` String - 查找内容, 不能为空. +* `text` String - 查找内容,不能为空。 * `options` Object (可选) - * `forward` Boolean - 是否向前或向后查找, 默认为 `true`. - * `findNext` Boolean - 当前操作是否是第一次查找或下一次查找, - 默认为 `false`. - * `matchCase` Boolean - 查找是否区分大小写, - 默认为 `false`. - * `wordStart` Boolean -是否仅以首字母查找. - 默认为 `false`. - * `medialCapitalAsWordStart` Boolean - 是否结合 `wordStart`,如果匹配是大写字母开头,后面接小写字母或无字母,那么就接受这个词中匹配.接受几个其它的合成词匹配, 默认为 `false`. + * `forward` Boolean - (可选) 是否向前或向后查找,默认为 `true`。 + * `findNext` Boolean - (可选) 当前操作是否是第一次查找或下一次查找, + 默认为 `false`。 + * `matchCase` Boolean - (可选) 查找是否区分大小写, + 默认为 `false`。 + * `wordStart` Boolean - (可选) 是否仅以首字母查找, + 默认为 `false`。 + * `medialCapitalAsWordStart` Boolean - (可选) 是否结合 `wordStart`,如果匹配是大写字母开头,后面接小写字母或无字母,那么就接受这个词中匹配。接受几个其它的合成词匹配,默认为 `false`。 -发起请求,在网页中查找所有与 `text` 相匹配的项,并且返回一个 `Integer` 来表示这个请求用的请求Id.这个请求结果可以通过订阅 - [`found-in-page`](web-contents.md#event-found-in-page) 事件来取得. +发起请求,在网页中查找所有与 `text` 相匹配的项,并且返回一个 `Integer` 来表示这个请求用的请求 Id。这个请求结果可以通过订阅 + [`found-in-page`](web-contents.md#event-found-in-page) 事件来取得。 -### `webContents.stopFindInPage(action)` +#### `contents.stopFindInPage(action)` * `action` String - 指定一个行为来接替停止 - [`webContents.findInPage`] 请求. - * `clearSelection` - 转变为一个普通的 selection. - * `keepSelection` - 清除 selection. - * `activateSelection` - 获取焦点并点击 selection node. + [`webContents.findInPage`] 请求。 + * `clearSelection` - 转变为一个普通的 selection。 + * `keepSelection` - 清除 selection。 + * `activateSelection` - 获取焦点并点击 selection node。 -使用给定的 `action` 来为 `webContents` 停止任何 `findInPage` 请求. +使用给定的 `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`响应的标识。 -检查是否有任何 ServiceWorker 注册了,并且返回一个布尔值,来作为 `callback`响应的标识. - -### `webContents.unregisterServiceWorker(callback)` +#### `contents.unregisterServiceWorker(callback)` * `callback` Function + * `success` Boolean + +如果存在任何 ServiceWorker,则全部注销,并且当JS承诺执行行或JS拒绝执行而失败的时候,返回一个布尔值,它标识了相应的 `callback`。 -如果存在任何 ServiceWorker ,则全部注销,并且当JS承诺执行行或JS拒绝执行而失败的时候,返回一个布尔值,它标识了相应的 `callback`. - -### `webContents.print([options])` +#### `contents.print([options])` * `options` Object (可选) - * `silent` Boolean - 不需要请求用户的打印设置. 默认为 `false`. - * `printBackground` Boolean - 打印背景和网页图片. 默认为 `false`. + * `silent` Boolean - 不需要请求用户的打印设置. 默认为 `false`。 + * `printBackground` Boolean - 打印背景和网页图片. 默认为 `false`。 -打印窗口的网页. 当设置 `silent` 为 `false` 的时候,Electron 将使用系统默认的打印机和打印方式来打印. +打印窗口的网页。当设置 `silent` 为 `true` 的时候,Electron 将使用系统默认的打印机和打印方式来打印。 -在网页中调用 `window.print()` 和 调用 `webContents.print({silent: false, printBackground: false})`具有相同的作用. +在网页中调用 `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`. - * `printBackground` Boolean - 是否打印 css 背景. - * `printSelectionOnly` Boolean - 是否只打印选中的部分. - * `landscape` Boolean - landscape 为 `true`, portrait 为 `false`. + * `marginsType` Integer - 指定使用的 margin type。默认 margin 使用 0,无 margin 使用 1,最小化 margin 使用 2。 + * `pageSize` String - 指定生成的PDF文件的page size. 可以是 `A3`、 + `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 预览打印的自定义设置. +打印窗口的网页为 pdf,使用 Chromium 预览打印的自定义设置。 -完成时使用 `callback(error, data)` 调用 `callback` . `data` 是一个 `Buffer` ,包含了生成的 pdf 数据. +完成时使用 `callback(error, data)` 调用 `callback` 。 `data` 是一个 `Buffer` ,包含了生成的 pdf 数据、 -默认,空的 `options` 被视为 : +默认,空的 `options` 被视为: ```javascript { @@ -564,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.') }) @@ -583,77 +899,81 @@ win.webContents.on('did-finish-load', function () { }) ``` -### `webContents.addWorkSpace(path)` +#### `contents.addWorkSpace(path)` * `path` String -添加指定的路径给开发者工具栏的 workspace.必须在 DevTools 创建之后使用它 : +添加指定的路径给开发者工具栏的 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 删除指定的路径. +从开发者工具栏的 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 开发者工具. +Toggles 开发者工具。 -### `webContents.inspectElement(x, y)` +#### `contents.inspectElement(x, y)` * `x` Integer * `y` Integer -在 (`x`, `y`) 开始检测元素. +在 (`x`, `y`) 开始检测元素。 -### `webContents.inspectServiceWorker()` +#### `contents.inspectServiceWorker()` -为 service worker 上下文打开开发者工具栏. +为 service worker 上下文打开开发者工具栏。 -### `webContents.send(channel[, arg1][, arg2][, ...])` +#### `contents.send(channel[, arg1][, arg2][, ...])` * `channel` String * `arg` (可选) -通过 `channel` 发送异步消息给渲染进程,你也可发送任意的参数.参数应该在 JSON 内部序列化,并且此后没有函数或原形链被包括了. +通过 `channel` 发送异步消息给渲染进程,你也可发送任意的参数。参数应该在 JSON 内部序列化,并且此后没有函数或原形链被包括了。 -渲染进程可以通过使用 `ipcRenderer` 监听 `channel` 来处理消息. +渲染进程可以通过使用 `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!') }) }) ``` @@ -663,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`) @@ -696,34 +1016,32 @@ app.on('ready', function () { * `y` Float - 设置相对左上角的y轴偏移值 * `scale` Float - 可用空间内的模拟视图偏移 (不在适应视图模式) (默认: `1`) -使用给定的参数来开启设备模拟. +使用给定的参数来开启设备模拟。 -### `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` 对象还有如下属性 : +对键盘事件来说,`event` 对象还有如下属性: -* `keyCode` String (**必需**) - 特点是将作为键盘事件发送. 可用的 key codes [Accelerator](accelerator.md). +* `keyCode` String (**必需**) - 将字符串作为键盘事件发送。可用的 key codes [Accelerator](accelerator.md)。 +对鼠标事件来说,`event` 对象还有如下属性: -对鼠标事件来说,`event` 对象还有如下属性 : - -* `x` Integer (**required**) -* `y` Integer (**required**) +* `x` Integer (**必需**) +* `y` Integer (**必需**) * `button` String - button 按下, 可以是 `left`, `middle`, `right` * `globalX` Integer * `globalY` Integer @@ -731,7 +1049,7 @@ app.on('ready', function () { * `movementY` Integer * `clickCount` Integer -对鼠标滚轮事件来说,`event` 对象还有如下属性 : +对鼠标滚轮事件来说,`event` 对象还有如下属性: * `deltaX` Integer * `deltaY` Integer @@ -742,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`。 -开始订阅 提交 事件和捕获数据帧,当有 提交 事件时,使用 `callback(frameBuffer)` 调用 `callback`. +`frameBuffer` 是一个包含原始像素数据的 `Buffer`,像素数据是按照 32bit BGRA 格式有效存储的,但是实际情况是取决于处理器的字节顺序的(大多数的处理器是存放小端序的,如果是在大端序的处理器上,数据是 32bit ARGB 格式)。 -`frameBuffer` 是一个包含原始像素数据的 `Buffer`,像素数据是按照 32bit BGRA 格式有效存储的,但是实际情况是取决于处理器的字节顺序的(大多数的处理器是存放小端序的,如果是在大端序的处理器上,数据是 32bit ARGB 格式). +`dirtyRect` 脏区域是一个包含 `x, y, width, height` 属性的对象,它们描述了一个页面中的重绘区域。如果 `onlyDirty` 被设置为`true`, `frameBuffer` 将只包含重绘区域。`onlyDirty` 的默认值为 `false`. -### `webContents.endFrameSubscription()` +#### `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_ -`WebContents` 对象也有下列属性: +在页面上显示搜索的弹窗。 -### `webContents.session` +#### `contents.setSize(options)` -返回这个 `webContents` 使用的 [session](session.md) 对象. +设置页面的大小。This is only supported for `` guest contents. -### `webContents.hostWebContents` +* `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 -返回这个 `webContents` 的父 `webContents` . +#### `contents.isOffscreen()` -### `webContents.devToolsWebContents` +Returns `Boolean` - 表明是否设置了 *offscreen rendering*. -获取这个 `WebContents` 的开发者工具栏的 `WebContents` . +#### `contents.startPainting()` -**注意:** 用户不可保存这个对象,因为当开发者工具栏关闭的时候它的值为 `null` . +如果设置了 *offscreen rendering* 并且没有绘制,开始绘制. -### `webContents.debugger` +#### `contents.stopPainting()` -调试 API 为 [remote debugging protocol][rdp] 提供交替传送. +如果设置了 *offscreen rendering* 并且绘制了,停止绘制. -```javascript -try { - win.webContents.debugger.attach('1.1') -} catch (err) { - console.log('Debugger attach failed : ', err) -} +#### `contents.isPainting()` -win.webContents.debugger.on('detach', function (event, reason) { - console.log('Debugger detached due to : ', reason) -}) +Returns `Boolean` - 如果设置了 *offscreen rendering* ,返回当前是否正在绘制. -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() - } - } -}) +#### `contents.setFrameRate(fps)` -win.webContents.debugger.sendCommand('Network.enable') -``` +* `fps` Integer -#### `webContents.debugger.attach([protocolVersion])` +如果设置了 *offscreen rendering*,设置帧频为制定数值。有效范围为1-60. -* `protocolVersion` String (可选) - 请求调试协议版本. +#### `contents.getFrameRate()` -添加 `webContents` 调试. +Returns `Integer` - 如果设置了 *offscreen rendering*,返回当前的帧频 -#### `webContents.debugger.isAttached()` +#### `contents.invalidate()` -返回一个布尔值,标识是否已经给 `webContents` 添加了调试. +全部重新绘制整个页面的内容。 -#### `webContents.debugger.detach()` +如果设置了*offscreen rendering* ,让页面失效并且生成一个新的`'paint'`事件 -删除 `webContents` 调试. +#### `contents.getWebRTCIPHandlingPolicy()` -#### `webContents.debugger.sendCommand(method[, commandParams, callback])` +Returns `String` - Returns the WebRTC IP Handling Policy. -* `method` String - 方法名, 应该是由远程调试协议定义的方法. -* `commandParams` Object (可选) - 请求参数为 JSON 对象. -* `callback` Function (可选) - Response - * `error` Object - 错误消息,标识命令失败. - * `result` Object - 回复在远程调试协议中由 'returns'属性定义的命令描述. +#### `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. -### Event: 'detach' +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. -* `event` Event -* `reason` String - 拆分调试器原因. +### 实例属性 -在调试 session 结束时发出事件.这在 `webContents` 关闭时或 `webContents` 请求开发者工具栏时发生. +`WebContents` 对象也有下列属性: -### Event: 'message' +#### `contents.id` -* `event` Event -* `method` String - 方法名. -* `params` Object - 在远程调试协议中由 'parameters' 属性定义的事件参数. +表明 WebContents 唯一标示的整数 -每当调试目标发出事件时发出. +#### `contents.session` -[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol -[`webContents.findInPage`]: web-contents.md#webcontentsfindinpagetext-options +返回这个 `webContents` 使用的 [session](session.md) 对象。 + +#### `contents.hostWebContents` + +返回这个 `webContents` 的父 [`WebContents`](web-contents.md)。 + +#### `contents.devToolsWebContents` + +获取这个 `WebContents` 的开发者工具栏的 `WebContents`。 + +**注意:** 用户不可保存这个对象,因为当开发者工具栏关闭的时候它的值为 `null`。 + +#### `contents.debugger` + +webContents 的 [Debugger](debugger.md) 实例. + +[keyboardevent]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent diff --git a/docs-translations/zh-CN/api/webview-tag.md b/docs-translations/zh-CN/api/webview-tag.md index ca824fce4e..db5b809ab8 100644 --- a/docs-translations/zh-CN/api/webview-tag.md +++ b/docs-translations/zh-CN/api/webview-tag.md @@ -147,7 +147,7 @@ CSS通过 `flex` 布局设置 `width` 和 `height`,并允许元素缩小到0px ```html - + ``` 为 page 设置 session。如果初始值为 `partition` ,这个 page 将会为app中的所有 page 应用同一个持续有效的 session。如果没有 `persist:` 前缀, 这个 page 将会使用一个历史 session。通过分配使用相同的 `partition`, 所有的 page 都可以分享相同的session。如果 `partition` 没有设置,那 app 将使用默认的 session。 @@ -193,7 +193,7 @@ CSS通过 `flex` 布局设置 `width` 和 `height`,并允许元素缩小到0px 指定要禁用的 blink 特征的字符串列表,用 `,` 分隔。 支持的功能字符串的完整列表 -[RuntimeEnabledFeatures.in][blink-feature-string]。 +[RuntimeEnabledFeatures.json5][blink-feature-string]。 ### `guestinstance` @@ -835,4 +835,4 @@ ipcRenderer.on('ping', () => { 在开发者工具获取焦点的时候触发。 -[blink-feature-string]: https://code.google.com/p/chromium/codesearch#chromium/src/out/Debug/gen/blink/platform/RuntimeEnabledFeatures.cpp&sq=package:chromium&type=cs&l=527 +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 diff --git a/docs-translations/zh-CN/api/window-open.md b/docs-translations/zh-CN/api/window-open.md index 069e2c3522..3ef1b59f1a 100644 --- a/docs-translations/zh-CN/api/window-open.md +++ b/docs-translations/zh-CN/api/window-open.md @@ -1,11 +1,13 @@ # `window.open` 函数 -当在界面中使用 `window.open` 来创建一个新的窗口时候,将会创建一个 `BrowserWindow` 的实例,并且将返回一个标识,这个界面通过标识来对这个新的窗口进行有限的控制. +> 通过链接打开一个新窗口。 -这个标识对传统的web界面来说,通过它能对子窗口进行有限的功能性兼容控制. -想要完全的控制这个窗口,可以直接创建一个 `BrowserWindow` . +当在界面中使用 `window.open` 来创建一个新的窗口时候,将会创建一个 `BrowserWindow` 的实例,并且将返回一个标识,这个界面通过标识来对这个新的窗口进行有限的控制。 -新创建的 `BrowserWindow` 默认为继承父窗口的属性参数,想重写属性的话可以在 `features` 中设置他们. +这个标识对传统的web界面来说,通过它能对子窗口进行有限的功能性兼容控制。 +想要完全的控制这个窗口,可以直接创建一个 `BrowserWindow`。 + +新创建的 `BrowserWindow` 默认为继承父窗口的属性参数,想重写属性的话可以在 `features` 中设置他们。 ### `window.open(url[, frameName][, features])` @@ -13,48 +15,19 @@ * `frameName` String (可选) * `features` String (可选) -创建一个新的window并且返回一个 `BrowserWindowProxy` 类的实例. +创建一个新的 window 并且返回一个 [`BrowserWindowProxy`](browser-window-proxy.md) 类的实例。 - `features` 遵循标准浏览器的格式,但是每个feature 应该作为 `BrowserWindow` 参数的一个字段. + `features` 遵循标准浏览器的格式,但是每个 feature 应该作为 `BrowserWindow` 参数的一个字段。 + +**注意:** +* 如果在父窗口禁用 Node integration,那么在新打开的 `window` 中将始终禁用。 +* 非标准功能(不由 Chromium 或 Electron 处理)的 +   `features` 将被传递给任何注册的 `webContent` 的 `new-window` 事件 +   在 `additionalFeatures` 参数的处理程序。 ### `window.opener.postMessage(message, targetOrigin)` * `message` String * `targetOrigin` String -通过指定位置或用 `*` 来代替没有明确位置来向父窗口发送信息. - -## Class: BrowserWindowProxy - -`BrowserWindowProxy` 由`window.open` 创建返回,并且提供了对子窗口的有限功能性控制. - -### `BrowserWindowProxy.blur()` - -子窗口的失去焦点. -### `BrowserWindowProxy.close()` - -强行关闭子窗口,忽略卸载事件. - -### `BrowserWindowProxy.closed` - -在子窗口关闭之后恢复正常. - -### `BrowserWindowProxy.eval(code)` - -* `code` String - -评估子窗口的代码. - -### `BrowserWindowProxy.focus()` - -子窗口获得焦点(让其显示在最前). - -### `BrowserWindowProxy.postMessage(message, targetOrigin)` - -* `message` String -* `targetOrigin` String - - -通过指定位置或用 `*` 来代替没有明确位置来向子窗口发送信息. - -除了这些方法,子窗口还可以无特性和使用单一方法来实现 `window.opener` 对象. \ No newline at end of file +通过指定位置或用 `*` 来代替没有明确位置来向父窗口发送信息。 diff --git a/docs-translations/zh-CN/development/clang-format.md b/docs-translations/zh-CN/development/clang-format.md new file mode 100644 index 0000000000..46e4bf97ec --- /dev/null +++ b/docs-translations/zh-CN/development/clang-format.md @@ -0,0 +1,27 @@ +# 在 C++ 代码中使用 clang-format 工具 + +[`clang-format`](http://clang.llvm.org/docs/ClangFormat.html) 是一个自动格式化 C/C++/Objective-C 代码的工具, 可以让开发人员不需要担心代码审查期间的样式问题. + +强烈建议在发起请求之前格式化已更改的 C++ 代码,这将节省您和审阅者的时间. + +你可以通过 `npm install -g clang-format` 安装 `clang-format` 和 `git-clang-format`. + +根据 Electron C++ 代码样式自动格式化文件, 只要运行 `clang-format -i path/to/electron/file.cc` 即可. 它应该能够在 macOS/Linux/Windows 上运行. + +格式化已更改代码的工作流: + +1. 在 Electron 存储库中更改代码. +2. 运行 `git add your_changed_file.cc`. +3. 运行 `git-clang-format`, 然后你将可能会看到修改后的 `your_changed_file.cc`, 这些修改是从 `clang-format` 生成的. +4. 运行 `git add your_changed_file.cc`, 并提交你的修改. +5. 现在准备好的分支推送请求已经被打开. + +如果你想在你最新的 git commit(HEAD)中格式化更改的代码, 你可以运行 `git-clang-format HEAD~1`. 通过 `git-clang-format -h` 可以获得更多详情. + +## 编辑器集成 + +您还可以将 `clang-format` 直接集成到您喜欢的编辑器中, +有关设置编辑器集成的更多指导,请参阅这些页面: + + * [Atom](https://atom.io/packages/clang-format) + * [Vim & Emacs](http://clang.llvm.org/docs/ClangFormat.html#vim-integration) diff --git a/docs-translations/zh-CN/development/debug-instructions-windows.md b/docs-translations/zh-CN/development/debug-instructions-windows.md new file mode 100644 index 0000000000..0afa03463c --- /dev/null +++ b/docs-translations/zh-CN/development/debug-instructions-windows.md @@ -0,0 +1,46 @@ +# 在 Windows 中调试 + +如果你在 Electron 中遇到问题或者引起崩溃,你认为它不是由你的JavaScript应用程序引起的,而是由 Electron 本身引起的。调试可能有点棘手,特别是对于不习惯 native/C++ 调试的开发人员。 然而,使用 Visual Studio,GitHub托管的 Electron Symbol Server 和Electron 源代码,在 Electron 的源代码中启用断点调试是相当容易的。 + +## 要求 + +* **Electron 的调试版本**: 最简单的方法是自己构建它,使用 [Windows 的构建说明](build-instructions-windows.md) 中列出的工具和先决条件要求。虽然你可以轻松地附加和调试 Electron,因为你可以直接下载它,你会发现,由于大量的优化,使调试实质上更加困难:调试器将无法向您显示所有变量的内容,并且执行路径可能看起来很奇怪,这是因为内联,尾部调用和其他编译器优化。 + +* **Visual Studio 与 C++ 工具**: Visual Studio 2013 和 Visual Studio 2015 的免费社区版本都可以使用。 安装之后, [配置 Visual Studio 使用 GitHub 的 Electron Symbol 服务器](setting-up-symbol-server.md). 它将使 Visual Studio 能够更好地理解 Electron 中发生的事情,从而更容易以人类可读的格式呈现变量。 + +* **ProcMon**: [免费的 SysInternals 工具][sys-internals] 允许您检查进程参数,文件句柄和注册表操作。 + +## 附加并调试 Electron + +要启动调试会话,请打开 PowerShell/CMD 并执行 Electron 的调试版本,使用应用程序作为参数打开。 + +```powershell +$ ./out/D/electron.exe ~/my-electron-app/ +``` + +### 设置断点 + +然后,打开 Visual Studio。 Electron 不是使用 Visual Studio 构建的,因此不包含项目文件 - 但是您可以打开源代码文件 "As File",这意味着 Visual Studio 将自己打开它们。 您仍然可以设置断点 - Visual Studio 将自动确定源代码与附加过程中运行的代码相匹配,并相应地中断。 + +相关的代码文件可以在 `./atom/` 以及 Brightray 中找到, 找到 `./vendor/brightray/browser` 和 `./vendor/brightray/common`. 如果是内核,你也可以直接调试 Chromium,这显然在 `chromium_src` 中。 + +### 附加 + +您可以将 Visual Studio 调试器附加到本地或远程计算机上正在运行的进程。 进程运行后,单击 调试 / 附加 到进程(或按下 `CTRL+ALT+P`)打开“附加到进程”对话框。 您可以使用此功能调试在本地或远程计算机上运行的应用程序,同时调试多个进程。 + +如果Electron在不同的用户帐户下运行,请选中“显示所有用户的进程”复选框。 请注意,根据您的应用程序打开的浏览器窗口数量,您将看到多个进程。 典型的单窗口应用程序将导致 Visual Studio 向您提供两个 `Electron.exe` 条目 - 一个用于主进程,一个用于渲染器进程。 因为列表只给你的名字,目前没有可靠的方法来弄清楚哪个是。 + +### 我应该附加哪个进程? + +在主进程内部执行的代码(即在主 JavaScript 文件中找到或最终运行的代码)以及使用远程代码调用的代码(`require('electron').remote`)将在主进程内运行,而其他代码将在其相应的渲染器进程内执行。 + +您可以在调试时附加到多个程序,但在任何时候只有一个程序在调试器中处于活动状态。 您可以在 `调试位置` 工具栏或 `进程窗口` 中设置活动程序。 + +## 使用 ProcMon 观察进程 + +虽然 Visual Studio 非常适合检查特定的代码路径,但 ProcMon 的优势在于它可以监视应用程序对操作系统的所有操作 - 捕获进程的文件,注册表,网络,进程和分析详细信息。 它试图记录发生的 **所有** 事件,并且可能是相当压倒性的,而且果你想了解你的应用程序对操作系统做什么和如何做,它则是一个很有价值的资源。 + +有关 ProcMon 的基本和高级调试功能的介绍,请查看Microsoft提供的[视频教程][procmon-instructions]。 + +[sys-internals]: https://technet.microsoft.com/en-us/sysinternals/processmonitor.aspx +[procmon-instructions]: https://channel9.msdn.com/shows/defrag-tools/defrag-tools-4-process-monitor diff --git a/docs-translations/zh-CN/development/debugging-instructions-macos.md b/docs-translations/zh-CN/development/debugging-instructions-macos.md new file mode 100644 index 0000000000..0a7929e57e --- /dev/null +++ b/docs-translations/zh-CN/development/debugging-instructions-macos.md @@ -0,0 +1,97 @@ +# 在 macOS 中调试 + +如果你在 Electron 中遇到问题或者引起崩溃,你认为它不是由你的JavaScript应用程序引起的,而是由 Electron 本身引起的。调试可能有点棘手,特别是对于不习惯 native/C++ 调试的开发人员。 然而,使用 lldb 和 Electron 源代码,可以在 Electron 的源代码中使用断点启用逐步调试,这是相当容易的。 + +## 要求 + +* **Electron 的调试版本**: 最简单的方法是自己构建它,使用 [macOS 的构建说明](build-instructions-osx.md) 中列出的工具和先决条件要求。 虽然你可以轻松地附加和调试 Electron,因为你可以直接下载它,你会发现,由于大量的优化,使调试实质上更加困难:调试器将无法向您显示所有变量的内容,并且执行路径可能看起来很奇怪,这是因为内联,尾部调用和其他编译器优化。 + +* **Xcode**: 除了 Xcode,还安装 Xcode 命令行工具。它们包括 LLDB,在 Mac OS X 的 Xcode 中的默认调试器。它支持在桌面和iOS设备和模拟器上调试 C,Objective-C 和 C++。 + +## 附加并调试 Electron + +要启动调试会话,打开命令行并启动 `lldb` ,并传递一个调试版本的 Electron 作为参数。 + +```bash +$ lldb ./out/D/Electron.app +(lldb) target create "./out/D/Electron.app" +Current executable set to './out/D/Electron.app' (x86_64). +``` + +### 设置断点 + +LLDB是一个强大的工具,支持进行多种策略的代码检查。 在这做一个基本的介绍,让我们假设你从 JavaScript 调用一个不正常的命令 - 所以你想打断该命令的 C++ 对应的 Electron 源。 + +相关的代码文件可以在 `./atom/` 以及 Brightray 中找到, 找到 `./vendor/brightray/browser` 和 `./vendor/brightray/common`. 如果是内核,你也可以直接调试 Chromium,这显然在 `chromium_src` 中。 + +让我们假设你想调试 `app.setName()`, 在 `browser.cc` 中定义为 `Browser::SetName()`. 使用 `breakpoint` 命令进行断点,指定文件和断点位置:: + +```bash +(lldb) breakpoint set --file browser.cc --line 117 +Breakpoint 1: where = Electron Framework`atom::Browser::SetName(std::__1::basic_string, std::__1::allocator > const&) + 20 at browser.cc:118, address = 0x000000000015fdb4 +``` + +然后, 启动 Electron: + +```bash +(lldb) run +``` + +应用程式会立即暂停,因为 Electron 会在启动时设定应用程序名称: + +```bash +(lldb) run +Process 25244 launched: '/Users/fr/Code/electron/out/D/Electron.app/Contents/MacOS/Electron' (x86_64) +Process 25244 stopped +* thread #1: tid = 0x839a4c, 0x0000000100162db4 Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 20 at browser.cc:118, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 + frame #0: 0x0000000100162db4 Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 20 at browser.cc:118 + 115 } + 116 + 117 void Browser::SetName(const std::string& name) { +-> 118 name_override_ = name; + 119 } + 120 + 121 int Browser::GetBadgeCount() { +(lldb) +``` + +显示当前帧的参数和局部变量, 运行 `frame variable` (或 `fr v`), 这将显示你的应用程序当前设置名称为 “Electron”. + + +```bash +(lldb) frame variable +(atom::Browser *) this = 0x0000000108b14f20 +(const string &) name = "Electron": { + [...] +} +``` + +在当前选择的线程中执行源级单步执行, 执行 `step` (或 `s`). 这将带你进入 `name_override_.empty()`。 继续前进,步过,运行 `next` (或 `n`). + +```bash +(lldb) step +Process 25244 stopped +* thread #1: tid = 0x839a4c, 0x0000000100162dcc Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 44 at browser.cc:119, queue = 'com.apple.main-thread', stop reason = step in + frame #0: 0x0000000100162dcc Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 44 at browser.cc:119 + 116 + 117 void Browser::SetName(const std::string& name) { + 118 name_override_ = name; +-> 119 } + 120 + 121 int Browser::GetBadgeCount() { + 122 return badge_count_; +``` + +要完成此时的调试,运行 `process continue`。 你也可以继续,直到这个线程中的某一行被命中(`线程直到100`)。 此命令将在当前帧中运行线程,直到它到达此帧中的行100,或者如果它离开当前帧,则停止。 + +现在,如果你打开 Electron 的开发工具并调用 `setName`,你将再次命中断点。 + +### 进一步阅读 +LLDB是一个强大的工具,有一个庞大的文档。 要了解更多信息,请参考 Apple 的调试文档, 例如 [LLDB Command Structure Reference][lldb-command-structure] +或 [Using LLDB as a Standalone Debugger][lldb-standalone]. + +你也可以查看LLDB的 [manual and tutorial][lldb-tutorial], 这将解释更复杂的调试场景. + +[lldb-command-structure]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-basics.html#//apple_ref/doc/uid/TP40012917-CH2-SW2 +[lldb-standalone]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/lldb-terminal-workflow-tutorial.html +[lldb-tutorial]: http://lldb.llvm.org/tutorial.html diff --git a/docs-translations/zh-CN/glossary.md b/docs-translations/zh-CN/glossary.md index 37ca43d9fb..69465b537d 100644 --- a/docs-translations/zh-CN/glossary.md +++ b/docs-translations/zh-CN/glossary.md @@ -1,4 +1,4 @@ -# Glossary +# 术语表 这篇文档说明了一些经常在 Electron 开发中使用的专业术语。 diff --git a/docs-translations/zh-CN/project/README.md b/docs-translations/zh-CN/project/README.md index 224243f400..bdd5d06dd8 100644 --- a/docs-translations/zh-CN/project/README.md +++ b/docs-translations/zh-CN/project/README.md @@ -1,4 +1,4 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![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/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) @@ -7,7 +7,7 @@ Electron框架,让您可使用JavaScript, HTML 及 CSS 编写桌面程序。 它是基于[Node.js](https://nodejs.org/)和[Chromium](http://www.chromium.org)开发的, -[Atom editor](https://github.com/atom/atom)以及很多其他的[apps](http://electron.atom.io/apps)就是使用Electron编写的。 +[Atom editor](https://github.com/atom/atom)以及很多其他的[apps](https://electron.atom.io/apps)就是使用Electron编写的。 请关注Twitter [@ElectronJS](https://twitter.com/electronjs) 以获得重要通告。 @@ -73,7 +73,7 @@ ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ npm install electron -g - [`electron-br`](https://electron-br.slack.com) *(葡萄牙语-巴西)* - [`electron-kr`](http://www.meetup.com/electron-kr/) *(韩语)* - [`electron-jp`](https://electron-jp-slackin.herokuapp.com/) *(日语)* -- [`electron-tr`](http://www.meetup.com/Electron-JS-Istanbul/) *(土耳其)* +- [`electron-tr`](http://electron-tr.herokuapp.com) *(土耳其)* - [`electron-id`](https://electron-id.slack.com) *(印度尼西亚)* 查看 [awesome-electron](https://github.com/sindresorhus/awesome-electron) diff --git a/docs-translations/zh-CN/styleguide.md b/docs-translations/zh-CN/styleguide.md new file mode 100644 index 0000000000..74f888ace0 --- /dev/null +++ b/docs-translations/zh-CN/styleguide.md @@ -0,0 +1,224 @@ +# Electron 文档风格指南 + +这里是一些编写 Electron 文档的指南. + +## 标题 + +* 每个页面顶部必须有一个单独的 `#` 级标题。 +* 同一页面中的章节必须有 `##` 级标题。 +* 子章节需要根据它们的嵌套深度增加标题中的 `#` 数量。 +* 页面标题中的所有单词首字母都必须大写,除了 “of” 和 “and” 之类的连接词。 +* 只有章节标题的第一个单词首字母必须大写. + +举一个 `Quick Start` 的例子: + +```markdown +# Quick Start + +... + +## Main process + +... + +## Renderer process + +... + +## Run your app + +... + +### Run as a distribution + +... + +### Manually downloaded Electron binary + +... +``` + +对于 API 参考, 可以例外于这些规则. + +## Markdown 规则 + +* 在代码块中使用 `bash` 而不是 `cmd`(由于语法高亮问题). +* 行长度应该控制在80列内. +* 列表嵌套不超出2级 (由于 Markdown 渲染问题). +* 所有的 `js` 和 `javascript` 代码块均被标记为 +[standard-markdown](http://npm.im/standard-markdown). + +## 用词选择 + +* 在描述结果时,使用 “will” 而不是 “would”。 +* 首选 "in the ___ process" 而不是 "on". + +## API 参考 + +以下规则仅适用于 API 的文档。 + +### 页面标题 + +每个页面必须使用由 `require('electron')` 返回的实际对象名称作为标题,例如 `BrowserWindow`,`autoUpdater` 和 `session`。 + +在页面标题下必须是以 `>` 开头的单行描述。 + +举一个 `session` 的例子: + +```markdown +# session + +> Manage browser sessions, cookies, cache, proxy settings, etc. +``` + +### 模块方法和事件 + +对于非类的模块,它们的方法和事件必须在 `## Methods` 和 `## Events` 章节中列出。 + +举一个 `autoUpdater` 的例子: + +```markdown +# autoUpdater + +## Events + +### Event: 'error' + +## Methods + +### `autoUpdater.setFeedURL(url[, requestHeaders])` +``` + +### 类 + +* API 类或作为模块一部分的类必须在 `## Class: TheClassName` 章节中列出. +* 一个页面可以有多个类. +* 构造函数必须用 `###` 级标题列出. +* [静态方法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static) 必须在 `### Static Methods` 章节中列出. +* [实例方法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Prototype_methods) 必须在 `### Instance Methods` 章节中列出. +* 所有具有返回值的方法必须用 "Returns `[TYPE]` - Return description" 的形式描述. + * 如果该方法返回一个 `Object`,则可以使用冒号后跟换行符,然后使用与函数参数相同样式的属性的无序列表来指定其结构. +* 实例事件必须在 `### Instance Events` 章节中列出. +* 实例属性必须在 `### Instance Properties` 章节中列出. + * 实例属性必须以 "A [Property Type] ..." 开始描述. + +这里用 `Session` 和 `Cookies` 类作为例子: + +```markdown +# session + +## Methods + +### session.fromPartition(partition) + +## Properties + +### session.defaultSession + +## Class: Session + +### Instance Events + +#### Event: 'will-download' + +### Instance Methods + +#### `ses.getCacheSize(callback)` + +### Instance Properties + +#### `ses.cookies` + +## Class: Cookies + +### Instance Methods + +#### `cookies.get(filter, callback)` +``` + +### 方法 + +方法章节必须采用以下形式: + +```markdown +### `objectName.methodName(required[, optional]))` + +* `required` String - A parameter description. +* `optional` Integer (optional) - Another parameter description. + +... +``` + +标题可以是 `###` 级别或 `####` 级别,具体取决于它是模块还是类的方法。 + +对于模块,`objectName` 是模块的名称。 对于类,它必须是类的实例的名称,并且不能与模块的名称相同。 + +例如,`session` 模块下的 `Session` 类的方法必须使用 `ses` 作为 `objectName` 。 + +可选参数由围绕可选参数的方括号 `[]` 表示,并且如果此可选参数跟随另一个参数,则需要逗号: + +``` +required[, optional] +``` + +下面的方法是每个参数更加详细的信息。 参数的类型由常见类型表示: + +* [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) +* [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) +* [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) +* [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +* [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) +* 或自定义类型,就像 Electron 的 [`WebContent`](api/web-contents.md) + +如果参数或方法对某些平台是唯一的,那么这些平台将使用数据类型后面的空格分隔的斜体列表来表示。 值可以是 `macOS`,`Windows` 或 `Linux` + +```markdown +* `animate` Boolean (optional) _macOS_ _Windows_ - Animate the thing. +``` + +`Array` 类型的参数, 必须在指定数组下面的描述中描述可能包含的元素. + +`Function` 类型参数的描述应该清楚描述它是如何被调用的,并列出将被传递给它的参数的类型. + +### 事件 + +事件章节必须采用以下形式: + +```markdown +### Event: 'wake-up' + +Returns: + +* `time` String + +... +``` + +标题可以是 `###` 级别或 `####` 级别,具体取决于它是模块还是类的事件。 + +事件的参数遵循与方法相同的规则. + +### 属性 + +属性章节必须采用以下形式: + +```markdown +### session.defaultSession + +... +``` + +标题可以是 `###` 级别或 `####` 级别,具体取决于它是模块还是类的属性。 + +## 文档翻译 + +Electron 文档的翻译文件位于 `docs-translations` 目录中. + +如要添加另一个设定集(或部分设定集): + +* 创建以语言缩写命名的子目录。 +* 翻译文件。 +* 更新您的语言目录中的 `README.md` 文件以链接到已翻译的文件。 +* 在 Electron 的主 [README](https://github.com/electron/electron#documentation-translations) 上添加到翻译目录的链接。 + +请注意,`docs-translations` 下的文件只能包含已被翻译的文件,不应将原始英语文件复制到那里。 diff --git a/docs-translations/zh-CN/tutorial/electron-versioning.md b/docs-translations/zh-CN/tutorial/electron-versioning.md new file mode 100644 index 0000000000..7027347b39 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/electron-versioning.md @@ -0,0 +1,11 @@ +# Electron 版本管理 + +如果你是一个经验丰富的Node开发人员,你肯定知道`semver` - 然而这里给你的依赖管理系统可能只有粗略的指导建议而不是固定的版本号。由于对 Node 和 Chromium 的硬性依赖,Electron 处于一个稍微复杂的境地,而且不遵循semver。因此,您应该始终引用特定版本的Electron。 + +版本号使用参照以下规则: + +* 主要版本: 适用于 Electron API 的突破性变更 - 如果您从 `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/offscreen-rendering.md b/docs-translations/zh-CN/tutorial/offscreen-rendering.md new file mode 100644 index 0000000000..bff3f939fb --- /dev/null +++ b/docs-translations/zh-CN/tutorial/offscreen-rendering.md @@ -0,0 +1,44 @@ +# 离屏渲染 + +离线渲染允许您在位图中获取浏览器窗口的内容,因此可以在任何地方渲染,例如在3D场景中的纹理。Electron中的离屏渲染使用与 [Chromium +Embedded Framework](https://bitbucket.org/chromiumembedded/cef) 项目类似的方法。 + +可以使用两种渲染模式,并且只有脏区通过 `'paint'` 事件才能更高效。渲染可以停止、继续,并且可以设置帧速率。 指定的帧速率是上限值,当网页上没有发生任何事件时,不会生成任何帧。 最大帧速率是60,因为再高没有好处,而且损失性能。 + +**注意:** 屏幕窗口始终创建为 [Frameless Window](../api/frameless-window.md). + +## 两种渲染模式 + +### GPU加速 + +GPU加速渲染意味着使用GPU用于合成。因为帧必须从需要更多性能的GPU中复制,因此这种模式比另一个模式慢得多。这种模式的优点是支持WebGL和3D CSS动画。 + +### 软件输出设备 + +此模式使用软件输出设备在CPU中渲染,因此帧生成速度更快,因此此模式优先于GPU加速模式。 + +要启用此模式,必须通过调用 [`app.disableHardwareAcceleration()`][disablehardwareacceleration] API 来禁用GPU加速。 + +## 使用 + +``` javascript +const {app, BrowserWindow} = require('electron') + +app.disableHardwareAcceleration() + +let win +app.once('ready', () => { + win = new BrowserWindow({ + webPreferences: { + offscreen: true + } + }) + win.loadURL('http://github.com') + win.webContents.on('paint', (event, dirty, image) => { + // updateBitmap(dirty, image.getBitmap()) + }) + win.webContents.setFrameRate(30) +}) +``` + +[disablehardwareacceleration]: ../api/app.md#appdisablehardwareacceleration diff --git a/docs-translations/zh-CN/tutorial/quick-start.md b/docs-translations/zh-CN/tutorial/quick-start.md index 390b311fcf..92c2fdbddd 100644 --- a/docs-translations/zh-CN/tutorial/quick-start.md +++ b/docs-translations/zh-CN/tutorial/quick-start.md @@ -195,6 +195,6 @@ $ cd electron-quick-start $ npm install && npm start ``` -更多 apps 例子,查看 electron 社区创建的 [list of boilerplates](http://electron.atom.io/community/#boilerplates)。 +更多 apps 例子,查看 electron 社区创建的 [list of boilerplates](https://electron.atom.io/community/#boilerplates)。 [share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/zh-CN/tutorial/repl.md b/docs-translations/zh-CN/tutorial/repl.md new file mode 100644 index 0000000000..c23a4fc951 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/repl.md @@ -0,0 +1,22 @@ +# 交互式解释器 (REPL) + +读取(Read)-运算(Eval)-输出(Print)-循环(Loop) (REPL) 是很简单的, 交互式的计算机编程环境,它采用单个用户输入,运算并返回结果给用户。 + +在这里 `repl` 模块提供了一个 REPL 的实现, 可以这样使用: + +* 如果你的 `electron` 或 `electron-prebuilt` 已经安装为本地项目依赖项: + + ```sh + ./node_modules/.bin/electron --interactive + ``` +* 如果你的 `electron` 或 `electron-prebuilt` 已经为全局方式安装: + + ```sh + electron --interactive + ``` + +这里只会为主进程创建一个REPL。 您可以使用 Dev Tools 的“控制台”选项卡来为渲染器进程获取一个REPL。 + +**注意:** `electron --interactive` 在 Windows 上不可用. + +更多的内容可以在这里找到 [Node.js REPL docs](https://nodejs.org/dist/latest/docs/api/repl.html). diff --git a/docs-translations/zh-CN/tutorial/security.md b/docs-translations/zh-CN/tutorial/security.md new file mode 100644 index 0000000000..b546f5c5ae --- /dev/null +++ b/docs-translations/zh-CN/tutorial/security.md @@ -0,0 +1,46 @@ +# 安全,本地功能和你的责任 + +作为 web 开发人员,我们通常喜欢网络安全性更强大的浏览器 - 与我们编写的代码相关的风险相对较小。我们的网站在沙箱中获得有限的权限,我们相信我们的用户可以享受由大量工程师团队构建的浏览器,能够快速响应新发现的安全威胁。 + +当使用 Electron 时,要知道 Electron 不是一个 Web 浏览器很重要。它允许您使用熟悉的 Web 技术构建功能丰富的桌面应用程序,但是您的代码具有更强大的功能。 JavaScript 可以访问文件系统,用户 shell 等。这允许您构建更高质量的本机应用程序,但是内在的安全风险会随着授予您的代码的额外权力而增加。 + +考虑到这一点,请注意,在 Electron 不任何处理的情况下显示来自不受信任的来源的任何内容将带来了严重的安全风险。事实上,最流行的 Electron 应用程序(Atom,Slack,Visual Studio Code 等)主要显示本地内容(或没有 Node 集成的可信安全远程内容) - 如果您的应用程序从在线源执行代码,那么您有责任确保代码不是恶意的。 + +## 报告安全问题 + +有关如何正确上报 Electron 漏洞的信息,参阅 [SECURITY.md](https://github.com/electron/electron/tree/master/SECURITY.md) + +## Chromium 安全问题和升级 + +尽管 Electron 努力尽快支持新版本的 Chromium,但开发人员应该意识到,升级是一项严肃的工作 - 涉及手动编辑几十个甚至几百个文件。 考虑到当前的资源和贡献,Electron 通常不会是最新版本的 Chromium,总是落后于一两天或几周。 + +我们认为,我们当前的更新 Chromium 组件的系统在我们可用的资源和构建在框架之上的大多数应用程序的需求之间取得了适当的平衡。 我们绝对有兴趣听听更多关于在 Electron 上构建事物的人的具体用例。 非常欢迎提出请求并且捐助支持我们的努力。 + +## 除了以上建议 + +每当您从远程目标收到代码并在本地执行它时,就会存在安全问题。 举个例子,比如在浏览器窗口内显示的远程网站。 如果攻击者以某种方式设法改变所述内容(通过直接攻击源或者通过在应用和实际目的地之间进行攻击),他们将能够在用户的机器上执行本地代码。 + +> :警告: 在任何情况下都不应该在启用了 Node 集成时加载并执行远程代码. 反而应该只使用本地文件(与应用程序一起打包)来执行 Node 代码。要显示远程内容, 应使用 `webview` 标签并确保禁用了 `nodeIntegration`. + +#### 检查列表 + +这并不是万无一失的,但至少,你应该尝试以下内容: + +* 只显示安全的内容(https) +* 在显示远程内容的所有渲染器中禁用 Node 集成 (在 `webPreferences` 中设置 `nodeIntegration` 为 `false`) +* 在显示远程内容的所有渲染器中启用上下文隔离 (在 `webPreferences` 中设置 `contextIsolation` 为 `true`) +* 在所有加载远程内容的会话中使用 `ses.setPermissionRequestHandler()` . +* 不要禁用 `webSecurity`. 禁用它将禁用同源策略. +* 定义一个 [`Content-Security-Policy`](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) +, 并使用限制规则 (即: `script-src 'self'`) +* 覆盖并禁用 [`eval`](https://github.com/nylas/N1/blob/0abc5d5defcdb057120d726b271933425b75b415/static/index.js#L6-L8) +, 它允许字符串作为代码执行. +* 不要设置 `allowRunningInsecureContent` 为 `true`. +* 不要启用 `experimentalFeatures` 或 `experimentalCanvasFeatures` 除非你知道你在做什么. +* 不要使用 `blinkFeatures` 除非你知道你在做什么. +* WebViews: 不要填加 `nodeintegration` 属性. +* WebViews: 不要使用 `disablewebsecurity` +* WebViews: 不要使用 `allowpopups` +* WebViews: 不要使用 `insertCSS` 或 `executeJavaScript` 操作远程 CSS/JS. + +强调一下,这份列表只是将风险降到最低,并不会完全屏蔽风险。 如果您的目的是展示一个网站,浏览器将是一个更安全的选择。 \ No newline at end of file diff --git a/docs-translations/zh-CN/tutorial/supported-platforms.md b/docs-translations/zh-CN/tutorial/supported-platforms.md index 42c180dbaf..27e8976aa0 100644 --- a/docs-translations/zh-CN/tutorial/supported-platforms.md +++ b/docs-translations/zh-CN/tutorial/supported-platforms.md @@ -1,26 +1,24 @@ # 支持的平台 -以下的平台是 Electron 目前支持的: +目前 Electron 支持以下平台: ### macOS -对于 macOS 系统仅有64位的二进制文档,支持的最低版本是 macOS 10.9。 +对于 macOS 仅提供64位版本,并且只支持 macOS 10.9 或更高版本。 ### Windows -仅支持 Windows 7 及其以后的版本,之前的版本中是不能工作的。 +仅支持 Windows 7 或更高版本。 -对于 Windows 提供 `ia32` (x86) 和 `amd64` (x64) 版本的二进制文件。需要注意的是 `ARM` 版本的 Windows 目前尚不支持。 +对于 Windows 提供 `ia32` (x86) 和 `amd64` (x64) 版本。需要注意的是 `ARM` 版本的 Windows 目前尚不支持。 ### Linux -预编译的 `ia32` (`i686`) 和 `x64` (`amd64`) 版本 Electron 二进制文件都是在 -Ubuntu 12.04 下编译的,`arm` 版的二进制文件是在 ARM v7(硬浮点 ABI 与 +Electron 的 `ia32` (`i686`) 和 `x64` (`amd64`) 预编译版本均是在Ubuntu 12.04 下编译的,`arm` 版的二进制文件是在 ARM v7(硬浮点 ABI 与 Debian Wheezy 版本的 NEON)下完成的。 -预编译二进制文件是否能够运行,取决于其中是否包括了编译平台链接的库,所以只有 Ubuntu 12.04 -可以保证正常工作,但是以下的平台也被证实可以运行 Electron 的预编译版本: +预编译版本是否能够正常运行,取决于其中是否包含了编译平台的链接库。所以只有 Ubuntu 12.04 是可以保证能正常运行的,并且以下平台也被证实可以正常运行 Electron 的预编译版本: -* Ubuntu 12.04 及更新 +* Ubuntu 12.04 或更高版本 * Fedora 21 * Debian 8 diff --git a/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md b/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md index 799c482bd9..b512344312 100644 --- a/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md @@ -155,4 +155,4 @@ client 当然,你也可以在运行 Electron 时传入参数指定你 app 的所在文件夹。这步可以免去你拷贝-粘贴你的 app 到 Electron 的资源目录。 [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: http://electron.atom.io/spectron +[spectron]: https://electron.atom.io/spectron diff --git a/docs-translations/zh-CN/tutorial/windows-store-guide.md b/docs-translations/zh-CN/tutorial/windows-store-guide.md new file mode 100644 index 0000000000..a34f40d2ee --- /dev/null +++ b/docs-translations/zh-CN/tutorial/windows-store-guide.md @@ -0,0 +1,113 @@ +# Windows商店指南 + +在 Windows 8 中, 一些不错的旧 win32 程序迎来了一个新朋友: 通用Windows平台(UWP)。 新的 `.appx` 格式不仅启用了许多新的强大的 API,如 Cortana 或推送通知,而且通过Windows 应用商店,也同时简化了安装和更新。 + +Microsoft 开发了一个工具,将 Electron 应用程序[编译为 `.appx` 软件包][electron-windows-store],使开发人员能够使用新应用程序模型中的一些好东西。 本指南解释了如何使用它 - 以及 Electron AppX 包的功能和限制。 + +## 背景和要求 + +Windows 10 的 "周年更新" 能够运行 win32 `.exe` 程序并且它们的虚拟化文件系统和注册表跟随一起启动。 两者都是通过在 Windows 容器中运行应用程序和安装器编译后创建的,允许 Windows 在安装过程中正确识别操作系统进行了哪些修改。 将可执行文件和虚拟文件系统与虚拟注册表配对, 允许 Windows 启用一键安装和卸载。 + +此外,exe 在 appx 模型内启动 - 这意味着它可以使用通用 Windows 平台可用的许多 API。 为了获得更多的功能,Electron 应用程序可以与一个看不见的 UWP 后台任务配合使用,它与 `exe` 一起启动,作为后台运行任务的接收器,接收推送通知或与其他 UWP 应用程序通信 。 + +要编译任何现有的 Electron 应用程序,请确保满足以下要求: + +* Windows 10及周年更新 (2016年8月2日发布的) +* Windows 10 SDK, [这里下载][windows-sdk] +* 最新的 Node 4 (运行 `node -v` 来确认) + +然后, 安装 `electron-windows-store` CLI: + +``` +npm install -g electron-windows-store +``` + +## 步骤 1: 打包你的 Electron 应用程序 + +打包应用程序使用 [electron-packager][electron-packager] (或类似工具). 确保在最终的应用程序中删除不需要的 `node_modules`, 因为这些你不需要模块只会额外增加你的应用程序的大小. + +结构输出应该看起来大致像这样: + +``` +├── Ghost.exe +├── LICENSE +├── content_resources_200_percent.pak +├── content_shell.pak +├── d3dcompiler_47.dll +├── ffmpeg.dll +├── icudtl.dat +├── libEGL.dll +├── libGLESv2.dll +├── locales +│   ├── am.pak +│   ├── ar.pak +│   ├── [...] +├── natives_blob.bin +├── node.dll +├── resources +│   ├── app +│   └── atom.asar +├── snapshot_blob.bin +├── squirrel.exe +└── ui_resources_200_percent.pak +``` + +## 步骤 2: 运行 electron-windows-store + +从提权的 PowerShell(用管理员身份运行它)中,以所需的参数运行 `electron-windows-store`,传递输入和输出目录,应用程序的名称和版本,以及确认`node_modules`应该是扁平的。 + + +``` +electron-windows-store ` + --input-directory C:\myelectronapp ` + --output-directory C:\output\myelectronapp ` + --flatten true ` + --package-version 1.0.0.0 ` + --package-name myelectronapp +``` + +一旦执行,工具就开始工作:它接受您的 Electron 应用程序作为输入,展平 `node_modules`。 然后,它将应用程序归档为 `app.zip`。 使用安装程序和 Windows 容器,该工具创建一个“扩展的” AppX 包 - 包括 Windows 应用程序清单 (`AppXManifest.xml`)以及虚拟文件系统和输出文件夹中的虚拟注册表。 + +当创建扩展的 AppX 文件后,该工具使用 Windows App Packager(`MakeAppx.exe`)将磁盘上的这些文件创建为单文件 AppX 包。 最后,该工具可用于在计算机上创建可信证书,以签署新的 AppX 包。 使用签名的 AppX 软件包,CLI也可以自动在您的计算机上安装软件包。 + + +## 步骤 3: 使用 AppX 包 + +为了运行您的软件包,您的用户将需要将 Windows 10 安装“周年纪念更新” - 有关如何更新Windows的详细信息可以在[这里][how-to-update]找到 + +与传统的UWP应用程序不同,打包应用程序目前需要进行手动验证过程,您可以在[这里][centennial-campaigns]申请. +在此期间,所有用户都能够通过双击安装包来安装您的程序,所以如果您只是寻找一个更简单的安装方法,可能不需要提交到商店。 + +在受管理的环境中(通常是企业), `Add-AppxPackage` PowerShell Cmdlet 可用于以[自动方式安装][add-appxpackage]它。 + +另一个重要的限制是编译的 AppX 包仍然包含一个 win32 可执行文件,因此不会在 Xbox,HoloLens 或 Phones 中运行。 + +## 可选: 使用 BackgroundTask 添加 UWP 功能 + +您可以将 Electron 应用程序与不可见的 UWP 后台任务配对,以充分利用 Windows 10 功能,如推送通知,Cortana 集成或活动磁贴。 + +如何使用 Electron 应用程序通过后台任务发送 Toast 通知和活动磁贴,请查看[微软提供的案例][background-task]. + + +## 可选: 使用容器虚拟化进行转换 + +要生成 AppX 包,`electron-windows-store` CLI 使用的模板应该适用于大多数 Electron 应用程序。 但是,如果您使用自定义安装程序,或者您遇到生成的包的任何问题,您可以尝试使用 Windows 容器编译创建包 - 在该模式下,CLI 将在空 Windows 容器中安装和运行应用程序,以确定应用程序正在对操作系统进行哪些修改。 + +在运行 CLI 之前,您必须设置 “Windows Desktop App Converter” 。 这将需要几分钟,但不要担心 - 你只需要这样做一次。 从这里下载 [Desktop App Converter][app-converter] + +您将得到两个文件: `DesktopAppConverter.zip` 和 `BaseImage-14316.wim`. + +1. 解压 `DesktopAppConverter.zip`. 打开提权的 PowerShell (用"以管理员权限运行"打开, 确保您的系统执行策略允许我们通过调用 `Set-ExecutionPolicy bypass` 来运行我们想要运行的一切). +2. 然后, 通过调用 `.\DesktopAppConverter.ps1 -Setup -BaseImage .\BaseImage-14316.wim`, 运行 Desktop App Converter 安装,并传递 Windows 基本映像的位置 (下载的 `BaseImage-14316.wim`). +3. 如果运行以上命令提示您重新启动,请重新启动计算机,并在成功重新启动后再次运行上述命令。 + +当安装成功后,您可以继续编译你的 Electron 应用程序。 + +[windows-sdk]: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk +[app-converter]: https://www.microsoft.com/en-us/download/details.aspx?id=51691 +[add-appxpackage]: https://technet.microsoft.com/en-us/library/hh856048.aspx +[electron-packager]: https://github.com/electron-userland/electron-packager +[electron-windows-store]: https://github.com/catalystcode/electron-windows-store +[background-task]: https://github.com/felixrieseberg/electron-uwp-background +[centennial-campaigns]: https://developer.microsoft.com/en-us/windows/projects/campaigns/desktop-bridge +[how-to-update]: https://blogs.windows.com/windowsexperience/2016/08/02/how-to-get-the-windows-10-anniversary-update diff --git a/docs-translations/zh-TW/project/README.md b/docs-translations/zh-TW/project/README.md index dae51010d1..7225b26c6f 100644 --- a/docs-translations/zh-TW/project/README.md +++ b/docs-translations/zh-TW/project/README.md @@ -1,4 +1,4 @@ -[![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) +[![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/kvxe4byi7jcxbe26/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) @@ -7,7 +7,7 @@ Electron框架讓你可以用JavaScript, HTML 和 CSS 編寫跨平台的應用程式。 它是基於[Node.js](https://nodejs.org/)和[Chromium](http://www.chromium.org), -並且被[Atom editor](https://github.com/atom/atom)及許多其他的[apps](http://electron.atom.io/apps)所使用。 +並且被[Atom editor](https://github.com/atom/atom)及許多其他的[apps](https://electron.atom.io/apps)所使用。 請關注[@ElectronJS](https://twitter.com/electronjs)的Twitter以獲得重要公告。 @@ -66,12 +66,12 @@ Clone 並使用 [`electron/electron-quick-start`](https://github.com/electron/el - [`electron-br`](https://electron-br.slack.com) *(葡萄牙語-巴西)* - [`electron-kr`](http://www.meetup.com/electron-kr/) *(韓語)* - [`electron-jp`](https://electron-jp-slackin.herokuapp.com/) *(日語)* -- [`electron-tr`](http://www.meetup.com/Electron-JS-Istanbul/) *(土耳其)* +- [`electron-tr`](http://electron-tr.herokuapp.com) *(土耳其)* - [`electron-id`](https://electron-id.slack.com) *(印度尼西亞)* 在 [awesome-electron](https://github.com/sindresorhus/awesome-electron) 查看由社群維護的清單,包括實用的應用程式、工具以及資源。 -## 憑證 +## 授權條款 MIT © 2016 Github diff --git a/docs-translations/zh-TW/tutorial/about.md b/docs-translations/zh-TW/tutorial/about.md index 03852baa89..686d1d92a7 100644 --- a/docs-translations/zh-TW/tutorial/about.md +++ b/docs-translations/zh-TW/tutorial/about.md @@ -1,10 +1,10 @@ # 關於 Electron -[Electron](http://electron.atom.io) 是 GitHub 為了透過 HTML, CSS 和 JavaScript 開發跨平台桌面應用程式, 所使用的一個開放原始碼函式庫。為了達成這個目標,Electron 把 [Chromium](https://www.chromium.org/Home) 和 [Node.js](https://nodejs.org) 整合成單一的執行程式,應用程式可以在 Mac, Windows, 和 Linux 作業系統下執行。 +[Electron](https://electron.atom.io) 是 GitHub 為了透過 HTML, CSS 和 JavaScript 開發跨平台桌面應用程式, 所使用的一個開放原始碼函式庫。為了達成這個目標,Electron 把 [Chromium](https://www.chromium.org/Home) 和 [Node.js](https://nodejs.org) 整合成單一的執行程式,應用程式可以在 Mac, Windows, 和 Linux 作業系統下執行。 Electron 在 2013 年創立,做為 [Atom](https://atom.io) (一款由GitHub 推出,可以快速修改調整的文字編輯器) 的程式框架, 這兩個專案在 2014 年春天開放原始碼。 -在這之後,Electron 變成一個非成流行的工具,為許多開放原始碼開發者、新創事業,以及已發展的公司所使用(請見[Apps](http://electron.atom.io/apps/))。 +在這之後,Electron 變成一個非成流行的工具,為許多開放原始碼開發者、新創事業,以及已發展的公司所使用(請見[Apps](https://electron.atom.io/apps/))。 若要了解更多關於 Electron 開發者或發行版的資訊,或想要開始使用 Electron 開發應用程式,可以參考[快速入門](https://github.com/electron/electron/blob/master/docs-translations/zh-TW/tutorial/quick-start.md) @@ -27,13 +27,13 @@ Electron 在 Chromium 提出新的穩定版本時會提出更新,時間通常 ### 版本控制 -由於同時高度依賴 Node.js 和 Chromium, Electron 在版本控制上處於一個有點特別的情況,所以不太遵照[ `semver`規範](http://semver.org)。你必須隨時參考一個特定的 Electron 版本。請參考 [Read more about Electron's versioning](http://electron.atom.io/docs/tutorial/electron-versioning/) 或是察看 [versions currently in use](https://electron.atom.io/#electron-versions). +由於同時高度依賴 Node.js 和 Chromium, Electron 在版本控制上處於一個有點特別的情況,所以不太遵照[ `semver`規範](http://semver.org)。你必須隨時參考一個特定的 Electron 版本。請參考 [Read more about Electron's versioning](https://electron.atom.io/docs/tutorial/electron-versioning/) 或是察看 [versions currently in use](https://electron.atom.io/#electron-versions). ### 長期支援 Electron 目前並未對舊的版本提供長期支援,如果你目前使用的 Electron 版本可以運作, 你可以隨自己的喜好持續使用。如果你想使用新版本所提供的功能,你必須更新到較新的版本。 -一個主要的更新是在 `v1.0.0` 版本。如果你使用比這個版本更舊的 Electron,你必須參考 [read more about the `v1.0.0` changes](http://electron.atom.io/blog/2016/05/11/electron-1-0). +一個主要的更新是在 `v1.0.0` 版本。如果你使用比這個版本更舊的 Electron,你必須參考 [read more about the `v1.0.0` changes](https://electron.atom.io/blog/2016/05/11/electron-1-0). ## 核心哲學 @@ -41,7 +41,7 @@ Electron 目前並未對舊的版本提供長期支援,如果你目前使用 舉例來說, Electron 只使用 Chromium 在圖形渲染上的函式庫,而不使用整個 Chromium,這讓它更容易隨著 Chromium 更新,但也表示有些在 Google Chrome 瀏覽器上擁有的功能,在 Electron中並不存在。 -會加入 Electron 的新功能,主要是原生的 APIs。如果某個功能被與它相關的 Node.js 模組所擁有,它在 Electron 中也必須存在。請參考[Electron tools built by the community](http://electron.atom.io/community). +會加入 Electron 的新功能,主要是原生的 APIs。如果某個功能被與它相關的 Node.js 模組所擁有,它在 Electron 中也必須存在。請參考[Electron tools built by the community](https://electron.atom.io/community). ## 歷史 @@ -52,6 +52,6 @@ Electron 目前並未對舊的版本提供長期支援,如果你目前使用 | **2013年4月**| [Atom Shell 專案開始](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| | **2014年5月** | [Atom Shell 專案開放原始碼](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | | **2015年4月** | [Atom Shell 專案重新命名為 Electron](https://github.com/electron/electron/pull/1389). | -| **2016年5月** | [Electron 發行 `v1.0.0` 版](http://electron.atom.io/blog/2016/05/11/electron-1-0).| -| **2016年5月** | [Electron 應用程式可以和 Mac App 市集相容](http://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| -| **2016年8月** | [Windows 市集支援 Electron 應用程式](http://electron.atom.io/docs/tutorial/windows-store-guide).| +| **2016年5月** | [Electron 發行 `v1.0.0` 版](https://electron.atom.io/blog/2016/05/11/electron-1-0).| +| **2016年5月** | [Electron 應用程式可以和 Mac App 市集相容](https://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| +| **2016年8月** | [Windows 市集支援 Electron 應用程式](https://electron.atom.io/docs/tutorial/windows-store-guide).| diff --git a/docs-translations/zh-TW/tutorial/accessibility.md b/docs-translations/zh-TW/tutorial/accessibility.md index 35ecf81183..3ecbea66b6 100644 --- a/docs-translations/zh-TW/tutorial/accessibility.md +++ b/docs-translations/zh-TW/tutorial/accessibility.md @@ -1,12 +1,12 @@ # 可存取性 -產生具可存取性的應用程式是非常重要的,我們非常高興能介紹 [Devtron](http://electron.atom.io/devtron) 和 [Spectron](http://electron.atom.io/spectron) 這兩個新功能,這可以讓開發者更有機會能開發對大家來說更棒的應用程式。 +產生具可存取性的應用程式是非常重要的,我們非常高興能介紹 [Devtron](https://electron.atom.io/devtron) 和 [Spectron](https://electron.atom.io/spectron) 這兩個新功能,這可以讓開發者更有機會能開發對大家來說更棒的應用程式。 --- 在 Electron 應用程式中,可存取性的議題和在開發網站時非常類似,因為在根本上兩者都使用 HTML。然而對 Electron 應用程式來說,你不能為了增加可存取性而使用線上的程式審計機制,因為你的應用程式並沒有一個 URL 連結能夠引導審計者. -這些新的功能也為你的 Electron 應用程式帶來新的審計工具,你可以選擇要透過 Spectron 為你的測試增加審計,或是透過 Devtron 在開發者工具中使用。請參考 [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) 以獲得更多資訊。 +這些新的功能也為你的 Electron 應用程式帶來新的審計工具,你可以選擇要透過 Spectron 為你的測試增加審計,或是透過 Devtron 在開發者工具中使用。請參考 [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) 以獲得更多資訊。 ### Spectron @@ -30,4 +30,4 @@ app.client.auditAccessibility().then(function (audit) { 這兩個工具都使用 Google 為 Chrome 所建立的 [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) 函式庫。你可以在 [repository's wiki](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) 學到更多在這個函式庫中有關可存取性審計的資訊。 -如果你知道其他有關 Electron 可存取性來說更好的工具, 請把他們透過 pull request 加入 [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) 。 +如果你知道其他有關 Electron 可存取性來說更好的工具, 請把他們透過 pull request 加入 [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) 。 diff --git a/docs-translations/zh-TW/tutorial/quick-start.md b/docs-translations/zh-TW/tutorial/quick-start.md index 8b139d742b..3fe46f1ada 100644 --- a/docs-translations/zh-TW/tutorial/quick-start.md +++ b/docs-translations/zh-TW/tutorial/quick-start.md @@ -23,8 +23,8 @@ Electron 的用戶擁有在網頁中呼叫 Node.js APIs 的能力,允許低級 ## 主行程與渲染行程的區別 -主行程創造網頁透過創造 `BroswerWindow` 實例。每一個 `BroswerWindow` 實例都在自己的渲染行程裡運行著一個網頁。 -當一個 `BroswerWindow` 實例被銷毀,對應的渲染行程也會被終止。主行程管理所有網頁和與之對應的渲染行程。 +主行程創造網頁透過創造 `BrowserWindow` 實例。每一個 `BrowserWindow` 實例都在自己的渲染行程裡運行著一個網頁。 +當一個 `BrowserWindow` 實例被銷毀,對應的渲染行程也會被終止。主行程管理所有網頁和與之對應的渲染行程。 每一個渲染行程都是相互獨立的,並且只關心他們自己的網頁。 在網頁中,是不允許呼叫原生 GUI 相關 APIs 因為管理原生 GUI 資源在網頁上是非常危險而且容易造成資源洩露。 diff --git a/docs/README.md b/docs/README.md index 9159257682..dbb1421ab7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,6 +32,7 @@ an issue: * [Using Widevine CDM Plugin](tutorial/using-widevine-cdm-plugin.md) * [Testing on Headless CI Systems (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) * [Offscreen Rendering](tutorial/offscreen-rendering.md) +* [Keyboard Shortcuts](tutorial/keyboard-shortcuts.md) ## Tutorials @@ -39,6 +40,7 @@ an issue: * [Desktop Environment Integration](tutorial/desktop-environment-integration.md) * [Online/Offline Event Detection](tutorial/online-offline-events.md) * [REPL](tutorial/repl.md) +* [Native Notifications](tutorial/notifications.md) ## API References @@ -102,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/app.md b/docs/api/app.md index 83e071618d..cf537474af 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -125,8 +125,10 @@ Returns: * `event` Event * `hasVisibleWindows` Boolean -Emitted when the application is activated, which usually happens when the user -clicks on the application's dock icon. +Emitted when the application is activated. Various actions can trigger +this event, such as launching the application for the first time, attempting +to re-launch the application when it's already running, or clicking on the +application's dock or taskbar icon. ### Event: 'continue-activity' _macOS_ @@ -401,6 +403,28 @@ You can request the following paths by the name: * `videos` Directory for a user's videos. * `pepperFlashSystemPlugin` Full path to the system version of the Pepper Flash plugin. +### `app.getFileIcon(path[, options], callback)` + +* `path` String +* `options` Object (optional) + * `size` String + * `small` - 16x16 + * `normal` - 32x32 + * `large` - 48x48 on _Linux_, 32x32 on _Windows_, unsupported on _macOS_. +* `callback` Function + * `error` Error + * `icon` [NativeImage](native-image.md) + +Fetches a path's associated icon. + +On _Windows_, there a 2 kinds of icons: + +- Icons associated with certain file extensions, like `.mp3`, `.png`, etc. +- Icons inside the file itself, like `.exe`, `.dll`, `.ico`. + +On _Linux_ and _macOS_, icons depend on the application associated with file +mime type. + ### `app.setPath(name, path)` * `name` String @@ -736,6 +760,10 @@ Disables hardware acceleration for current app. This method can only be called before app is ready. +### `app.getAppMemoryInfo()` + +Returns [ProcessMemoryInfo[]](structures/process-memory-info.md): Array of `ProcessMemoryInfo` objects that correspond to memory usage statistics of all the processes associated with the app. + ### `app.setBadgeCount(count)` _Linux_ _macOS_ * `count` Integer @@ -796,11 +824,11 @@ Returns `Object`: `app.getLoginItemStatus().wasOpenedAsHidden` should be checked when the app is opened to know the current value. This setting is only supported on macOS. - * `path` String (optional) _Windows_ - The executable to launch at login. - Defaults to `process.execPath`. - * `args` String[] (optional) _Windows_ - The command-line arguments to pass to - the executable. Defaults to an empty array. Take care to wrap paths in - quotes. +* `path` String (optional) _Windows_ - The executable to launch at login. + Defaults to `process.execPath`. +* `args` String[] (optional) _Windows_ - The command-line arguments to pass to + the executable. Defaults to an empty array. Take care to wrap paths in + quotes. Set the app's login item settings. diff --git a/docs/api/browser-view.md b/docs/api/browser-view.md new file mode 100644 index 0000000000..3b7e6f9b9b --- /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 370902be99..8fcc3f8213 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -35,9 +35,9 @@ without visual flash, there are two solutions for different situations. ### Using `ready-to-show` event -While loading the page, the `ready-to-show` event will be emitted when renderer -process has done drawing for the first time, showing window after this event -will have no visual flash: +While loading the page, the `ready-to-show` event will be emitted when the renderer +process has rendered the page for the first time if the window has not been shown yet. Showing +the window after this event will have no visual flash: ```javascript const {BrowserWindow} = require('electron') @@ -47,7 +47,7 @@ win.once('ready-to-show', () => { }) ``` -This is event is usually emitted after the `did-finish-load` event, but for +This event is usually emitted after the `did-finish-load` event, but for pages with many remote resources, it may be emitted before the `did-finish-load` event. @@ -211,10 +211,16 @@ 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 is `true`. + * `nodeIntegrationInWorker` Boolean (optional) - Whether node integration is + enabled in web workers. Default is `false`. More about this can be found + in [Multithreading](../tutorial/multithreading.md). * `preload` String (optional) - Specifies a script that will be loaded before other scripts run in the page. This script will always have access to node APIs no matter whether node integration is turned on or off. The value should @@ -222,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 @@ -256,12 +269,12 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. macOS. Default is `false`. * `blinkFeatures` String (optional) - A list of feature strings separated by `,`, like `CSSVariables,KeyboardEventKey` to enable. The full list of supported feature - strings can be found in the [RuntimeEnabledFeatures.in][blink-feature-string] + strings can be found in the [RuntimeEnabledFeatures.json5][blink-feature-string] file. * `disableBlinkFeatures` String (optional) - A list of feature strings separated by `,`, like `CSSVariables,KeyboardEventKey` to disable. The full list of supported feature strings can be found in the - [RuntimeEnabledFeatures.in][blink-feature-string] file. + [RuntimeEnabledFeatures.json5][blink-feature-string] file. * `defaultFontFamily` Object (optional) - Sets the default font for the font-family. * `standard` String (optional) - Defaults to `Times New Roman`. * `serif` String (optional) - Defaults to `Times New Roman`. @@ -279,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 @@ -364,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. @@ -390,7 +407,7 @@ Emitted when the window is hidden. #### Event: 'ready-to-show' -Emitted when the web page has been rendered and window can be displayed without +Emitted when the web page has been rendered (while not being shown) and window can be displayed without a visual flash. #### Event: 'maximize' @@ -486,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: @@ -632,7 +657,8 @@ Returns `Boolean` - Whether current window is a modal window. #### `win.maximize()` -Maximizes the window. +Maximizes the window. This will also show (but not focus) the window if it +isn't being displayed already. #### `win.unmaximize()` @@ -669,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 @@ -853,7 +877,7 @@ On Linux always returns `true`. [macOS docs][window-levels] for more details. * `relativeLevel` Integer (optional) _macOS_ - The number of layers higher to set this window relative to the given `level`. The default is `0`. Note that Apple - discourages setting levels higher than 1 above `screen-saver`. + discourages setting levels higher than 1 above `screen-saver`. Sets whether the window should show always on top of other windows. After setting this, the window is still a normal window, not a toolbox window which @@ -1004,6 +1028,7 @@ Same as `webContents.capturePage([rect, ]callback)`. * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\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) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Same as `webContents.loadURL(url[, options])`. @@ -1266,7 +1291,25 @@ Controls whether to hide cursor when typing. Adds a vibrancy effect to the browser window. Passing `null` or an empty string will remove the vibrancy effect on the window. -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +#### `win.setTouchBar(touchBar)` _macOS_ _Experimental_ + +* `touchBar` TouchBar + +Sets the touchBar layout for the current window. Specifying `null` or +`undefined` clears the touch bar. This method only has an effect if the +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 [window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels diff --git a/docs/api/client-request.md b/docs/api/client-request.md index 0b722f2f09..9821b44261 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/clipboard.md b/docs/api/clipboard.md index 932a1da7b2..559fc01a90 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -103,7 +103,7 @@ clipboard. ```js clipboard.write({ - text: 'http://electron.atom.io', + text: 'https://electron.atom.io', bookmark: 'Electron Homepage' }) ``` @@ -133,24 +133,29 @@ Clears the clipboard content. Returns `String[]` - An array of supported formats for the clipboard `type`. -### `clipboard.has(data[, type])` _Experimental_ +### `clipboard.has(format[, type])` _Experimental_ -* `data` String +* `format` String * `type` String (optional) -Returns `Boolean` - Whether the clipboard supports the format of specified `data`. +Returns `Boolean` - Whether the clipboard supports the specified `format`. ```javascript const {clipboard} = require('electron') console.log(clipboard.has('

selection

')) ``` -### `clipboard.read(data[, type])` _Experimental_ +### `clipboard.read(format)` _Experimental_ -* `data` String -* `type` String (optional) +* `format` String -Returns `String` - Reads `data` from the clipboard. +Returns `String` - Reads `format` type from the clipboard. + +### `clipboard.readBuffer(format)` _Experimental_ + +* `format` String + +Returns `Buffer` - Reads `format` type from the clipboard. ### `clipboard.write(data[, type])` diff --git a/docs/api/cookies.md b/docs/api/cookies.md index 8e64202967..ba3cff3360 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 3e93de4a25..2f0848c119 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -40,18 +40,18 @@ 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) _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 - report. Only string properties are sent correctly, Nested objects are not + report. Only string properties are sent correctly. Nested objects are not supported. You are required to call this method before using any other `crashReporter` APIs and in each process (main/renderer) from which you want to collect crash reports. You can pass different options to `crashReporter.start` when calling from different processes. -**Note** Child processes created via the `child_process` module will not have access to the Electron modules. +**Note** Child processes created via the `child_process` module will not have access to the Electron modules. Therefore, to collect crash reports from them, use `process.crashReporter.start` instead. Pass the same options as above along with an additional one called `crashesDirectory` that should point to a directory to store the crash reports temporarily. You can test this out by calling `process.crash()` to crash the child process. @@ -60,6 +60,10 @@ reports temporarily. You can test this out by calling `process.crash()` to crash This will start the process that will monitor and send the crash reports. Replace `submitURL`, `productName` and `crashesDirectory` with appropriate values. +**Note:** If you need send additional/updated `extra` parameters after your +first call `start` you can call `setExtraParameter` on macOS or call `start` +again with the new/updated `extra` parameters on Linux and Windows. + ```js const args = [ `--reporter-url=${submitURL}`, @@ -95,14 +99,14 @@ Returns [`CrashReport[]`](structures/crash-report.md): Returns all uploaded crash reports. Each report contains the date and uploaded ID. -### `crashReporter.getUploadToServer()` _macOS_ +### `crashReporter.getUploadToServer()` _Linux_ _macOS_ Returns `Boolean` - Whether reports should be submitted to the server. Set through the `start` method or `setUploadToServer`. **Note:** This API can only be called from the main process. -### `crashReporter.setUploadToServer(uploadToServer)` _macOS_ +### `crashReporter.setUploadToServer(uploadToServer)` _Linux_ _macOS_ * `uploadToServer` Boolean _macOS_ - Whether reports should be submitted to the server @@ -111,6 +115,18 @@ called before `start` is called. **Note:** This API can only be called from the main process. +### `crashReporter.setExtraParameter(key, value)` _macOS_ + +* `key` String - Parameter key. +* `value` String - Parameter value. Specifying `null` or `undefined` will + remove the key from the extra parameters. + +Set an extra parameter to set be sent with the crash report. The values +specified here will be sent in addition to any values set via the `extra` option +when `start` was called. This API is only available on macOS, if you need to +add/update extra parameters on Linux and Windows after your first call to +`start` you can call `start` again with the updated `extra` options. + ## Crash Report Payload The crash reporter will send the following data to the `submitURL` as diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 85755dc03e..00542f402f 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 00a7e92ef1..68cfa7fcea 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -38,19 +38,16 @@ The `dialog` module has the following methods: * `openDirectory` - Allow directories to be selected. * `multiSelections` - Allow multiple paths to be selected. * `showHiddenFiles` - Show hidden files in dialog. - * `createDirectory` _macOS_ - Allow creating new directories from dialog. - * `promptToCreate` _Windows_ - Prompt for creation if the file path entered + * `createDirectory` - Allow creating new directories from dialog. _macOS_ + * `promptToCreate` - Prompt for creation if the file path entered in the dialog does not exist. This does not actually create the file at the path but allows non-existent paths to be returned that should be - created by the application. - * `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. + created by the application. _Windows_ + * `noResolveAliases` - Disable the automatic alias (symlink) path + resolution. Selected aliases will now return the alias path instead of + their target path. _macOS_ + * `message` String (optional) _macOS_ - Message to display above input + boxes. * `callback` Function (optional) * `filePaths` String[] - An array of file paths chosen by the user @@ -94,6 +91,11 @@ shown. * `buttonLabel` String (optional) - Custom label for the confirmation button, when left empty the default label will be used. * `filters` [FileFilter[]](structures/file-filter.md) (optional) + * `message` String (optional) _macOS_ - Message to display above text fields. + * `nameFieldLabel` String (optional) _macOS_ - Custom label for the text + displayed in front of the filename text field. + * `showsTagField` Boolean (optional) _macOS_ - Show the tags input box, + defaults to `true`. * `callback` Function (optional) * `filename` String @@ -113,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 @@ -122,19 +125,33 @@ will be passed via `callback(filename)` * `title` String (optional) - Title of the message box, some platforms will not show it. * `message` String - Content of the message box. * `detail` String (optional) - Extra information of the message. + * `checkboxLabel` String (optional) - If provided, the message box will + include a checkbox with the given label. The checkbox state can be + inspected only when using `callback`. + * `checkboxChecked` Boolean (optional) - Initial checked state of the + checkbox. `false` by default. * `icon` [NativeImage](native-image.md) (optional) - * `cancelId` Integer (optional) - The value will be returned when user cancels the dialog - instead of clicking the buttons of the dialog. By default it is the index - of the buttons that have "cancel" or "no" as label, or 0 if there is no such - buttons. On macOS and Windows the index of the "Cancel" button will always - be used as `cancelId` even if it is specified. + * `cancelId` Integer (optional) - The index of the button to be used to cancel the dialog, via + the `Esc` key. By default this is assigned to the first button with "cancel" or "no" as the + label. If no such labeled buttons exist and this option is not set, `0` will be used as the + return value or callback response. This option is ignored on Windows. * `noLink` Boolean (optional) - On Windows Electron will try to figure out which one of the `buttons` are common buttons (like "Cancel" or "Yes"), and show the 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 + `checkboxLabel` was set. Otherwise `false`. Returns `Integer`, the index of the clicked button, if a callback is provided it returns undefined. @@ -159,6 +176,26 @@ 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_ _Windows_ + +* `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 + +On macOS, this displays a modal dialog that shows a message and certificate +information, and gives the user the option of trusting/importing the +certificate. If you provide a `browserWindow` argument the dialog will be +attached to the parent window, making it modal. + +On Windows the options are more limited, due to the Win32 APIs used: + + - The `message` argument is not used, as the OS provides its own confirmation + dialog. + - The `browserWindow` argument is ignored since it is not possible to make + this confirmation dialog 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 6603b619aa..c07bf98a86 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 3ef5cdc93e..4f091ec6c3 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/ipc-main.md b/docs/api/ipc-main.md index 57c57b5a4e..f6e24e9f63 100644 --- a/docs/api/ipc-main.md +++ b/docs/api/ipc-main.md @@ -16,8 +16,8 @@ It is also possible to send messages from the main process to the renderer process, see [webContents.send][web-contents-send] for more information. * When sending a message, the event name is the `channel`. -* To reply a synchronous message, you need to set `event.returnValue`. -* To send an asynchronous back to the sender, you can use +* To reply to a synchronous message, you need to set `event.returnValue`. +* To send an asynchronous message back to the sender, you can use `event.sender.send(...)`. An example of sending and handling messages between the render and main diff --git a/docs/api/locales.md b/docs/api/locales.md index e8af957b26..a45fdbcbe5 100644 --- a/docs/api/locales.md +++ b/docs/api/locales.md @@ -8,132 +8,135 @@ values are listed below: | Language Code | Language Name | |---------------|---------------| | af | Afrikaans | -| an | Aragonese | -| ar-AE | Arabic (U.A.E.) | -| ar-IQ | Arabic (Iraq) | -| ar | Arabic (Standard) | -| ar-BH | Arabic (Bahrain) | -| ar-DZ | Arabic (Algeria) | -| ar-EG | Arabic (Egypt) | -| ar-JO | Arabic (Jordan) | -| ar-KW | Arabic (Kuwait) | -| ar-LB | Arabic (Lebanon) | -| ar-LY | Arabic (Libya) | -| ar-MA | Arabic (Morocco) | -| ar-OM | Arabic (Oman) | -| ar-QA | Arabic (Qatar) | -| ar-SA | Arabic (Saudi Arabia) | -| ar-SY | Arabic (Syria) | -| ar-TN | Arabic (Tunisia) | -| ar-YE | Arabic (Yemen) | -| as | Assamese | -| ast | Asturian | +| am | Amharic | +| ar | Arabic | | az | Azerbaijani | | be | Belarusian | | bg | Bulgarian | -| bg | Bulgarian | +| bh | Bihari | | bn | Bengali | | br | Breton | | bs | Bosnian | | ca | Catalan | -| ce | Chechen | -| ch | Chamorro | | co | Corsican | -| cr | Cree | | cs | Czech | -| cv | Chuvash | +| cy | Welsh | | da | Danish | -| de | German (Standard) | +| de | German | | de-AT | German (Austria) | | de-CH | German (Switzerland) | | de-DE | German (Germany) | -| de-LI | German (Liechtenstein) | -| de-LU | German (Luxembourg) | | el | Greek | -| en-AU | English (Australia) | -| en-BZ | English (Belize) | | en | English | +| en-AU | English (Australia) | | en-CA | English (Canada) | -| en-GB | English (United Kingdom) | -| en-IE | English (Ireland) | -| en-JM | English (Jamaica) | +| en-GB | English (UK) | | en-NZ | English (New Zealand) | -| en-PH | English (Philippines) | -| en-TT | English (Trinidad & Tobago) | -| en-US | English (United States) | +| en-US | English (US) | | en-ZA | English (South Africa) | -| en-ZW | English (Zimbabwe) | | eo | Esperanto | +| es | Spanish | +| es-419 | Spanish (Latin America) | | et | Estonian | | eu | Basque | | fa | Persian | -| fa | Farsi | -| fa-IR | Persian/Iran | | fi | Finnish | -| fj | Fijian | -| fo | Faeroese | +| fil | Filipino | +| fo | Faroese | +| fr | French | +| fr-CA | French (Canada) | | fr-CH | French (Switzerland) | | fr-FR | French (France) | -| fr-LU | French (Luxembourg) | -| fr-MC | French (Monaco) | -| fr | French (Standard) | -| fr-BE | French (Belgium) | -| fr-CA | French (Canada) | -| fur | Friulian | | fy | Frisian | | ga | Irish | -| gd-IE | Gaelic (Irish) | -| gd | Gaelic (Scots) | -| gl | Galacian | -| gu | Gujurati | +| gd | Scots Gaelic | +| gl | Galician | +| gn | Guarani | +| gu | Gujarati | +| ha | Hausa | +| haw | Hawaiian | | he | Hebrew | | hi | Hindi | | hr | Croatian | -| ht | Haitian | | hu | Hungarian | | hy | Armenian | +| ia | Interlingua | | id | Indonesian | | is | Icelandic | +| it | Italian | | it-CH | Italian (Switzerland) | -| it | Italian (Standard) | -| iu | Inuktitut | +| it-IT | Italian (Italy) | | ja | Japanese | +| jw | Javanese | | ka | Georgian | | kk | Kazakh | -| km | Khmer | +| km | Cambodian | | kn | Kannada | | ko | Korean | -| ko-KP | Korean (North Korea) | -| ko-KR | Korean (South Korea) | -| ks | Kashmiri | -| ky | Kirghiz | +| ku | Kurdish | +| ky | Kyrgyz | | la | Latin | -| lb | Luxembourgish | +| ln | Lingala | +| lo | Laothian | | lt | Lithuanian | | lv | Latvian | -| mi | Maori | -| mk | FYRO Macedonian | +| mk | Macedonian | | ml | Malayalam | +| mn | Mongolian | | mo | Moldavian | | mr | Marathi | | ms | Malay | | mt | Maltese | -| my | Burmese | | nb | Norwegian (Bokmal) | | ne | Nepali | -| ng | Ndonga | -| nl | Dutch (Standard) | -| nl-BE | Dutch (Belgian) | +| nl | Dutch | | nn | Norwegian (Nynorsk) | | no | Norwegian | -| nv | Navajo | | oc | Occitan | | om | Oromo | | or | Oriya | +| pa | Punjabi | +| pl | Polish | +| ps | Pashto | +| pt | Portuguese | +| pt-BR | Portuguese (Brazil) | +| pt-PT | Portuguese (Portugal) | +| qu | Quechua | +| rm | Romansh | +| ro | Romanian | +| ru | Russian | +| sd | Sindhi | +| sh | Serbo-Croatian | +| si | Sinhalese | +| sk | Slovak | +| sl | Slovenian | +| sn | Shona | +| so | Somali | | sq | Albanian | -| tlh | Klingon | -| zh-TW | Chinese (Taiwan) | +| sr | Serbian | +| st | Sesotho | +| su | Sundanese | +| sv | Swedish | +| sw | Swahili | +| ta | Tamil | +| te | Telugu | +| tg | Tajik | +| th | Thai | +| ti | Tigrinya | +| tk | Turkmen | +| to | Tonga | +| tr | Turkish | +| tt | Tatar | +| tw | Twi | +| ug | Uighur | +| uk | Ukrainian | +| ur | Urdu | +| uz | Uzbek | +| vi | Vietnamese | +| xh | Xhosa | +| yi | Yiddish | +| yo | Yoruba | | zh | Chinese | -| zh-CN | Chinese (PRC) | -| zh-HK | Chinese (Hong Kong) | -| zh-SG | Chinese (Singapore) | +| zh-CN | Chinese (Simplified) | +| zh-TW | Chinese (Traditional) | +| zu | Zulu | diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 1c12d9fdac..a7486f3003 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 b9bdc36064..6a30d49b1d 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. @@ -52,17 +59,28 @@ will become properties of the constructed menu items. The `menu` object has the following instance methods: -#### `menu.popup([browserWindow, x, y, positioningItem])` +#### `menu.popup([browserWindow, options])` -* `browserWindow` BrowserWindow (optional) - Default is `BrowserWindow.getFocusedWindow()`. -* `x` Number (optional) - Default is the current mouse cursor position. -* `y` Number (**required** if `x` is used) - Default is the current mouse cursor position. -* `positioningItem` Number (optional) _macOS_ - The index of the menu item to - be positioned under the mouse cursor at the specified coordinates. Default is - -1. +* `browserWindow` BrowserWindow (optional) - Default is the focused window. +* `options` Object (optional) + * `x` Number (optional) - Default is the current mouse cursor position. + * `y` Number (**required** if `x` is used) - Default is the current mouse + cursor position. + * `async` Boolean (optional) - Set to `true` to have this method return + immediately called, `false` to return after the menu has been selected + or closed. Defaults to `false`. + * `positioningItem` Number (optional) _macOS_ - The index of the menu item to + be positioned under the mouse cursor at the specified coordinates. Default + is -1. Pops up this menu as a context menu in the `browserWindow`. +#### `menu.closePopup([browserWindow])` + +* `browserWindow` BrowserWindow (optional) - Default is the focused window. + +Closes the context menu in the `browserWindow`. + #### `menu.append(menuItem)` * `menuItem` MenuItem @@ -104,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'} ] }, { @@ -181,7 +159,7 @@ const template = [ submenu: [ { label: 'Learn More', - click () { require('electron').shell.openExternal('http://electron.atom.io') } + click () { require('electron').shell.openExternal('https://electron.atom.io') } } ] } @@ -191,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'} ] } @@ -302,7 +241,7 @@ Linux. Here are some notes on making your app's menu more native-like. On macOS there are many system-defined standard menus, like the `Services` and `Windows` menus. To make your menu a standard menu, you should set your menu's -`role` to one of following and Electron will recognize them and make them +`role` to one of the following and Electron will recognize them and make them become standard menus: * `window` diff --git a/docs/api/native-image.md b/docs/api/native-image.md index cc910cbb8a..da496436b3 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -165,7 +165,10 @@ Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer The following methods are available on instances of the `NativeImage` class: -#### `image.toPNG()` +#### `image.toPNG([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `Buffer` - A [Buffer][buffer] that contains the image's `PNG` encoded data. @@ -175,16 +178,25 @@ Returns `Buffer` - A [Buffer][buffer] that contains the image's `PNG` encoded da Returns `Buffer` - A [Buffer][buffer] that contains the image's `JPEG` encoded data. -#### `image.toBitmap()` +#### `image.toBitmap([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `Buffer` - A [Buffer][buffer] that contains a copy of the image's raw bitmap pixel data. -#### `image.toDataURL()` +#### `image.toDataURL([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `String` - The data URL of the image. -#### `image.getBitmap()` +#### `image.getBitmap([options])` + +* `options` Object (optional) + * `scaleFactor` Double (optional) - Defaults to 1.0. Returns `Buffer` - A [Buffer][buffer] that contains the image's raw bitmap pixel data. @@ -207,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)` @@ -224,19 +233,15 @@ 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. #### `image.resize(options)` * `options` Object - * `width` Integer (optional) - * `height` Integer (optional) + * `width` Integer (optional) - Defaults to the image's width. + * `height` Integer (optional) - Defaults to the image's height * `quality` String (optional) - The desired quality of the resize image. Possible values are `good`, `better` or `best`. The default is `best`. These values express a desired quality/speed tradeoff. They are translated @@ -253,4 +258,20 @@ will be preserved in the resized image. Returns `Float` - The image's aspect ratio. +#### `image.addRepresentation(options)` + +* `options` Object + * `scaleFactor` Double - The scale factor to add the image representation for. + * `width` Integer (optional) - Defaults to 0. Required if a bitmap buffer + is specified as `buffer`. + * `height` Integer (optional) - Defaults to 0. Required if a bitmap buffer + is specified as `buffer`. + * `buffer` Buffer (optional) - The buffer containing the raw image data. + * `dataURL` String (optional) - The data URL containing either a base 64 + encoded PNG or JPEG image. + +Add an image representation for a specific scale factor. This can be used +to explicitly add different scale factor representations to an image. This +can be called on empty images. + [buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer diff --git a/docs/api/process.md b/docs/api/process.md index 951abc7df1..59f80963d8 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 @@ -116,3 +116,15 @@ Returns `Object`: Returns an object giving memory usage statistics about the entire system. Note that all statistics are reported in Kilobytes. + +### `process.getCPUUsage()` + +Returns: + +* `CPUUsage` [CPUUsage](structures/cpu-usage.md) + +### `process.getIOCounters()` _Windows_ _Linux_ + +Returns: + +* `IOCounters` [IOCounters](structures/io-counters.md) \ No newline at end of file diff --git a/docs/api/remote.md b/docs/api/remote.md index 0bed3ad9b5..2abb2a8431 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 0000000000..9598e47257 --- /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 9704f88134..49f0d9f55c 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 58de08d39a..7f24937295 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -98,7 +98,7 @@ The following methods are available on instances of `Session`: * `callback` Function * `size` Integer - Cache size used in bytes. -Returns the session's current cache size. +Callback is invoked with the session's current cache size. #### `ses.clearCache(callback)` @@ -204,7 +204,7 @@ The `proxyBypassRules` is a comma separated list of rules described below: * `url` URL * `callback` Function - * `proxy` Object + * `proxy` String Resolves the proxy information for `url`. The `callback` will be called with `callback(proxy)` when the request is performed. @@ -250,15 +250,22 @@ the original network configuration. #### `ses.setCertificateVerifyProc(proc)` * `proc` Function - * `hostname` String - * `certificate` [Certificate](structures/certificate.md) + * `request` Object + * `hostname` String + * `certificate` [Certificate](structures/certificate.md) + * `error` String - Verification result from chromium. * `callback` Function - * `isTrusted` Boolean - Determines if the certificate should be trusted + * `verificationResult` Integer - Value can be one of certificate error codes + from [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). + Apart from the certificate error codes, the following special codes can be used. + * `0` - Indicates success and disables Certificate Transperancy verification. + * `-2` - Indicates failure. + * `-3` - Uses the verification result from chromium. Sets the certificate verify proc for `session`, the `proc` will be called with -`proc(hostname, certificate, callback)` whenever a server certificate -verification is requested. Calling `callback(true)` accepts the certificate, -calling `callback(false)` rejects it. +`proc(request, callback)` whenever a server certificate +verification is requested. Calling `callback(0)` accepts the certificate, +calling `callback(-2)` rejects it. Calling `setCertificateVerifyProc(null)` will revert back to default certificate verify proc. @@ -267,15 +274,20 @@ verify proc. const {BrowserWindow} = require('electron') let win = new BrowserWindow() -win.webContents.session.setCertificateVerifyProc((hostname, cert, callback) => { - callback(hostname === 'github.com') +win.webContents.session.setCertificateVerifyProc((request, callback) => { + const {hostname} = request + if (hostname === 'github.com') { + callback(0) + } else { + callback(-2) + } }) ``` #### `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 @@ -376,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/cpu-usage.md b/docs/api/structures/cpu-usage.md new file mode 100644 index 0000000000..b700d4945f --- /dev/null +++ b/docs/api/structures/cpu-usage.md @@ -0,0 +1,6 @@ +# CPUUsage Object + +* `percentCPUUsage` Number - Percentage of CPU used since the last call to getCPUUsage. + First call returns 0. +* `idleWakeupsPerSecond` Number - The number of average idle cpu wakeups per second + since the last call to getCPUUsage. First call returns 0. diff --git a/docs/api/structures/display.md b/docs/api/structures/display.md index d702b35a1b..f5f5b9866b 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/io-counters.md b/docs/api/structures/io-counters.md new file mode 100644 index 0000000000..62ad39a90b --- /dev/null +++ b/docs/api/structures/io-counters.md @@ -0,0 +1,8 @@ +# IOCounters Object + +* `readOperationCount` Number - The number of I/O read operations. +* `writeOperationCount` Number - The number of I/O write operations. +* `otherOperationCount` Number - Then number of I/O other operations. +* `readTransferCount` Number - The number of I/O read transfers. +* `writeTransferCount` Number - The number of I/O write transfers. +* `otherTransferCount` Number - Then number of I/O other transfers. diff --git a/docs/api/structures/memory-info.md b/docs/api/structures/memory-info.md new file mode 100644 index 0000000000..69c67f16cc --- /dev/null +++ b/docs/api/structures/memory-info.md @@ -0,0 +1,12 @@ +# MemoryInfo Object + +* `workingSetSize` Integer - Process id of the process. +* `workingSetSize` Integer - The amount of memory currently pinned to actual physical RAM. +* `peakWorkingSetSize` Integer - The maximum amount of memory that has ever been pinned + to actual physical RAM. +* `privateBytes` Integer - The amount of memory not shared by other processes, such as + JS heap or HTML content. +* `sharedBytes` Integer - The amount of memory shared between processes, typically + memory consumed by the Electron code itself + +Note that all statistics are reported in Kilobytes. \ No newline at end of file diff --git a/docs/api/structures/mime-typed-buffer.md b/docs/api/structures/mime-typed-buffer.md index dc1a20d28f..08e5cd47a4 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 0000000000..69b87cbdf9 --- /dev/null +++ b/docs/api/structures/point.md @@ -0,0 +1,4 @@ +# Point Object + +* `x` Number +* `y` Number diff --git a/docs/api/structures/process-memory-info.md b/docs/api/structures/process-memory-info.md new file mode 100644 index 0000000000..68198f2d45 --- /dev/null +++ b/docs/api/structures/process-memory-info.md @@ -0,0 +1,4 @@ +# ProcessMemoryInfo Object + +* `pid` Integer - Process id of the process. +* `memory` [MemoryInfo](memory-info.md) - Memory information of the process. diff --git a/docs/api/structures/scrubber-item.md b/docs/api/structures/scrubber-item.md new file mode 100644 index 0000000000..0dd3c4ff0f --- /dev/null +++ b/docs/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/api/structures/segmented-control-segment.md b/docs/api/structures/segmented-control-segment.md new file mode 100644 index 0000000000..ae01a07f32 --- /dev/null +++ b/docs/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/api/structures/size.md b/docs/api/structures/size.md new file mode 100644 index 0000000000..1d9c8b1f5a --- /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 new file mode 100644 index 0000000000..456fc207fe --- /dev/null +++ b/docs/api/touch-bar-button.md @@ -0,0 +1,34 @@ +## Class: TouchBarButton + +> Create a button in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarButton(options)` _Experimental_ + +* `options` Object + * `label` String (optional) - Button text. + * `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 + +The following properties are available on instances of `TouchBarButton`: + +#### `touchBarButton.label` + +A `String` representing the button's current text. Changing this value immediately updates the button +in the touch bar. + +#### `touchBarButton.backgroundColor` + +A `String` hex code representing the button's current background color. Changing this value immediately updates +the button in the touch bar. + +#### `touchBarButton.icon` + +A `NativeImage` representing the button's current icon. Changing this value immediately updates the button +in the touch bar. diff --git a/docs/api/touch-bar-color-picker.md b/docs/api/touch-bar-color-picker.md new file mode 100644 index 0000000000..defc95c5da --- /dev/null +++ b/docs/api/touch-bar-color-picker.md @@ -0,0 +1,29 @@ +## Class: TouchBarColorPicker + +> Create a color picker in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarColorPicker(options)` _Experimental_ + +* `options` Object + * `availableColors` String[] (optional) - Array of hex color strings to + appear as possible colors to select. + * `selectedColor` String (optional) - The selected hex color in the picker, + i.e `#ABCDEF`. + * `change` Function (optional) - Function to call when a color is selected. + * `color` String - The color that the user selected from the picker + +### Instance Properties + +The following properties are available on instances of `TouchBarColorPicker`: + +#### `touchBarColorPicker.availableColors` + +A `String[]` array representing the color picker's available colors to select. Changing this value immediately +updates the color picker in the touch bar. + +#### `touchBarColorPicker.selectedColor` + +A `String` hex code representing the color picker's currently selected color. Changing this value immediately +updates the color picker in the touch bar. diff --git a/docs/api/touch-bar-group.md b/docs/api/touch-bar-group.md new file mode 100644 index 0000000000..a22f5960a3 --- /dev/null +++ b/docs/api/touch-bar-group.md @@ -0,0 +1,10 @@ +## Class: TouchBarGroup + +> Create a group in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarGroup(options)` _Experimental_ + +* `options` Object + * `items` [TouchBar](touch-bar.md) - Items to display as a group. diff --git a/docs/api/touch-bar-label.md b/docs/api/touch-bar-label.md new file mode 100644 index 0000000000..82b25d3dfd --- /dev/null +++ b/docs/api/touch-bar-label.md @@ -0,0 +1,25 @@ +## Class: TouchBarLabel + +> Create a label in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarLabel(options)` _Experimental_ + +* `options` Object + * `label` String (optional) - Text to display. + * `textColor` String (optional) - Hex color of text, i.e `#ABCDEF`. + +### Instance Properties + +The following properties are available on instances of `TouchBarLabel`: + +#### `touchBarLabel.label` + +A `String` representing the label's current text. Changing this value immediately updates the label in +the touch bar. + +#### `touchBarLabel.textColor` + +A `String` hex code representing the label's current text color. Changing this value immediately updates the +label in the touch bar. diff --git a/docs/api/touch-bar-popover.md b/docs/api/touch-bar-popover.md new file mode 100644 index 0000000000..af43d38bf7 --- /dev/null +++ b/docs/api/touch-bar-popover.md @@ -0,0 +1,28 @@ +## Class: TouchBarPopover + +> Create a popover in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarPopover(options)` _Experimental_ + +* `options` Object + * `label` String (optional) - Popover button text. + * `icon` [NativeImage](native-image.md) (optional) - Popover button icon. + * `items` [TouchBar](touch-bar.md) (optional) - Items to display in the popover. + * `showCloseButton` Boolean (optional) - `true` to display a close button + on the left of the popover, `false` to not show it. Default is `true`. + +### Instance Properties + +The following properties are available on instances of `TouchBarPopover`: + +#### `touchBarPopover.label` + +A `String` representing the popover's current button text. Changing this value immediately updates the +popover in the touch bar. + +#### `touchBarPopover.icon` + +A `NativeImage` representing the popover's current button icon. Changing this value immediately updates the +popover in the touch bar. diff --git a/docs/api/touch-bar-scrubber.md b/docs/api/touch-bar-scrubber.md new file mode 100644 index 0000000000..31f65d8770 --- /dev/null +++ b/docs/api/touch-bar-scrubber.md @@ -0,0 +1,65 @@ +## Class: TouchBarScrubber + +> Create a scrubber (a scrollable selector) + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarScrubber(options)` _Experimental_ + +* `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` Integer - The index of the item the user selected + * `highlight` Function - Called when the user taps any item + * `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`. + * `mode` String - Defaults to `free`. + * `continuous` Boolean - Defaults to `true`. + +### Instance Properties + +The following properties are available on instances of `TouchBarScrubber`: + +#### `touchBarSegmentedControl.items` + +A `ScrubberItem[]` array representing the items in this scrubber. Updating this value immediately +updates the control in the touch bar. Updating deep properties inside this array **does not update the touch bar**. + +#### `touchBarSegmentedControl.selectedStyle` + +A `String` representing the style that selected items in the scrubber should have. Updating this value immediately +updates the control in the touch bar. Possible values: + +* `background` - Maps to `[NSScrubberSelectionStyle roundedBackgroundStyle]` +* `outline` - Maps to `[NSScrubberSelectionStyle outlineOverlayStyle]` +* `null` - Actually null, not a string, removes all styles + +#### `touchBarSegmentedControl.overlayStyle` + +A `String` representing the style that selected items in the scrubber should have. This style is overlayed on top +of the scrubber item instead of being placed behind it. Updating this value immediately updates the control in the +touch bar. Possible values: + +* `background` - Maps to `[NSScrubberSelectionStyle roundedBackgroundStyle]` +* `outline` - Maps to `[NSScrubberSelectionStyle outlineOverlayStyle]` +* `null` - Actually null, not a string, removes all styles + +#### `touchBarSegmentedControl.showArrowButtons` + +A `Boolean` representing whether to show the left / right selection arrows in this scrubber. Updating this value +immediately updates the control in the touch bar. + +#### `touchBarSegmentedControl.mode` + +A `String` representing the mode of this scrubber. Updating this value immediately +updates the control in the touch bar. Possible values: + +* `fixed` - Maps to `NSScrubberModeFixed` +* `free` - Maps to `NSScrubberModeFree` + +#### `touchBarSegmentedControl.continuous` + +A `Boolean` representing whether this scrubber is continuous or not. Updating this value immediately +updates the control in the touch bar. diff --git a/docs/api/touch-bar-segmented-control.md b/docs/api/touch-bar-segmented-control.md new file mode 100644 index 0000000000..986fee4929 --- /dev/null +++ b/docs/api/touch-bar-segmented-control.md @@ -0,0 +1,51 @@ +## Class: TouchBarSegmentedControl + +> Create a segmented control (a button group) where one button has a selected state + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarSegmentedControl(options)` _Experimental_ + +* `options` Object + * `segmentStyle` String - (Optional) Style of the segments: + * `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. + * `mode` String - (Optional) The selection mode of the control: + * `single` - Default. One item selected at a time, selecting one deselects the previously selected item. + * `multiple` - Multiple items can be selected at a time. + * `buttons` - Make the segments act as buttons, each segment can be pressed and released but never marked as active. + * `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. When the mode is multiple it will be the last selected item. + * `change` Function - Called when the user selects a new segment + * `selectedIndex` Integer - The index of the segment the user selected. + * `isSelected` Boolean - Whether as a result of user selection the segment is selected or not. + +### Instance Properties + +The following properties are available on instances of `TouchBarSegmentedControl`: + +#### `touchBarSegmentedControl.segmentStyle` + +A `String` representing the controls current segment style. Updating this value immediately updates the control +in the touch bar. + +#### `touchBarSegmentedControl.segments` + +A `SegmentedControlSegment[]` array representing the segments in this control. Updating this value immediately +updates the control in the touch bar. Updating deep properties inside this array **does not update the touch bar**. + +#### `touchBarSegmentedControl.selectedIndex` + +An `Integer` representing the currently selected segment. Changing this value immediately updates the control +in the touch bar. User interaction with the touch bar will update this value automatically. diff --git a/docs/api/touch-bar-slider.md b/docs/api/touch-bar-slider.md new file mode 100644 index 0000000000..600a777304 --- /dev/null +++ b/docs/api/touch-bar-slider.md @@ -0,0 +1,39 @@ +## Class: TouchBarSlider + +> Create a slider in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarSlider(options)` _Experimental_ + +* `options` Object + * `label` String (optional) - Label text. + * `value` Integer (optional) - Selected value. + * `minValue` Integer (optional) - Minimum value. + * `maxValue` Integer (optional) - Maximum value. + * `change` Function (optional) - Function to call when the slider is changed. + * `newValue` Number - The value that the user selected on the Slider + +### Instance Properties + +The following properties are available on instances of `TouchBarSlider`: + +#### `touchBarSlider.label` + +A `String` representing the slider's current text. Changing this value immediately updates the slider +in the touch bar. + +#### `touchBarSlider.value` + +A `Number` representing the slider's current value. Changing this value immediately updates the slider +in the touch bar. + +#### `touchBarSlider.minValue` + +A `Number` representing the slider's current minimum value. Changing this value immediately updates the +slider in the touch bar. + +#### `touchBarSlider.maxValue` + +A `Number` representing the slider's current maximum value. Changing this value immediately updates the +slider in the touch bar. diff --git a/docs/api/touch-bar-spacer.md b/docs/api/touch-bar-spacer.md new file mode 100644 index 0000000000..e722f8a77c --- /dev/null +++ b/docs/api/touch-bar-spacer.md @@ -0,0 +1,13 @@ +## Class: TouchBarSpacer + +> Create a spacer between two items in the touch bar for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBarSpacer(options)` _Experimental_ + +* `options` Object + * `size` String (optional) - Size of spacer, possible values are: + * `small` - Small space between items. + * `large` - Large space between items. + * `flexible` - Take up all available space. diff --git a/docs/api/touch-bar.md b/docs/api/touch-bar.md new file mode 100644 index 0000000000..8f1b8732f8 --- /dev/null +++ b/docs/api/touch-bar.md @@ -0,0 +1,150 @@ +## Class: TouchBar + +> Create TouchBar layouts for native macOS applications + +Process: [Main](../tutorial/quick-start.md#main-process) + +### `new TouchBar(options)` _Experimental_ + +* `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. + +**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 +and some labels. + +```javascript +const {app, BrowserWindow, TouchBar} = require('electron') + +const {TouchBarLabel, TouchBarButton, TouchBarSpacer} = TouchBar + +let spinning = false + +// Reel labels +const reel1 = new TouchBarLabel() +const reel2 = new TouchBarLabel() +const reel3 = new TouchBarLabel() + +// Spin result label +const result = new TouchBarLabel() + +// Spin button +const spin = new TouchBarButton({ + label: '🎰 Spin', + backgroundColor: '#7851A9', + click: () => { + // Ignore clicks if already spinning + if (spinning) { + return + } + + spinning = true + result.label = '' + + let timeout = 10 + const spinLength = 4 * 1000 // 4 seconds + const startTime = Date.now() + + const spinReels = () => { + updateReels() + + if ((Date.now() - startTime) >= spinLength) { + finishSpin() + } else { + // Slow down a bit on each spin + timeout *= 1.1 + setTimeout(spinReels, timeout) + } + } + + spinReels() + } +}) + +const getRandomValue = () => { + const values = ['🍒', '💎', '7️⃣', '🍊', '🔔', '⭐', '🍇', '🍀'] + return values[Math.floor(Math.random() * values.length)] +} + +const updateReels = () => { + reel1.label = getRandomValue() + reel2.label = getRandomValue() + reel3.label = getRandomValue() +} + +const finishSpin = () => { + const uniqueValues = new Set([reel1.label, reel2.label, reel3.label]).size + if (uniqueValues === 1) { + // All 3 values are the same + result.label = '💰 Jackpot!' + result.textColor = '#FDFF00' + } else if (uniqueValues === 2) { + // 2 values are the same + result.label = '😍 Winner!' + result.textColor = '#FDFF00' + } else { + // No values are the same + result.label = '🙁 Spin Again' + result.textColor = null + } + spinning = false +} + +const touchBar = new TouchBar([ + spin, + new TouchBarSpacer({size: 'large'}), + reel1, + new TouchBarSpacer({size: 'small'}), + reel2, + new TouchBarSpacer({size: 'small'}), + reel3, + new TouchBarSpacer({size: 'large'}), + result +]) + +let window + +app.once('ready', () => { + window = new BrowserWindow({ + frame: false, + titleBarStyle: 'hidden-inset', + width: 200, + height: 200, + backgroundColor: '#000' + }) + window.loadURL('about:blank') + 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 0be9702986..141b24bbbf 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 9c2893b9eb..5304ec5815 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -157,9 +157,20 @@ requested by `window.open` or an external link like `
`. By default a new `BrowserWindow` will be created for the `url`. -Calling `event.preventDefault()` will prevent creating new windows. In such case, the -`event.newGuest` may be set with a reference to a `BrowserWindow` instance to make it -used by the Electron's runtime. +Calling `event.preventDefault()` will prevent Electron from automatically creating a +new `BrowserWindow`. If you call `event.preventDefault()` and manually create a new +`BrowserWindow` then you must set `event.newGuest` to reference the new `BrowserWindow` +instance, failing to do so may result in unexpected behavior. For example: + +```javascript +myBrowserWindow.webContents.on('new-window', (event, url) => { + event.preventDefault() + const win = new BrowserWindow({show: false}) + win.once('ready-to-show', () => win.show()) + win.loadURL(url) + event.newGuest = win +}) +``` #### Event: 'will-navigate' @@ -325,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. @@ -363,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`, @@ -502,6 +510,24 @@ win.loadURL('http://github.com') Emitted when the devtools window instructs the webContents to reload +#### 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. + ### Instance Methods #### `contents.loadURL(url[, options])` @@ -512,6 +538,7 @@ Emitted when the devtools window instructs the webContents to reload * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\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) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the window. The `url` must contain the protocol prefix, e.g. the `http://` or `file://`. If the load should bypass http cache then @@ -642,7 +669,7 @@ Injects CSS into the current web page. #### `contents.executeJavaScript(code[, userGesture, callback])` * `code` String -* `userGesture` Boolean (optional) +* `userGesture` Boolean (optional) - Default is `false`. * `callback` Function (optional) - Called after script has been executed. * `result` Any @@ -1072,24 +1099,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`) @@ -1169,7 +1188,7 @@ End subscribing for frame presentation events. #### `contents.startDrag(item)` * `item` Object - * `file` String - The path to the file being dragged. + * `file` String or `files` Array - The path(s) to the file(s) being dragged. * `icon` [NativeImage](native-image.md) - The image must be non-empty on macOS. @@ -1187,7 +1206,7 @@ the cursor when dragging. * `callback` Function - `(error) => {}`. * `error` Error -Returns true if the process of saving page has been initiated successfully. +Returns `Boolean` - true if the process of saving page has been initiated successfully. ```javascript const {BrowserWindow} = require('electron') @@ -1246,9 +1265,36 @@ Returns `Integer` - If *offscreen rendering* is enabled returns the current fram #### `contents.invalidate()` +Schedules a full repaint of the window this web contents is in. + If *offscreen rendering* is enabled invalidates the frame and generates a new one through the `'paint'` event. +#### `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. + ### Instance Properties #### `contents.id` diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 25ae3480f9..abe8e4d916 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 6da98d81f1..a271add9da 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 b0e1721ad2..1da5679c18 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -2,6 +2,8 @@ > Display external web content in an isolated frame and process. +Process: [Renderer](../tutorial/quick-start.md#renderer-process) + Use the `webview` tag to embed 'guest' content (such as web pages) in your Electron app. The guest content is contained within the `webview` container. An embedded page within your app controls how the guest content is laid out and @@ -10,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. @@ -177,7 +180,7 @@ Web security is enabled by default. ```html - + ``` Sets the session used by the page. If `partition` starts with `persist:`, the @@ -212,7 +215,7 @@ The full list of supported preference strings can be found in [BrowserWindow](br The string follows the same format as the features string in `window.open`. A name by itself is given a `true` boolean value. A preference can be set to another value by including an `=`, followed by the value. -Special values `yes` and `1` are interpreted as `true`, while `no` and `0` are interpreted as `false`. +Special values `yes` and `1` are interpreted as `true`, while `no` and `0` are interpreted as `false`. ### `blinkfeatures` @@ -222,7 +225,7 @@ Special values `yes` and `1` are interpreted as `true`, while `no` and `0` are i A list of strings which specifies the blink features to be enabled separated by `,`. The full list of supported feature strings can be found in the -[RuntimeEnabledFeatures.in][blink-feature-string] file. +[RuntimeEnabledFeatures.json5][blink-feature-string] file. ### `disableblinkfeatures` @@ -232,7 +235,7 @@ The full list of supported feature strings can be found in the A list of strings which specifies the blink features to be disabled separated by `,`. The full list of supported feature strings can be found in the -[RuntimeEnabledFeatures.in][blink-feature-string] file. +[RuntimeEnabledFeatures.json5][blink-feature-string] file. ### `guestinstance` @@ -310,6 +313,7 @@ webview.addEventListener('dom-ready', () => { * `userAgent` String (optional) - A user agent originating the request. * `extraHeaders` String (optional) - Extra headers separated by "\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) - Base url (with trailing path separator) for files to be loaded by the data url. This is needed only if the specified `url` is a data url and needs to load other files. Loads the `url` in the webview, the `url` must contain the protocol prefix, e.g. the `http://` or `file://`. @@ -535,20 +539,42 @@ Stops any `findInPage` request for the `webview` with the provided `action`. ### `.print([options])` +* `options` Object (optional) + * `silent` Boolean - Don't ask user for print settings. Default is `false`. + * `printBackground` Boolean - Also prints the background color and image of + the web page. Default is `false`. + Prints `webview`'s web page. Same as `webContents.print([options])`. ### `.printToPDF(options, callback)` +* `options` Object + * `marginsType` Integer - (optional) Specifies the type of margins to use. Uses 0 for + default margin, 1 for no margin, and 2 for minimum margin. + * `pageSize` String - (optional) Specify page size of the generated PDF. Can be `A3`, + `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` + and `width` in microns. + * `printBackground` Boolean - (optional) Whether to print CSS backgrounds. + * `printSelectionOnly` Boolean - (optional) Whether to print selection only. + * `landscape` Boolean - (optional) `true` for landscape, `false` for portrait. +* `callback` Function + * `error` Error + * `data` Buffer + Prints `webview`'s web page as PDF, Same as `webContents.printToPDF(options, callback)`. ### `.capturePage([rect, ]callback)` +* `rect` [Rectangle](structures/rectangle.md) (optional) - The area of the page to be captured +* `callback` Function + * `image` [NativeImage](native-image.md) + Captures a snapshot of the `webview`'s page. Same as `webContents.capturePage([rect, ]callback)`. ### `.send(channel[, arg1][, arg2][, ...])` * `channel` String -* `arg` (optional) +* `...args` any[] Send an asynchronous message to renderer process via `channel`, you can also send arbitrary arguments. The renderer process can handle the message by @@ -724,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. @@ -913,4 +940,4 @@ Emitted when DevTools is closed. Emitted when DevTools is focused / opened. -[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in +[blink-feature-string]: https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.json5?l=62 diff --git a/docs/api/window-open.md b/docs/api/window-open.md index 56216f551a..41332aa6cf 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-linux.md b/docs/development/build-instructions-linux.md index 42b7730a84..e6b5004bf2 100644 --- a/docs/development/build-instructions-linux.md +++ b/docs/development/build-instructions-linux.md @@ -117,6 +117,14 @@ To clean the build files: $ npm run clean ``` +To clean only `out` and `dist` directories: + +```bash +$ npm run clean-build +``` + +**Note:** Both clean commands require running `bootstrap` again before building. + ## Troubleshooting ### Error While Loading Shared Libraries: libtinfo.so.5 diff --git a/docs/development/build-instructions-osx.md b/docs/development/build-instructions-osx.md index 7b934cd55b..d0e95aaf18 100644 --- a/docs/development/build-instructions-osx.md +++ b/docs/development/build-instructions-osx.md @@ -85,6 +85,14 @@ To clean the build files: $ npm run clean ``` +To clean only `out` and `dist` directories: + +```bash +$ npm run clean-build +``` + +**Note:** Both clean commands require running `bootstrap` again before building. + ## Tests See [Build System Overview: Tests](build-system-overview.md#tests) diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 2146abad97..6bfd242576 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/) @@ -83,6 +86,14 @@ To clean the build files: $ npm run clean ``` +To clean only `out` and `dist` directories: + +```bash +$ npm run clean-build +``` + +**Note:** Both clean commands require running `bootstrap` again before building. + ## Tests See [Build System Overview: Tests](build-system-overview.md#tests) diff --git a/docs/development/chromium-development.md b/docs/development/chromium-development.md new file mode 100644 index 0000000000..23506ae918 --- /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/setting-up-symbol-server.md b/docs/development/setting-up-symbol-server.md index 098fd2a5df..100384d4d3 100644 --- a/docs/development/setting-up-symbol-server.md +++ b/docs/development/setting-up-symbol-server.md @@ -43,8 +43,8 @@ SRV*c:\code\symbols\*http://msdl.microsoft.com/download/symbols;SRV*c:\code\symb ## Using the symbol server in Visual Studio - - + + ## Troubleshooting: Symbols will not load diff --git a/docs/development/upgrading-chrome.md b/docs/development/upgrading-chrome.md index 5cb9337cd0..6e47a8b4f5 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 0000000000..76d13299ca --- /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 0079d43f4e..abeac23f76 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/glossary.md b/docs/glossary.md index 6019bddeee..b923037f9f 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -127,7 +127,7 @@ available in "core". ### V8 V8 is Google's open source JavaScript engine. It is written in C++ and is -used in Google Chrome, the open source browser from Google. V8 can run +used in Google Chrome. V8 can run standalone, or can be embedded into any C++ application. ### webview diff --git a/docs/tutorial/about.md b/docs/tutorial/about.md index a816c094b4..f812a810e9 100644 --- a/docs/tutorial/about.md +++ b/docs/tutorial/about.md @@ -1,10 +1,10 @@ # About Electron -[Electron](http://electron.atom.io) is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining [Chromium](https://www.chromium.org/Home) and [Node.js](https://nodejs.org) into a single runtime and apps can be packaged for Mac, Windows, and Linux. +[Electron](https://electron.atom.io) is an open source library developed by GitHub for building cross-platform desktop applications with HTML, CSS, and JavaScript. Electron accomplishes this by combining [Chromium](https://www.chromium.org/Home) and [Node.js](https://nodejs.org) into a single runtime and apps can be packaged for Mac, Windows, and Linux. Electron began in 2013 as the framework on which [Atom](https://atom.io), GitHub's hackable text editor, would be built. The two were open sourced in the Spring of 2014. -It has since become a popular tool used by open source developers, startups, and established companies. [See who is building on Electron](http://electron.atom.io/apps/). +It has since become a popular tool used by open source developers, startups, and established companies. [See who is building on Electron](https://electron.atom.io/apps/). Read on to learn more about the contributors and releases of Electron or get started building with Electron in the [Quick Start Guide](quick-start.md). @@ -27,13 +27,13 @@ In Electron, Node.js and Chromium share a single V8 instance—usually the versi ### Versioning -Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](http://electron.atom.io/docs/tutorial/electron-versioning/) or see the [versions currently in use](https://electron.atom.io/#electron-versions). +Due to the hard dependency on Node.js and Chromium, Electron is in a tricky versioning position and [does not follow `semver`](http://semver.org). You should therefore always reference a specific version of Electron. [Read more about Electron's versioning](https://electron.atom.io/docs/tutorial/electron-versioning/) or see the [versions currently in use](https://electron.atom.io/#electron-versions). ### LTS Long term support of older versions of Electron does not currently exist. If your current version of Electron works for you, you can stay on it for as long as you'd like. If you want to make use of new features as they come in you should upgrade to a newer version. -A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](http://electron.atom.io/blog/2016/05/11/electron-1-0). +A major update came with version `v1.0.0`. If you're not yet using this version, you should [read more about the `v1.0.0` changes](https://electron.atom.io/blog/2016/05/11/electron-1-0). ## Core Philosophy @@ -41,7 +41,7 @@ In order to keep Electron small (file size) and sustainable (the spread of depen For instance, Electron uses just the rendering library from Chromium rather than all of Chromium. This makes it easier to upgrade Chromium but also means some browser features found in Google Chrome do not exist in Electron. -New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](http://electron.atom.io/community). +New features added to Electron should primarily be native APIs. If a feature can be its own Node.js module, it probably should be. See the [Electron tools built by the community](https://electron.atom.io/community). ## History @@ -52,6 +52,6 @@ Below are milestones in Electron's history. | **April 2013**| [Atom Shell is started](https://github.com/electron/electron/commit/6ef8875b1e93787fa9759f602e7880f28e8e6b45).| | **May 2014** | [Atom Shell is open sourced](http://blog.atom.io/2014/05/06/atom-is-now-open-source.html). | | **April 2015** | [Atom Shell is re-named Electron](https://github.com/electron/electron/pull/1389). | -| **May 2016** | [Electron releases `v1.0.0`](http://electron.atom.io/blog/2016/05/11/electron-1-0).| -| **May 2016** | [Electron apps compatible with Mac App Store](http://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| -| **August 2016** | [Windows Store support for Electron apps](http://electron.atom.io/docs/tutorial/windows-store-guide).| +| **May 2016** | [Electron releases `v1.0.0`](https://electron.atom.io/blog/2016/05/11/electron-1-0).| +| **May 2016** | [Electron apps compatible with Mac App Store](https://electron.atom.io/docs/tutorial/mac-app-store-submission-guide).| +| **August 2016** | [Windows Store support for Electron apps](https://electron.atom.io/docs/tutorial/windows-store-guide).| diff --git a/docs/tutorial/accessibility.md b/docs/tutorial/accessibility.md index 22b9704bdb..d256ff5567 100644 --- a/docs/tutorial/accessibility.md +++ b/docs/tutorial/accessibility.md @@ -1,12 +1,12 @@ # Accessibility -Making accessible applications is important and we're happy to introduce new functionality to [Devtron](http://electron.atom.io/devtron) and [Spectron](http://electron.atom.io/spectron) that gives developers the opportunity to make their apps better for everyone. +Making accessible applications is important and we're happy to introduce new functionality to [Devtron](https://electron.atom.io/devtron) and [Spectron](https://electron.atom.io/spectron) that gives developers the opportunity to make their apps better for everyone. --- Accessibility concerns in Electron applications are similar to those of websites because they're both ultimately HTML. With Electron apps, however, you can't use the online resources for accessibility audits because your app doesn't have a URL to point the auditor to. -These new features bring those auditing tools to your Electron app. You can choose to add audits to your tests with Spectron or use them within DevTools with Devtron. Read on for a summary of the tools or checkout our [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) for more information. +These new features bring those auditing tools to your Electron app. You can choose to add audits to your tests with Spectron or use them within DevTools with Devtron. Read on for a summary of the tools or checkout our [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) for more information. ### Spectron @@ -30,4 +30,4 @@ In Devtron, there is a new accessibility tab which will allow you to audit a pag Both of these tools are using the [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools) library built by Google for Chrome. You can learn more about the accessibility audit rules this library uses on that [repository's wiki](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules). -If you know of other great accessibility tools for Electron, add them to the [accessibility documentation](http://electron.atom.io/docs/tutorial/accessibility) with a pull request. +If you know of other great accessibility tools for Electron, add them to the [accessibility documentation](https://electron.atom.io/docs/tutorial/accessibility) with a pull request. diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index cbe1021c58..96e56362b2 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -8,55 +8,9 @@ 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 (Windows, Linux, macOS) +## Notifications -All three operating systems provide means for applications to send notifications -to the user. Electron conveniently allows developers to send notifications with -the [HTML5 Notification API](https://notifications.spec.whatwg.org/), using -the currently running operating system's native notification APIs to display it. - -**Note:** Since this is an HTML5 API it is only available in the renderer process. - -```javascript -let myNotification = new Notification('Title', { - body: 'Lorem Ipsum Dolor Sit Amet' -}) - -myNotification.onclick = () => { - console.log('Notification clicked') -} -``` - -While code and user experience across operating systems are similar, there -are fine differences. - -### Windows - -* On Windows 10, notifications "just work". -* On Windows 8.1 and Windows 8, a shortcut to your app, with a [Application User -Model ID][app-user-model-id], must be installed to the Start screen. Note, -however, that it does not need to be pinned to the Start screen. -* On Windows 7, notifications are not supported. You can however send -"balloon notifications" using the [Tray API][tray-balloon]. - -Furthermore, the maximum length for the notification body is 250 characters, -with the Windows team recommending that notifications should be kept to 200 -characters. - -### Linux - -Notifications are sent using `libnotify`, it can show notifications on any -desktop environment that follows [Desktop Notifications -Specification][notification-spec], including Cinnamon, Enlightenment, Unity, -GNOME, KDE. - -### macOS - -Notifications are straight-forward on macOS, you should however be aware of -[Apple's Human Interface guidelines regarding notifications](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/NotificationCenter.html). - -Note that notifications are limited to 256 bytes in size - and will be truncated -if you exceed that limit. +See [Notifications](notifications.md) ## Recent documents (Windows & macOS) @@ -65,7 +19,7 @@ the application via JumpList or dock menu, respectively. __JumpList:__ -![JumpList Recent Files](http://i.msdn.microsoft.com/dynimg/IC420538.png) +![JumpList Recent Files](https://cloud.githubusercontent.com/assets/2289/23446924/11a27b98-fdfc-11e6-8485-cc3b1e86b80a.png) __Application dock menu:__ diff --git a/docs/tutorial/electron-versioning.md b/docs/tutorial/electron-versioning.md index cae99344a6..3f6c9ef90d 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/keyboard-shortcuts.md b/docs/tutorial/keyboard-shortcuts.md new file mode 100644 index 0000000000..9b20d23f6d --- /dev/null +++ b/docs/tutorial/keyboard-shortcuts.md @@ -0,0 +1,88 @@ +# Keyboard Shortcuts + +> Configure local and global keyboard shortcuts + +## Local Shortcuts + +You can use the [Menu] module to configure keyboard shortcuts that will +be triggered only when the app is focused. To do so, specify an +[`accelerator`] property when creating a [MenuItem]. + +```js +const {Menu, MenuItem} = require('electron') +const menu = new Menu() + +menu.append(new MenuItem({ + label: 'Print', + accelerator: 'CmdOrCtrl+P', + click: () => { console.log('time to print stuff') } +})) +``` + +It's easy to configure different key combinations based on the user's operating system. + +```js +{ + accelerator: process.platform === 'darwin' ? 'Alt+Cmd+I' : 'Ctrl+Shift+I' +} +``` + +## Global Shortcuts + +You can use the [globalShortcut] module to detect keyboard events even when +the application does not have keyboard focus. + +```js +const {app, globalShortcut} = require('electron') + +app.on('ready', () => { + globalShortcut.register('CommandOrControl+X', () => { + console.log('CommandOrControl+X is pressed') + }) +}) +``` + +## Shortcuts within a BrowserWindow + +If you want to handle keyboard shortcuts for a [BrowserWindow], you can use the `keyup` and `keydown` event listeners on the window object inside the renderer process. + +```js +window.addEventListener('keyup', doSomething, true) +``` + +Note the third parameter `true` which means the listener will always receive key presses before other listeners so they can't have `stopPropagation()` called on them. + +If you don't want to do manual shortcut parsing there are libraries that do advanced key detection such as [mousetrap]. + +```js +Mousetrap.bind('4', () => { console.log('4') }) +Mousetrap.bind('?', () => { console.log('show shortcuts!') }) +Mousetrap.bind('esc', () => { console.log('escape') }, 'keyup') + +// combinations +Mousetrap.bind('command+shift+k', () => { console.log('command shift k') }) + +// map multiple combinations to the same callback +Mousetrap.bind(['command+k', 'ctrl+k'], () => { + console.log('command k or control k') + + // return false to prevent default behavior and stop event from bubbling + return false +}) + +// gmail style sequences +Mousetrap.bind('g i', () => { console.log('go to inbox') }) +Mousetrap.bind('* a', () => { console.log('select all') }) + +// konami code! +Mousetrap.bind('up up down down left right left right b a enter', () => { + console.log('konami code') +}) +``` + +[Menu]: ../api/menu.md +[MenuItem]: ../api/menu-item.md +[globalShortcut]: ../api/global-shortcut.md +[`accelerator`]: ../api/accelerator.md +[BrowserWindow]: ../api/browser-window.md +[mousetrap]: https://github.com/ccampbell/mousetrap diff --git a/docs/tutorial/multithreading.md b/docs/tutorial/multithreading.md new file mode 100644 index 0000000000..b0d298e57e --- /dev/null +++ b/docs/tutorial/multithreading.md @@ -0,0 +1,50 @@ +# Multithreading + +With [Web Workers][web-workers], it is possible to run JavaScript in OS-level +threads. + +## Multi-threaded Node.js + +It is possible to use Node.js features in Electron's Web Workers, to do +so the `nodeIntegrationInWorker` option should be set to `true` in +`webPreferences`. + +```javascript +let win = new BrowserWindow({ + webPreferences: { + nodeIntegrationInWorker: true + } +}) +``` + +The `nodeIntegrationInWorker` can be used independent of `nodeIntegration`, but +`sandbox` must not be set to `true`. + +## Available APIs + +All built-in modules of Node.js are supported in Web Workers, and `asar` +archives can still be read with Node.js APIs. However none of Electron's +built-in modules can be used in a multi-threaded environment. + +## Native Node.js modules + +Any native Node.js module can be loaded directly in Web Workers, but it is +strongly recommended not to do so. Most existing native modules have been +written assuming single-threaded environment, using them in Web Workers will +lead to crashes and memory corruptions. + +Note that even if a native Node.js module is thread-safe it's still not safe to +load it in a Web Worker because the `process.dlopen` function is not thread +safe. + +The only way to load a native module safely for now, is to make sure the app +loads no native modules after the Web Workers get started. + +```javascript +process.dlopen = () => { + throw new Error('Load native module is not safe') +} +let worker = new Worker('script.js') +``` + +[web-workers]: https://developer.mozilla.org/en/docs/Web/API/Web_Workers_API/Using_web_workers diff --git a/docs/tutorial/notifications.md b/docs/tutorial/notifications.md new file mode 100644 index 0000000000..34c64ad9bb --- /dev/null +++ b/docs/tutorial/notifications.md @@ -0,0 +1,85 @@ +# Notifications (Windows, Linux, macOS) + +All three operating systems provide means for applications to send notifications +to the user. Electron conveniently allows developers to send notifications with +the [HTML5 Notification API](https://notifications.spec.whatwg.org/), using +the currently running operating system's native notification APIs to display it. + +**Note:** Since this is an HTML5 API it is only available in the renderer process. + +```javascript +let myNotification = new Notification('Title', { + body: 'Lorem Ipsum Dolor Sit Amet' +}) + +myNotification.onclick = () => { + console.log('Notification clicked') +} +``` + +While code and user experience across operating systems are similar, there +are subtle differences. + +## Windows + +* On Windows 10, notifications "just work". +* On Windows 8.1 and Windows 8, a shortcut to your app, with an [Application User +Model ID][app-user-model-id], must be installed to the Start screen. Note, +however, that it does not need to be pinned to the Start screen. +* On Windows 7, notifications work via a custom implementation which visually + resembles the native one on newer systems. + +Furthermore, in Windows 8, the maximum length for the notification body is 250 +characters, with the Windows team recommending that notifications should be kept +to 200 characters. That said, that limitation has been removed in Windows 10, with +the Windows team asking developers to be reasonable. Attempting to send gigantic +amounts of text to the API (thousands of characters) might result in instability. + +### Advanced Notifications + +Later versions of Windows allow for advanced notifications, with custom templates, +images, and other flexible elements. To send those notifications (from either the +main process or the renderer process), use the userland module +[electron-windows-notifications](https://github.com/felixrieseberg/electron-windows-notifications), +which uses native Node addons to send `ToastNotification` and `TileNotification` objects. + +While notifications including buttons work with just `electron-windows-notifications`, +handling replies requires the use of [`electron-windows-interactive-notifications`](https://github.com/felixrieseberg/electron-windows-interactive-notifications), which +helps with registering the required COM components and calling your Electron app with +the entered user data. + +### Quiet Hours / Presentation Mode + +To detect whether or not you're allowed to send a notification, use the userland module +[electron-notification-state](https://github.com/felixrieseberg/electron-notification-state). + +This allows you to determine ahead of time whether or not Windows will silently throw +the notification away. + +## macOS + +Notifications are straight-forward on macOS, but you should be aware of +[Apple's Human Interface guidelines regarding notifications](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/NotificationCenter.html). + +Note that notifications are limited to 256 bytes in size and will be truncated +if you exceed that limit. + +### Advanced Notifications + +Later versions of macOS allow for notifications with an input field, allowing the user +to quickly reply to a notification. In order to send notifications with an input field, +use the userland module [node-mac-notifier](https://github.com/CharlieHess/node-mac-notifier). + +### Do not disturb / Session State + +To detect whether or not you're allowed to send a notification, use the userland module +[electron-notification-state](https://github.com/felixrieseberg/electron-notification-state). + +This will allow you to detect ahead of time whether or not the notification will be displayed. + +## Linux + +Notifications are sent using `libnotify` which can show notifications on any +desktop environment that follows [Desktop Notifications +Specification][notification-spec], including Cinnamon, Enlightenment, Unity, +GNOME, KDE. diff --git a/docs/tutorial/planned-breaking-changes.md b/docs/tutorial/planned-breaking-changes.md index e2d0e3a939..b5771feea5 100644 --- a/docs/tutorial/planned-breaking-changes.md +++ b/docs/tutorial/planned-breaking-changes.md @@ -57,6 +57,15 @@ crashReporter.start({ }) ``` +## `menu` + +```js +// Deprecated +menu.popup(browserWindow, 100, 200, 2) +// Replace with +menu.popup(browserWindow, {x: 100, y: 200, positioningItem: 2}) +``` + ## `nativeImage` ```js @@ -91,6 +100,19 @@ process.versions.electron read-only properties for consistency with the other `process.versions` properties set by Node. +## `session` + +```js +// Deprecated +ses.setCertificateVerifyProc(function (hostname, certificate, callback) { + callback(true) +}) +// Replace with +ses.setCertificateVerifyProc(function (request, callback) { + callback(0) +}) +``` + ## `Tray` ```js diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index d95957a48c..f490b73889 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -238,7 +238,7 @@ $ npm start ``` For more example apps, see the -[list of boilerplates](http://electron.atom.io/community/#boilerplates) +[list of boilerplates](https://electron.atom.io/community/#boilerplates) created by the awesome electron community. [share-data]: ../faq.md#how-to-share-data-between-web-pages diff --git a/docs/tutorial/security.md b/docs/tutorial/security.md index 1af370c3f3..628edd84ed 100644 --- a/docs/tutorial/security.md +++ b/docs/tutorial/security.md @@ -20,6 +20,11 @@ display primarily local content (or trusted, secure remote content without Node integration) – if your application executes code from an online source, it is your responsibility to ensure that the code is not malicious. +## Reporting Security Issues + +For information on how to properly disclose an Electron vulnerability, +see [SECURITY.md](https://github.com/electron/electron/tree/master/SECURITY.md) + ## Chromium Security Issues and Upgrades While Electron strives to support new versions of Chromium as soon as possible, @@ -75,22 +80,3 @@ This is not bulletproof, but at the least, you should attempt the following: Again, this list merely minimizes the risk, it does not remove it. If your goal is to display a website, a browser will be a more secure option. - -## Buffer Global - -Node's [Buffer](https://nodejs.org/api/buffer.html) class is currently available -as a global even when the `nodeintegration` attribute is not added. You can -delete this in your app by doing the following in your `preload` script: - -```js -delete global.Buffer -``` - -Deleting it may break Node modules used in your preload script and app since -many libraries expect it to be a global instead of requiring it directly via: - -```js -const {Buffer} = require('buffer') -``` - -The `Buffer` global may be removed in future major versions of Electron. diff --git a/docs/tutorial/using-selenium-and-webdriver.md b/docs/tutorial/using-selenium-and-webdriver.md index 464d1ce99f..f7fe2347d9 100644 --- a/docs/tutorial/using-selenium-and-webdriver.md +++ b/docs/tutorial/using-selenium-and-webdriver.md @@ -169,4 +169,4 @@ your app's folder. This eliminates the need to copy-paste your app into Electron's resource directory. [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ -[spectron]: http://electron.atom.io/spectron +[spectron]: https://electron.atom.io/spectron diff --git a/docs/tutorial/windows-store-guide.md b/docs/tutorial/windows-store-guide.md index 2075691baa..abf9643181 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 @@ -125,7 +124,7 @@ Cortana integration, or live tiles. To check out how an Electron app that uses a background task to send toast notifications and live tiles, [check out the Microsoft-provided sample][background-task]. -## Optional: Convert using Container Virtualiziation +## Optional: Convert using Container Virtualization To generate the AppX package, the `electron-windows-store` CLI uses a template that should work for most Electron apps. However, if you are using a custom diff --git a/electron.gyp b/electron.gyp index 39c1f19045..f960fd99f1 100644 --- a/electron.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '1.5.1', + 'version%': '1.6.8', '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', ], }, ], @@ -210,6 +219,7 @@ 'type': 'static_library', 'dependencies': [ 'atom_js2c', + 'vendor/pdf_viewer/pdf_viewer.gyp:pdf_viewer', 'vendor/brightray/brightray.gyp:brightray', 'vendor/node/node.gyp:node', ], @@ -328,6 +338,7 @@ }], # OS=="mac" and mas_build==1 ['OS=="linux"', { 'sources': [ + '<@(lib_sources_linux)', '<@(lib_sources_nss)', ], 'link_settings': { @@ -435,11 +446,29 @@ # depend on this target to ensure the '<(js2c_input_dir)' is created 'atom_js2c_copy', ], + 'variables': { + 'sandbox_args': [ + './lib/sandboxed_renderer/init.js', + '-r', + './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', + ] + }, 'actions': [ { 'action_name': 'atom_browserify_sandbox', 'inputs': [ - '<@(browserify_entries)', + ' { - // 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 @@ -110,7 +107,9 @@ BrowserWindow.prototype._init = function () { const newState = this.isVisible() && !this.isMinimized() if (isVisible !== newState) { isVisible = newState - this.webContents.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', isVisible ? 'visible' : 'hidden') + const visibilityState = isVisible ? 'visible' : 'hidden' + this.webContents.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState) + this.webContents.emit('-window-visibility-change', visibilityState) } } @@ -193,6 +192,9 @@ Object.assign(BrowserWindow.prototype, { }, capturePage (...args) { return this.webContents.capturePage(...args) + }, + setTouchBar (touchBar) { + electron.TouchBar._setOnWindow(touchBar, this) } }) diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js index 85572b3b4f..9644618047 100644 --- a/lib/browser/api/dialog.js +++ b/lib/browser/api/dialog.js @@ -10,7 +10,8 @@ const fileDialogProperties = { multiSelections: 1 << 2, createDirectory: 1 << 3, showHiddenFiles: 1 << 4, - promptToCreate: 1 << 5 + promptToCreate: 1 << 5, + noResolveAliases: 1 << 6 } const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] @@ -81,7 +82,7 @@ module.exports = { } } - let {buttonLabel, defaultPath, filters, properties, title} = options + let {buttonLabel, defaultPath, filters, properties, title, message} = options if (properties == null) { properties = ['openFile'] @@ -118,11 +119,18 @@ module.exports = { filters = [] } + if (message == null) { + message = '' + } else if (typeof message !== 'string') { + throw new TypeError('Message must be a string') + } + const wrappedCallback = typeof callback === 'function' ? function (success, result) { return callback(success ? result : void 0) } : null - return binding.showOpenDialog(title, buttonLabel, defaultPath, filters, - dialogProperties, window, wrappedCallback) + const settings = {title, buttonLabel, defaultPath, filters, message, window} + settings.properties = dialogProperties + return binding.showOpenDialog(settings, wrappedCallback) }, showSaveDialog: function (...args) { @@ -136,7 +144,7 @@ module.exports = { } } - let {buttonLabel, defaultPath, filters, title} = options + let {buttonLabel, defaultPath, filters, title, message, nameFieldLabel, showsTagField} = options if (title == null) { title = '' @@ -160,11 +168,27 @@ module.exports = { filters = [] } + if (message == null) { + message = '' + } else if (typeof message !== 'string') { + throw new TypeError('Message must be a string') + } + + if (nameFieldLabel == null) { + nameFieldLabel = '' + } else if (typeof nameFieldLabel !== 'string') { + throw new TypeError('Name field label must be a string') + } + + if (showsTagField == null) { + showsTagField = true + } + const wrappedCallback = typeof callback === 'function' ? function (success, result) { return callback(success ? result : void 0) } : null - return binding.showSaveDialog(title, buttonLabel, defaultPath, filters, - window, wrappedCallback) + const settings = {title, buttonLabel, defaultPath, filters, message, nameFieldLabel, showsTagField, window} + return binding.showSaveDialog(settings, wrappedCallback) }, showMessageBox: function (...args) { @@ -178,7 +202,10 @@ module.exports = { } } - let {buttons, cancelId, defaultId, detail, icon, message, title, type} = options + let { + buttons, cancelId, checkboxLabel, checkboxChecked, defaultId, detail, + icon, message, title, type + } = options if (type == null) { type = 'none' @@ -217,6 +244,14 @@ module.exports = { throw new TypeError('Detail must be a string') } + checkboxChecked = !!checkboxChecked + + if (checkboxLabel == null) { + checkboxLabel = '' + } else if (typeof checkboxLabel !== 'string') { + throw new TypeError('checkboxLabel must be a string') + } + if (icon == null) { icon = null } @@ -239,12 +274,33 @@ module.exports = { const flags = options.noLink ? messageBoxOptions.noLink : 0 return binding.showMessageBox(messageBoxType, buttons, defaultId, cancelId, - flags, title, message, detail, icon, window, - callback) + flags, title, message, detail, checkboxLabel, + checkboxChecked, icon, window, callback) }, 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/exports/electron.js b/lib/browser/api/exports/electron.js index 9a486b575d..753aa30fd5 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -1,123 +1,13 @@ const common = require('../../../common/api/exports/electron') +// since browser module list is also used in renderer, keep it separate. +const moduleList = require('../module-list') // Import common modules. common.defineProperties(exports) -Object.defineProperties(exports, { - // Browser side modules, please sort with alphabet order. - app: { - enumerable: true, - get: function () { - return require('../app') - } - }, - autoUpdater: { - enumerable: true, - get: function () { - return require('../auto-updater') - } - }, - BrowserWindow: { - enumerable: true, - get: function () { - return require('../browser-window') - } - }, - contentTracing: { - enumerable: true, - get: function () { - return require('../content-tracing') - } - }, - dialog: { - enumerable: true, - get: function () { - return require('../dialog') - } - }, - ipcMain: { - enumerable: true, - get: function () { - return require('../ipc-main') - } - }, - globalShortcut: { - enumerable: true, - get: function () { - return require('../global-shortcut') - } - }, - Menu: { - enumerable: true, - get: function () { - return require('../menu') - } - }, - MenuItem: { - enumerable: true, - get: function () { - return require('../menu-item') - } - }, - powerMonitor: { - enumerable: true, - get: function () { - return require('../power-monitor') - } - }, - powerSaveBlocker: { - enumerable: true, - get: function () { - return require('../power-save-blocker') - } - }, - protocol: { - enumerable: true, - get: function () { - return require('../protocol') - } - }, - screen: { - enumerable: true, - get: function () { - return require('../screen') - } - }, - session: { - enumerable: true, - get: function () { - return require('../session') - } - }, - systemPreferences: { - enumerable: true, - get: function () { - return require('../system-preferences') - } - }, - Tray: { - enumerable: true, - get: function () { - return require('../tray') - } - }, - webContents: { - enumerable: true, - get: function () { - return require('../web-contents') - } - }, - net: { - enumerable: true, - get: function () { - return require('../net') - } - }, - - // The internal modules, invisible unless you know their names. - NavigationController: { - get: function () { - return require('../navigation-controller') - } - } -}) +for (const module of moduleList) { + Object.defineProperty(exports, module.name, { + enumerable: !module.private, + get: () => require(`../${module.file}`) + }) +} diff --git a/lib/browser/api/menu-item-roles.js b/lib/browser/api/menu-item-roles.js index 8a78670267..af0d35b4a5 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 98b8e9980e..e95226d3b3 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/menu.js b/lib/browser/api/menu.js index 167e9a744e..99d650d773 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -144,14 +144,29 @@ Menu.prototype._init = function () { } Menu.prototype.popup = function (window, x, y, positioningItem) { - if (typeof window !== 'object' || window.constructor !== BrowserWindow) { + let asyncPopup + + // menu.popup(x, y, positioningItem) + if (window != null && (typeof window !== 'object' || window.constructor !== BrowserWindow)) { // Shift. positioningItem = y y = x x = window - window = BrowserWindow.getFocusedWindow() + window = null } + // menu.popup(window, {}) + if (x != null && typeof x === 'object') { + const options = x + x = options.x + y = options.y + positioningItem = options.positioningItem + asyncPopup = options.async + } + + // Default to showing in focused window. + if (window == null) window = BrowserWindow.getFocusedWindow() + // Default to showing under mouse location. if (typeof x !== 'number') x = -1 if (typeof y !== 'number') y = -1 @@ -159,7 +174,19 @@ Menu.prototype.popup = function (window, x, y, positioningItem) { // Default to not highlighting any item. if (typeof positioningItem !== 'number') positioningItem = -1 - this.popupAt(window, x, y, positioningItem) + // Default to synchronous for backwards compatibility. + if (typeof asyncPopup !== 'boolean') asyncPopup = false + + this.popupAt(window, x, y, positioningItem, asyncPopup) +} + +Menu.prototype.closePopup = function (window) { + if (window == null || window.constructor !== BrowserWindow) { + window = BrowserWindow.getFocusedWindow() + } + if (window != null) { + this.closePopupAt(window.id) + } } Menu.prototype.append = function (item) { diff --git a/lib/browser/api/module-list.js b/lib/browser/api/module-list.js new file mode 100644 index 0000000000..64b2829064 --- /dev/null +++ b/lib/browser/api/module-list.js @@ -0,0 +1,25 @@ +// Browser side modules, please sort alphabetically. +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'}, + {name: 'globalShortcut', file: 'global-shortcut'}, + {name: 'ipcMain', file: 'ipc-main'}, + {name: 'Menu', file: 'menu'}, + {name: 'MenuItem', file: 'menu-item'}, + {name: 'net', file: 'net'}, + {name: 'powerMonitor', file: 'power-monitor'}, + {name: 'powerSaveBlocker', file: 'power-save-blocker'}, + {name: 'protocol', file: 'protocol'}, + {name: 'screen', file: 'screen'}, + {name: 'session', file: 'session'}, + {name: 'systemPreferences', file: 'system-preferences'}, + {name: 'TouchBar', file: 'touch-bar'}, + {name: 'Tray', file: 'tray'}, + {name: 'webContents', file: 'web-contents'}, + // The internal modules, invisible unless you know their names. + {name: 'NavigationController', file: 'navigation-controller', private: true} +] diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 10d903919e..d32f6946ee 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/session.js b/lib/browser/api/session.js index ac47244db3..33f3b47dee 100644 --- a/lib/browser/api/session.js +++ b/lib/browser/api/session.js @@ -20,3 +20,16 @@ Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype) Session.prototype._init = function () { app.emit('session-created', this) } + +Session.prototype.setCertificateVerifyProc = function (verifyProc) { + if (verifyProc != null && verifyProc.length > 2) { + // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings + this._setCertificateVerifyProc(({hostname, certificate, verificationResult}, cb) => { + verifyProc(hostname, certificate, (result) => { + cb(result ? 0 : -2) + }) + }) + } else { + this._setCertificateVerifyProc(verifyProc) + } +} diff --git a/lib/browser/api/touch-bar.js b/lib/browser/api/touch-bar.js new file mode 100644 index 0000000000..ae0c1f1428 --- /dev/null +++ b/lib/browser/api/touch-bar.js @@ -0,0 +1,311 @@ +const {EventEmitter} = require('events') + +let nextItemID = 1 + +class TouchBar extends EventEmitter { + // Bind a touch bar to a window + static _setOnWindow (touchBar, window) { + if (window._touchBar != null) { + window._touchBar._removeFromWindow(window) + } + + if (touchBar == null) { + window._setTouchBarItems([]) + return + } + + if (Array.isArray(touchBar)) { + touchBar = new TouchBar(touchBar) + } + touchBar._addToWindow(window) + } + + 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)) { + 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.changeListener) + if (item.child instanceof TouchBar) { + item.child.ordereredItems.forEach(registerItem) + } + } + items.forEach((item) => { + if (!(item instanceof TouchBarItem)) { + throw new Error('Each item must be an instance of TouchBarItem') + } + this.ordereredItems.push(item) + registerItem(item) + }) + } + + 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 + + // Already added to window + if (this.windowListeners.hasOwnProperty(id)) return + + window._touchBar = this + + const changeListener = (itemID) => { + window._refreshTouchBarItem(itemID) + } + this.on('change', changeListener) + + const escapeItemListener = (item) => { + window._setEscapeTouchBarItem(item != null ? item : {}) + } + this.on('escape-item-change', escapeItemListener) + + const interactionListener = (event, itemID, details) => { + 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) + } + } + window.on('-touch-bar-interaction', interactionListener) + + 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 + delete this.windowListeners[id] + } + window.once('closed', removeListeners) + this.windowListeners[id] = removeListeners + + window._setTouchBarItems(this.ordereredItems) + escapeItemListener(this.escapeItem) + } + + _removeFromWindow (window) { + const removeListeners = this.windowListeners[window.id] + if (removeListeners != null) removeListeners() + } +} + +class TouchBarItem extends EventEmitter { + constructor () { + super() + this.id = `${nextItemID++}` + } + + _addLiveProperty (name, initialValue) { + const privateName = `_${name}` + this[privateName] = initialValue + Object.defineProperty(this, name, { + get: function () { + return this[privateName] + }, + set: function (value) { + this[privateName] = value + this.emit('change', this) + }, + enumerable: true + }) + } +} + +TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + this.type = 'button' + 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() + } + } + } +} + +TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + this.type = 'colorpicker' + const {availableColors, change, selectedColor} = config + this._addLiveProperty('availableColors', availableColors) + this._addLiveProperty('selectedColor', selectedColor) + + if (typeof change === 'function') { + this.onInteraction = (details) => { + this._selectedColor = details.color + change(details.color) + } + } + } +} + +TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + this.type = 'group' + this.child = config.items + if (!(this.child instanceof TouchBar)) { + this.child = new TouchBar(this.child) + } + } +} + +TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + this.type = 'label' + this._addLiveProperty('label', config.label) + this._addLiveProperty('textColor', config.textColor) + } +} + +TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + this.type = 'popover' + this._addLiveProperty('label', config.label) + this._addLiveProperty('icon', config.icon) + this.showCloseButton = config.showCloseButton + this.child = config.items + 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) + }) + } +} + +TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + this.type = 'slider' + const {change, label, minValue, maxValue, value} = config + this._addLiveProperty('label', label) + this._addLiveProperty('minValue', minValue) + this._addLiveProperty('maxValue', maxValue) + this._addLiveProperty('value', value) + + if (typeof change === 'function') { + this.onInteraction = (details) => { + this._value = details.value + change(details.value) + } + } + } +} + +TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + this.type = 'spacer' + this.size = config.size + } +} + +TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + const {segmentStyle, segments, selectedIndex, change, mode} = config + this.type = 'segmented_control' + this._addLiveProperty('segmentStyle', segmentStyle) + this._addLiveProperty('segments', segments || []) + this._addLiveProperty('selectedIndex', selectedIndex) + this._addLiveProperty('mode', mode) + + if (typeof change === 'function') { + this.onInteraction = (details) => { + this._selectedIndex = details.selectedIndex + change(details.selectedIndex, details.isSelected) + } + } + } +} + +TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem { + constructor (config) { + super() + if (config == null) config = {} + const {items, selectedStyle, overlayStyle, showArrowButtons, continuous, mode} = config + let {select, highlight} = config + this.type = 'scrubber' + this._addLiveProperty('items', items) + this._addLiveProperty('selectedStyle', selectedStyle || null) + this._addLiveProperty('overlayStyle', overlayStyle || null) + this._addLiveProperty('showArrowButtons', showArrowButtons || false) + this._addLiveProperty('mode', mode || 'free') + this._addLiveProperty('continuous', continuous || true) + + if (typeof select === 'function' || typeof highlight === 'function') { + if (select == null) select = () => {} + if (highlight == null) highlight = () => {} + this.onInteraction = (details) => { + if (details.type === 'select') { + select(details.selectedIndex) + } else if (details.type === 'highlight') { + highlight(details.highlightedIndex) + } + } + } + } +} + +module.exports = TouchBar diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index a92ef7603b..c49c1be8ba 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -105,22 +105,17 @@ const webFrameMethods = [ 'insertText', 'setLayoutZoomLevelLimits', 'setVisualZoomLevelLimits', - 'setZoomFactor', - 'setZoomLevel', // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings 'setZoomLevelLimits' ] -const webFrameMethodsWithResult = [ - 'getZoomFactor', - 'getZoomLevel' -] +const webFrameMethodsWithResult = [] const asyncWebFrameMethods = function (requestId, method, callback, ...args) { return new Promise((resolve, reject) => { this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, error, result) { if (error == null) { - if (callback != null) callback(result) + if (typeof callback === 'function') callback(result) resolve(result) } else { reject(error) @@ -154,10 +149,17 @@ for (const method of webFrameMethodsWithResult) { // WebContents has been loaded. WebContents.prototype.executeJavaScript = function (code, hasUserGesture, callback) { const requestId = getNextId() + if (typeof hasUserGesture === 'function') { + // Shift. callback = hasUserGesture + hasUserGesture = null + } + + if (hasUserGesture == null) { hasUserGesture = false } + if (this.getURL() && !this.isLoadingMainFrame()) { return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture) } else { @@ -211,6 +213,26 @@ WebContents.prototype.printToPDF = function (options, callback) { this._printToPDF(printingSetting, callback) } +WebContents.prototype.getZoomLevel = function (callback) { + if (typeof callback !== 'function') { + throw new Error('Must pass function as an argument') + } + process.nextTick(() => { + const zoomLevel = this._getZoomLevel() + callback(zoomLevel) + }) +} + +WebContents.prototype.getZoomFactor = function (callback) { + if (typeof callback !== 'function') { + throw new Error('Must pass function as an argument') + } + process.nextTick(() => { + const zoomFactor = this._getZoomFactor() + callback(zoomFactor) + }) +} + // Add JavaScript wrappers for WebContents class. WebContents.prototype._init = function () { // The navigation controller. @@ -238,7 +260,7 @@ WebContents.prototype._init = function () { this.on('pepper-context-menu', function (event, params) { // Access Menu via electron.Menu to prevent circular require const menu = electron.Menu.buildFromTemplate(params.menu) - menu.popup(params.x, params.y) + menu.popup(event.sender.getOwnerBrowserWindow(), params.x, params.y) }) // The devtools requests the webContents to reload. @@ -246,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-view-manager.js b/lib/browser/guest-view-manager.js index 500ed37bbb..0bfe77d1e5 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -147,7 +147,8 @@ const createGuest = function (embedder, params) { } // Attach the guest to an element of embedder. -const attachGuest = function (embedder, elementInstanceId, guestInstanceId, params) { +const attachGuest = function (event, elementInstanceId, guestInstanceId, params) { + const embedder = event.sender // Destroy the old guest when attaching. const key = `${embedder.getId()}-${elementInstanceId}` const oldGuestInstanceId = embedderElementsMap[key] @@ -183,7 +184,7 @@ const attachGuest = function (embedder, elementInstanceId, guestInstanceId, para guestInstanceId: guestInstanceId, nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false, plugins: params.plugins, - zoomFactor: params.zoomFactor, + zoomFactor: embedder._getZoomFactor(), webSecurity: !params.disablewebsecurity, blinkFeatures: params.blinkfeatures, disableBlinkFeatures: params.disableblinkfeatures @@ -204,6 +205,14 @@ const attachGuest = function (embedder, elementInstanceId, guestInstanceId, para if (params.preload) { webPreferences.preloadURL = params.preload } + + embedder.emit('will-attach-webview', event, webPreferences, params) + if (event.defaultPrevented) { + if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId + destroyGuest(embedder, guestInstanceId) + return + } + webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) guest.attachParams = params embedderElementsMap[key] = guestInstanceId @@ -243,6 +252,17 @@ const watchEmbedder = function (embedder) { } watchedEmbedders.add(embedder) + // Forward embedder window visiblity change events to guest + const onVisibilityChange = function (visibilityState) { + for (const guestInstanceId of Object.keys(guestInstances)) { + const guestInstance = guestInstances[guestInstanceId] + if (guestInstance.embedder === embedder) { + guestInstance.guest.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState) + } + } + } + embedder.on('-window-visibility-change', onVisibilityChange) + const destroyEvents = ['will-destroy', 'crashed', 'did-navigate'] const destroy = function () { for (const guestInstanceId of Object.keys(guestInstances)) { @@ -254,6 +274,7 @@ const watchEmbedder = function (embedder) { for (const event of destroyEvents) { embedder.removeListener(event, destroy) } + embedder.removeListener('-window-visibility-change', onVisibilityChange) watchedEmbedders.delete(embedder) } @@ -276,7 +297,7 @@ ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, }) ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, elementInstanceId, guestInstanceId, params) { - attachGuest(event.sender, elementInstanceId, guestInstanceId, params) + attachGuest(event, elementInstanceId, guestInstanceId, params) }) ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) { diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index e5bfa74123..ecf4093dcf 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 1dac516459..8d543f2d7d 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -7,8 +7,6 @@ const {WebContents} = process.atomBinding('web_contents') const {ipcMain, isPromise, webContents} = electron -const fs = require('fs') - const objectsRegistry = require('./objects-registry') const hasProp = {}.hasOwnProperty @@ -222,6 +220,7 @@ const unwrapArgs = function (sender, args) { removeRemoteListenersAndLogWarning(meta, args, callIntoRenderer) } } + Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }) v8Util.setRemoteCallbackFreer(callIntoRenderer, meta.id, sender) rendererFunctions.set(objectId, callIntoRenderer) @@ -267,13 +266,6 @@ ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, module) { } }) -ipcMain.on('ELECTRON_BROWSER_READ_FILE', function (event, file) { - fs.readFile(file, (err, data) => { - if (err) event.returnValue = {err: err.message} - else event.returnValue = {data: data.toString()} - }) -}) - ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) { try { event.returnValue = valueToMeta(event.sender, electron[module]) @@ -368,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 c5a462cf51..7a54e24fbc 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 }) @@ -75,7 +75,7 @@ class CrashReporter { } getUploadedReports () { - return binding._getUploadedReports(this.getCrashesDirectory()) + return binding.getUploadedReports(this.getCrashesDirectory()) } getCrashesDirectory () { @@ -104,7 +104,7 @@ class CrashReporter { getUploadToServer () { if (process.type === 'browser') { - return binding._getUploadToServer() + return binding.getUploadToServer() } else { throw new Error('getUploadToServer can only be called from the main process') } @@ -112,11 +112,15 @@ class CrashReporter { setUploadToServer (uploadToServer) { if (process.type === 'browser') { - return binding._setUploadToServer(uploadToServer) + return binding.setUploadToServer(uploadToServer) } else { throw new Error('setUploadToServer can only be called from the main process') } } + + setExtraParameter (key, value) { + binding.setExtraParameter(key, value) + } } module.exports = new CrashReporter() diff --git a/lib/common/api/exports/electron.js b/lib/common/api/exports/electron.js index efa44d452c..88b1f78587 100644 --- a/lib/common/api/exports/electron.js +++ b/lib/common/api/exports/electron.js @@ -1,53 +1,13 @@ +const moduleList = require('../module-list') + // Attaches properties to |exports|. exports.defineProperties = function (exports) { - return Object.defineProperties(exports, { - // Common modules, please sort with alphabet order. - clipboard: { - // Must be enumerable, otherwise it woulde be invisible to remote module. - enumerable: true, - get: function () { - return require('../clipboard') - } - }, - crashReporter: { - enumerable: true, - get: function () { - return require('../crash-reporter') - } - }, - nativeImage: { - enumerable: true, - get: function () { - return require('../native-image') - } - }, - shell: { - enumerable: true, - get: function () { - return require('../shell') - } - }, - - // The internal modules, invisible unless you know their names. - CallbacksRegistry: { - get: function () { - return require('../callbacks-registry') - } - }, - deprecate: { - get: function () { - return require('../deprecate') - } - }, - deprecations: { - get: function () { - return require('../deprecations') - } - }, - isPromise: { - get: function () { - return require('../is-promise') - } + const descriptors = {} + for (const module of moduleList) { + descriptors[module.name] = { + enumerable: !module.private, + get: () => require(`../${module.file}`) } - }) + } + return Object.defineProperties(exports, descriptors) } diff --git a/lib/common/api/module-list.js b/lib/common/api/module-list.js new file mode 100644 index 0000000000..00c208c805 --- /dev/null +++ b/lib/common/api/module-list.js @@ -0,0 +1,12 @@ +// Common modules, please sort alphabetically +module.exports = [ + {name: 'clipboard', file: 'clipboard'}, + {name: 'crashReporter', file: 'crash-reporter'}, + {name: 'nativeImage', file: 'native-image'}, + {name: 'shell', file: 'shell'}, + // The internal modules, invisible unless you know their names. + {name: 'CallbacksRegistry', file: 'callbacks-registry', private: true}, + {name: 'deprecate', file: 'deprecate', private: true}, + {name: 'deprecations', file: 'deprecations', private: true}, + {name: 'isPromise', file: 'is-promise', private: true} +] diff --git a/lib/common/atom-binding-setup.js b/lib/common/atom-binding-setup.js new file mode 100644 index 0000000000..8292a23d69 --- /dev/null +++ b/lib/common/atom-binding-setup.js @@ -0,0 +1,13 @@ +module.exports = function atomBindingSetup (binding, processType) { + return function atomBinding (name) { + try { + return binding(`atom_${processType}_${name}`) + } catch (error) { + if (/No such module/.test(error.message)) { + return binding(`atom_common_${name}`) + } else { + throw error + } + } + } +} diff --git a/lib/common/init.js b/lib/common/init.js index 44939b52f4..d732352a30 100644 --- a/lib/common/init.js +++ b/lib/common/init.js @@ -1,18 +1,6 @@ const timers = require('timers') -const {binding} = process - -process.atomBinding = function (name) { - try { - return binding(`atom_${process.type}_${name}`) - } catch (error) { - if (/No such module/.test(error.message)) { - return binding(`atom_common_${name}`) - } else { - throw error - } - } -} +process.atomBinding = require('./atom-binding-setup')(process.binding, process.type) // setImmediate and process.nextTick makes use of uv_check and uv_prepare to // run the callbacks, however since we only run uv loop on requests, the @@ -56,7 +44,7 @@ if (process.platform === 'win32') { // // Nobody else get's to install there, changing the path is forbidden // We can therefore say that we're running as appx - if (__dirname.indexOf('\\Program Files\\WindowsApps\\') === 2) { + if (__dirname.includes('\\WindowsApps\\')) { process.windowsStore = true } } diff --git a/lib/renderer/api/exports/electron.js b/lib/renderer/api/exports/electron.js index 8e05adc980..d9b377a985 100644 --- a/lib/renderer/api/exports/electron.js +++ b/lib/renderer/api/exports/electron.js @@ -1,38 +1,12 @@ const common = require('../../../common/api/exports/electron') +const moduleList = require('../module-list') // Import common modules. common.defineProperties(exports) -Object.defineProperties(exports, { - // Renderer side modules, please sort with alphabet order. - desktopCapturer: { - enumerable: true, - get: function () { - return require('../desktop-capturer') - } - }, - ipcRenderer: { - enumerable: true, - get: function () { - return require('../ipc-renderer') - } - }, - remote: { - enumerable: true, - get: function () { - return require('../remote') - } - }, - screen: { - enumerable: true, - get: function () { - return require('../screen') - } - }, - webFrame: { - enumerable: true, - get: function () { - return require('../web-frame') - } - } -}) +for (const module of moduleList) { + Object.defineProperty(exports, module.name, { + enumerable: !module.private, + get: () => require(`../${module.file}`) + }) +} diff --git a/lib/renderer/api/ipc-renderer-setup.js b/lib/renderer/api/ipc-renderer-setup.js deleted file mode 100644 index c899452fdd..0000000000 --- a/lib/renderer/api/ipc-renderer-setup.js +++ /dev/null @@ -1,40 +0,0 @@ -// Any requires added here need to be added to the browserify_entries array -// in filenames.gypi so they get built into the preload_bundle.js bundle - -module.exports = function (ipcRenderer, binding) { - ipcRenderer.send = function (...args) { - return binding.send('ipc-message', args) - } - - ipcRenderer.sendSync = function (...args) { - return JSON.parse(binding.sendSync('ipc-message-sync', args)) - } - - ipcRenderer.sendToHost = function (...args) { - return binding.send('ipc-message-host', args) - } - - ipcRenderer.sendTo = function (webContentsId, channel, ...args) { - if (typeof webContentsId !== 'number') { - throw new TypeError('First argument has to be webContentsId') - } - - ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', false, webContentsId, channel, ...args) - } - - ipcRenderer.sendToAll = function (webContentsId, channel, ...args) { - if (typeof webContentsId !== 'number') { - throw new TypeError('First argument has to be webContentsId') - } - - ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', true, webContentsId, channel, ...args) - } - - const removeAllListeners = ipcRenderer.removeAllListeners.bind(ipcRenderer) - ipcRenderer.removeAllListeners = function (...args) { - if (args.length === 0) { - throw new Error('Removing all listeners from ipcRenderer will make Electron internals stop working. Please specify a event name') - } - removeAllListeners(...args) - } -} diff --git a/lib/renderer/api/ipc-renderer.js b/lib/renderer/api/ipc-renderer.js index 0a84a0d8ba..d173adb778 100644 --- a/lib/renderer/api/ipc-renderer.js +++ b/lib/renderer/api/ipc-renderer.js @@ -5,6 +5,41 @@ const v8Util = process.atomBinding('v8_util') // Created by init.js. const ipcRenderer = v8Util.getHiddenValue(global, 'ipc') -require('./ipc-renderer-setup')(ipcRenderer, binding) + +ipcRenderer.send = function (...args) { + return binding.send('ipc-message', args) +} + +ipcRenderer.sendSync = function (...args) { + return JSON.parse(binding.sendSync('ipc-message-sync', args)) +} + +ipcRenderer.sendToHost = function (...args) { + return binding.send('ipc-message-host', args) +} + +ipcRenderer.sendTo = function (webContentsId, channel, ...args) { + if (typeof webContentsId !== 'number') { + throw new TypeError('First argument has to be webContentsId') + } + + ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', false, webContentsId, channel, ...args) +} + +ipcRenderer.sendToAll = function (webContentsId, channel, ...args) { + if (typeof webContentsId !== 'number') { + throw new TypeError('First argument has to be webContentsId') + } + + ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', true, webContentsId, channel, ...args) +} + +const removeAllListeners = ipcRenderer.removeAllListeners.bind(ipcRenderer) +ipcRenderer.removeAllListeners = function (...args) { + if (args.length === 0) { + throw new Error('Removing all listeners from ipcRenderer will make Electron internals stop working. Please specify a event name') + } + removeAllListeners(...args) +} module.exports = ipcRenderer diff --git a/lib/renderer/api/module-list.js b/lib/renderer/api/module-list.js new file mode 100644 index 0000000000..4d0e1959de --- /dev/null +++ b/lib/renderer/api/module-list.js @@ -0,0 +1,8 @@ +// Renderer side modules, please sort alphabetically. +module.exports = [ + {name: 'desktopCapturer', file: 'desktop-capturer'}, + {name: 'ipcRenderer', file: 'ipc-renderer'}, + {name: 'remote', file: 'remote'}, + {name: 'screen', file: 'screen'}, + {name: 'webFrame', file: 'web-frame'} +] diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index c3c8e3b89c..5e790133d3 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -1,8 +1,12 @@ 'use strict' -const {Buffer} = require('buffer') +// Note: Don't use destructuring assignment for `Buffer`, or we'll hit a +// browserify bug that makes the statement invalid, throwing an error in +// sandboxed renderer. +const Buffer = require('buffer').Buffer const v8Util = process.atomBinding('v8_util') const {ipcRenderer, isPromise, CallbacksRegistry} = require('electron') +const resolvePromise = Promise.resolve.bind(Promise) const callbacksRegistry = new CallbacksRegistry() @@ -79,7 +83,8 @@ const wrapArgs = function (args, visited) { return { type: 'function', id: callbacksRegistry.add(value), - location: v8Util.getHiddenValue(value, 'location') + location: v8Util.getHiddenValue(value, 'location'), + length: value.length } } else { return { @@ -134,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 } } @@ -174,7 +185,15 @@ const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) { }, get: (target, property, receiver) => { if (!target.hasOwnProperty(property)) loadRemoteProperties() - return target[property] + const value = target[property] + + // Bind toString to target if it is a function to avoid + // Function.prototype.toString is not generic errors + if (property === 'toString' && typeof value === 'function') { + return value.bind(target) + } + + return value }, ownKeys: (target) => { loadRemoteProperties() @@ -206,9 +225,7 @@ const metaToValue = function (meta) { case 'buffer': return Buffer.from(meta.value) case 'promise': - return Promise.resolve({ - then: metaToValue(meta.then) - }) + return resolvePromise({then: metaToValue(meta.then)}) case 'error': return metaToPlainObject(meta) case 'date': @@ -287,18 +304,6 @@ ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', function (event, id) { callbacksRegistry.remove(id) }) -// List all built-in modules in browser process. -const browserModules = require('../../browser/api/exports/electron') - -// And add a helper receiver for each one. -for (let name of Object.getOwnPropertyNames(browserModules)) { - Object.defineProperty(exports, name, { - get: function () { - return exports.getBuiltin(name) - } - }) -} - // Get remote module. exports.require = function (module) { return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_REQUIRE', module)) @@ -343,3 +348,21 @@ exports.getGuestWebContents = function (guestInstanceId) { const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId) return metaToValue(meta) } + +const addBuiltinProperty = (name) => { + Object.defineProperty(exports, name, { + get: function () { + return exports.getBuiltin(name) + } + }) +} + +const browserModules = + require('../../common/api/module-list').concat( + require('../../browser/api/module-list')) + +// And add a helper receiver for each one. +browserModules + .filter((m) => !m.private) + .map((m) => m.name) + .forEach(addBuiltinProperty) diff --git a/lib/renderer/init.js b/lib/renderer/init.js index 02c71b5fa0..9b7ea9dabd 100644 --- a/lib/renderer/init.js +++ b/lib/renderer/init.js @@ -3,9 +3,10 @@ const events = require('events') const path = require('path') const Module = require('module') +const resolvePromise = Promise.resolve.bind(Promise) // We modified the original process.argv to let node.js load the -// atom-renderer.js, we need to restore it here. +// init.js, we need to restore it here. process.argv.splice(1, 1) // Clear search paths. @@ -39,7 +40,7 @@ electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_SYNC_WEB_FRAME_METHOD', (eve electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => { const responseCallback = function (result) { - Promise.resolve(result) + resolvePromise(result) .then((resolvedResult) => { event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, null, resolvedResult) }) @@ -55,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. @@ -68,17 +70,22 @@ 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) nodeIntegration = 'false' +} else if (window.location.protocol === 'chrome:') { + // Disable node integration for chrome UI scheme. + nodeIntegration = 'false' } else { // Override default web functions. require('./override') @@ -112,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. @@ -127,6 +139,7 @@ if (nodeIntegration === 'true') { // Delete Node's symbols after the Environment has been loaded. process.once('loaded', function () { delete global.process + delete global.Buffer delete global.setImmediate delete global.clearImmediate delete global.global diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js index ba8ae32d46..ac45ab32a1 100644 --- a/lib/renderer/web-view/web-view.js +++ b/lib/renderer/web-view/web-view.js @@ -33,17 +33,6 @@ class WebViewImpl { this.setupFocusPropagation() this.viewInstanceId = getNextId() shadowRoot.appendChild(this.browserPluginNode) - - // Subscribe to host's zoom level changes. - this.onZoomLevelChanged = (zoomLevel) => { - this.webviewNode.setZoomLevel(zoomLevel) - } - webFrame.on('zoom-level-changed', this.onZoomLevelChanged) - - this.onVisibilityChanged = (event, visibilityState) => { - this.webviewNode.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', visibilityState) - } - ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', this.onVisibilityChanged) } createBrowserPluginNode () { @@ -56,10 +45,6 @@ class WebViewImpl { // Resets some state upon reattaching element to the DOM. reset () { - // Unlisten the zoom-level-changed event. - webFrame.removeListener('zoom-level-changed', this.onZoomLevelChanged) - ipcRenderer.removeListener('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', this.onVisibilityChanged) - // If guestInstanceId is defined then the has navigated and has // already picked up a partition ID. Thus, we need to reset the initialization // state. However, it may be the case that beforeFirstNavigation is false BUT @@ -230,8 +215,7 @@ class WebViewImpl { buildParams () { const params = { instanceId: this.viewInstanceId, - userAgentOverride: this.userAgentOverride, - zoomFactor: webFrame.getZoomFactor() + userAgentOverride: this.userAgentOverride } for (const attributeName in this.attributes) { if (hasProp.call(this.attributes, attributeName)) { @@ -273,8 +257,7 @@ const registerBrowserPluginElement = function () { this.style.flex = '1 1 auto' } proto.attributeChangedCallback = function (name, oldValue, newValue) { - var internal - internal = v8Util.getHiddenValue(this, 'internal') + const internal = v8Util.getHiddenValue(this, 'internal') if (internal) { internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue) } @@ -294,21 +277,19 @@ const registerBrowserPluginElement = function () { } // Registers custom element. -var registerWebViewElement = function () { +const registerWebViewElement = function () { const proto = Object.create(HTMLObjectElement.prototype) proto.createdCallback = function () { return new WebViewImpl(this) } proto.attributeChangedCallback = function (name, oldValue, newValue) { - var internal - internal = v8Util.getHiddenValue(this, 'internal') + const internal = v8Util.getHiddenValue(this, 'internal') if (internal) { internal.handleWebviewAttributeMutation(name, oldValue, newValue) } } proto.detachedCallback = function () { - var internal - internal = v8Util.getHiddenValue(this, 'internal') + const internal = v8Util.getHiddenValue(this, 'internal') if (!internal) { return } @@ -318,15 +299,14 @@ var registerWebViewElement = function () { internal.reset() } proto.attachedCallback = function () { - var internal, instance - internal = v8Util.getHiddenValue(this, 'internal') + const internal = v8Util.getHiddenValue(this, 'internal') if (!internal) { return } if (!internal.elementAttached) { guestViewInternal.registerEvents(internal, internal.viewInstanceId) internal.elementAttached = true - instance = internal.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].getValue() + const instance = internal.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].getValue() if (instance) { internal.attachGuestInstance(instance) } else { @@ -383,7 +363,11 @@ var registerWebViewElement = function () { 'print', 'printToPDF', 'showDefinitionForSelection', - 'capturePage' + 'capturePage', + 'setZoomFactor', + 'setZoomLevel', + 'getZoomLevel', + 'getZoomFactor' ] const nonblockMethods = [ 'insertCSS', @@ -392,8 +376,6 @@ var registerWebViewElement = function () { 'sendInputEvent', 'setLayoutZoomLevelLimits', 'setVisualZoomLevelLimits', - 'setZoomFactor', - 'setZoomLevel', // TODO(kevinsawicki): Remove in 2.0, deprecate before then with warnings 'setZoomLevelLimits' ] diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index 21f0741a22..99f7aef542 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 0000000000..ff39e96a12 --- /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 new file mode 100644 index 0000000000..02f23be396 --- /dev/null +++ b/lib/sandboxed_renderer/api/exports/electron.js @@ -0,0 +1,30 @@ +Object.defineProperties(exports, { + ipcRenderer: { + enumerable: true, + get: function () { + return require('../ipc-renderer') + } + }, + remote: { + enumerable: true, + get: function () { + 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') + } + }, + isPromise: { + get: function () { + return require('../../../common/api/is-promise') + } + } +}) diff --git a/lib/sandboxed_renderer/api/exports/fs.js b/lib/sandboxed_renderer/api/exports/fs.js new file mode 100644 index 0000000000..7342908e59 --- /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 0000000000..ecd0d38a63 --- /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 0000000000..f2b2f2a77f --- /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/api/ipc-renderer.js b/lib/sandboxed_renderer/api/ipc-renderer.js new file mode 100644 index 0000000000..008372b4aa --- /dev/null +++ b/lib/sandboxed_renderer/api/ipc-renderer.js @@ -0,0 +1,20 @@ +const ipcRenderer = require('../../renderer/api/ipc-renderer') + +const v8Util = process.atomBinding('v8_util') +const ipcNative = process.atomBinding('ipc') + +// AtomSandboxedRendererClient will look for the "ipcNative" hidden object when +// invoking the `onMessage`/`onExit` callbacks. We could reuse "ipc" and assign +// `onMessage`/`onExit` directly to `ipcRenderer`, but it is better to separate +// private/public APIs. +v8Util.setHiddenValue(global, 'ipcNative', ipcNative) + +ipcNative.onMessage = function (channel, args) { + ipcRenderer.emit(channel, {sender: ipcRenderer}, ...args) +} + +ipcNative.onExit = function () { + process.emit('exit') +} + +module.exports = ipcRenderer diff --git a/lib/sandboxed_renderer/init.js b/lib/sandboxed_renderer/init.js index 68daf476ca..1aec0bc0bf 100644 --- a/lib/sandboxed_renderer/init.js +++ b/lib/sandboxed_renderer/init.js @@ -1,32 +1,51 @@ -// Any requires added here need to be added to the browserify_entries array -// in filenames.gypi so they get built into the preload_bundle.js bundle - /* eslint no-eval: "off" */ -/* global binding, preloadPath, process, Buffer */ +/* global binding, preloadPath, Buffer */ const events = require('events') -const ipcRenderer = new events.EventEmitter() -const proc = new events.EventEmitter() -// eval in window scope: -// http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2 -const geval = eval +process.atomBinding = require('../common/atom-binding-setup')(binding.get, 'renderer') -require('../renderer/api/ipc-renderer-setup')(ipcRenderer, binding) - -binding.onMessage = function (channel, args) { - ipcRenderer.emit(channel, ...args) -} - -binding.onExit = function () { - proc.emit('exit') +const v8Util = process.atomBinding('v8_util') +// Expose browserify Buffer as a hidden value. This is used by C++ code to +// deserialize Buffer instances sent from browser process. +v8Util.setHiddenValue(global, 'Buffer', Buffer) +// The `lib/renderer/api/ipc-renderer.js` module looks for the ipc object in the +// "ipc" hidden value +v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter()) +// The process object created by browserify is not an event emitter, fix it so +// the API is more compatible with non-sandboxed renderers. +for (let prop of Object.keys(events.EventEmitter.prototype)) { + if (process.hasOwnProperty(prop)) { + delete process[prop] + } } +Object.setPrototypeOf(process, events.EventEmitter.prototype) +const electron = require('electron') +const fs = require('fs') const preloadModules = new Map([ - ['electron', { - ipcRenderer: ipcRenderer - }] + ['child_process', require('child_process')], + ['electron', electron], + ['fs', fs], + ['os', require('os')], + ['path', require('path')], + ['url', require('url')], + ['timers', require('timers')] ]) +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() +preloadProcess.hang = () => binding.hang() +preloadProcess.getProcessMemoryInfo = () => binding.getProcessMemoryInfo() +preloadProcess.getSystemMemoryInfo = () => binding.getSystemMemoryInfo() +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 function preloadRequire (module) { if (preloadModules.has(module)) { return preloadModules.get(module) @@ -34,26 +53,32 @@ function preloadRequire (module) { throw new Error('module not found') } -// Fetch the source for the preload -let preloadSrc = ipcRenderer.sendSync('ELECTRON_BROWSER_READ_FILE', preloadPath) -if (preloadSrc.err) { - throw new Error(preloadSrc.err) -} - -// Wrap the source into a function receives a `require` function as argument. -// Browserify bundles can make use of this, as explained in: -// https://github.com/substack/node-browserify#multiple-bundles +// Wrap the script into a function executed in global scope. It won't have +// access to the current scope, so we'll expose a few objects as arguments: // -// For example, the user can create a browserify bundle with: +// - `require`: The `preloadRequire` function +// - `process`: The `preloadProcess` object +// - `Buffer`: Browserify `Buffer` implementation +// - `global`: The window object, which is aliased to `global` by browserify. +// +// Browserify bundles can make use of an external require function as explained +// in https://github.com/substack/node-browserify#multiple-bundles, so electron +// apps can use multi-module preload scripts in sandboxed renderers. +// +// For example, the user can create a bundle with: // // $ browserify -x electron preload.js > renderer.js // // and any `require('electron')` calls in `preload.js` will work as expected -// since browserify won't try to include `electron` in the bundle and will fall -// back to the `preloadRequire` function above. -let preloadWrapperSrc = `(function(require, process, Buffer, global) { -${preloadSrc.data} +// since browserify won't try to include `electron` in the bundle, falling back +// to the `preloadRequire` function above. +const preloadWrapperSrc = `(function(require, process, Buffer, global, setImmediate) { +${preloadSrc} })` -let preloadFn = geval(preloadWrapperSrc) -preloadFn(preloadRequire, proc, Buffer, global) +// eval in window scope: +// http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2 +const geval = eval +const preloadFn = geval(preloadWrapperSrc) +const {setImmediate} = require('timers') +preloadFn(preloadRequire, preloadProcess, Buffer, global, setImmediate) diff --git a/lib/worker/init.js b/lib/worker/init.js new file mode 100644 index 0000000000..6a38ce85dc --- /dev/null +++ b/lib/worker/init.js @@ -0,0 +1,37 @@ +'use strict' + +const path = require('path') +const Module = require('module') + +// We modified the original process.argv to let node.js load the +// init.js, we need to restore it here. +process.argv.splice(1, 1) + +// Clear search paths. +require('../common/reset-search-paths') + +// Import common settings. +require('../common/init') + +// Expose public APIs. +Module.globalPaths.push(path.join(__dirname, 'api', 'exports')) + +// Export node bindings to global. +global.require = require +global.module = module + +// Set the __filename to the path of html file if it is file: protocol. +if (self.location.protocol === 'file:') { + let pathname = process.platform === 'win32' && self.location.pathname[0] === '/' ? self.location.pathname.substr(1) : self.location.pathname + global.__filename = path.normalize(decodeURIComponent(pathname)) + global.__dirname = path.dirname(global.__filename) + + // Set module's filename so relative require can work as expected. + module.filename = global.__filename + + // Also search for module under the html file. + module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname)) +} else { + global.__filename = __filename + global.__dirname = __dirname +} diff --git a/package.json b/package.json index 4265611d87..af80571da5 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "electron", - "version": "1.5.1", + "version": "1.6.8", "devDependencies": { "asar": "^0.11.0", "browserify": "^13.1.0", "electabul": "~0.0.4", - "electron-docs-linter": "^1.16.1", + "electron-docs-linter": "^2.1.0", + "electron-typescript-definitions": "^1.0.1", "request": "*", "standard": "^8.4.0", "standard-markdown": "^2.1.1" @@ -30,6 +31,7 @@ "bump-version": "./script/bump-version.py", "build": "python ./script/build.py -c D", "clean": "python ./script/clean.py", + "clean-build": "python ./script/clean.py --build", "coverage": "npm run instrument-code-coverage && npm test -- --use-instrumented-asar", "instrument-code-coverage": "electabul instrument --input-path ./lib --output-path ./out/coverage/electron.asar", "lint": "npm run lint-js && npm run lint-cpp && npm run lint-py && npm run lint-api-docs-js && npm run lint-api-docs", diff --git a/script/cibuild b/script/cibuild index 92dec9db2d..f523fa1af5 100755 --- a/script/cibuild +++ b/script/cibuild @@ -81,16 +81,16 @@ def main(): sys.stderr.write('\nRunning `npm run lint`\n') sys.stderr.flush() execute([npm, 'run', 'lint']) + if is_release: run_script('build.py', ['-c', 'R']) 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/clean.py b/script/clean.py index 669b6b4dd5..c63bdbbade 100755 --- a/script/clean.py +++ b/script/clean.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +import argparse import os import sys @@ -11,13 +12,34 @@ SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def main(): os.chdir(SOURCE_ROOT) - rm_rf('node_modules') - rm_rf('dist') - rm_rf('out') - rm_rf('spec/node_modules') - rm_rf('vendor/brightray/vendor/download/libchromiumcontent') - rm_rf('vendor/brightray/vendor/libchromiumcontent/src') - rm_rf(os.path.expanduser('~/.node-gyp')) + + args = parse_args() + + remove_directory('dist') + remove_directory('out') + + if not args.build: + remove_directory('node_modules') + remove_directory('spec/node_modules') + + remove_directory('vendor/brightray/vendor/download/libchromiumcontent') + remove_directory('vendor/brightray/vendor/libchromiumcontent/src') + + remove_directory(os.path.expanduser('~/.node-gyp')) + + +def parse_args(): + parser = argparse.ArgumentParser(description='Remove generated and' \ + 'downloaded build files') + parser.add_argument('-b', '--build', + help='Only remove out and dist directories', + action='store_true') + return parser.parse_args() + + +def remove_directory(directory): + print 'Removing %s' % directory + rm_rf(directory) if __name__ == '__main__': diff --git a/script/cpplint.py b/script/cpplint.py index 7ec3537e8d..6c715522f8 100755 --- a/script/cpplint.py +++ b/script/cpplint.py @@ -11,6 +11,9 @@ IGNORE_FILES = [ os.path.join('atom', 'browser', 'mac', 'atom_application_delegate.h'), os.path.join('atom', 'browser', 'resources', 'win', 'resource.h'), os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_menu_controller.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', 'atom_touch_bar.h'), + os.path.join('atom', 'browser', 'ui', 'cocoa', + 'touch_bar_forward_declarations.h'), os.path.join('atom', 'common', 'api', 'api_messages.h'), os.path.join('atom', 'common', 'common_message_generator.cc'), os.path.join('atom', 'common', 'common_message_generator.h'), diff --git a/script/create-dist.py b/script/create-dist.py index f777d7a6ee..9188ad8dca 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 @@ -9,8 +10,7 @@ import sys import stat from lib.config import LIBCHROMIUMCONTENT_COMMIT, BASE_URL, PLATFORM, \ - get_target_arch, get_chromedriver_version, \ - get_zip_name + get_target_arch, get_zip_name from lib.util import scoped_cwd, rm_rf, get_electron_version, make_zip, \ execute, electron_gyp @@ -32,6 +32,7 @@ TARGET_BINARIES = { 'win32': [ '{0}.exe'.format(PROJECT_NAME), # 'electron.exe' 'content_shell.pak', + 'pdf_viewer_resources.pak', 'd3dcompiler_47.dll', 'icudtl.dat', 'libEGL.dll', @@ -42,13 +43,13 @@ 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', ], 'linux': [ PROJECT_NAME, # 'electron' 'content_shell.pak', + 'pdf_viewer_resources.pak', 'icudtl.dat', 'libffmpeg.so', 'libnode.so', @@ -86,15 +87,18 @@ 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() + create_typescript_definitions() if PLATFORM == 'linux': strip_binaries() create_version() create_dist_zip() - create_chrome_binary_zip('chromedriver', get_chromedriver_version()) + create_chrome_binary_zip('chromedriver', ELECTRON_VERSION) create_chrome_binary_zip('mksnapshot', ELECTRON_VERSION) create_ffmpeg_zip() create_symbols_zip() @@ -132,9 +136,25 @@ def copy_license(): shutil.copy2(os.path.join(SOURCE_ROOT, 'LICENSE'), DIST_DIR) def create_api_json_schema(): + node_bin_dir = os.path.join(SOURCE_ROOT, 'node_modules', '.bin') + env = os.environ.copy() + env['PATH'] = os.path.pathsep.join([node_bin_dir, env['PATH']]) outfile = os.path.relpath(os.path.join(DIST_DIR, 'electron-api.json')) execute(['electron-docs-linter', 'docs', '--outfile={0}'.format(outfile), - '--version={}'.format(ELECTRON_VERSION.replace('v', ''))]) + '--version={}'.format(ELECTRON_VERSION.replace('v', ''))], + env=env) + +def create_typescript_definitions(): + node_bin_dir = os.path.join(SOURCE_ROOT, 'node_modules', '.bin') + env = os.environ.copy() + env['PATH'] = os.path.pathsep.join([node_bin_dir, env['PATH']]) + infile = os.path.relpath(os.path.join(DIST_DIR, 'electron-api.json')) + outfile = os.path.relpath(os.path.join(DIST_DIR, 'electron.d.ts')) + tslintconfig = os.path.relpath(os.path.join(DIST_DIR, + '../node_modules/electron-typescript-definitions/tslint.json')) + execute(['electron-typescript-definitions', '--in={0}'.format(infile), + '--out={0}'.format(outfile)], env=env) + execute(['tslint', '--config', tslintconfig, outfile], env=env) def strip_binaries(): for binary in TARGET_BINARIES[PLATFORM]: @@ -237,5 +257,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 e6d149353c..5818571089 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 \ - 'e0da1e9caa7c8f3da3519963a9ea32abba43c7c8' + '4a0e32606e52c12c50c2e3a0973d015d8cdff494' PLATFORM = { 'cygwin': 'win32', @@ -42,9 +42,6 @@ def get_target_arch(): return 'x64' -def get_chromedriver_version(): - return 'v2.21' - def get_env_var(name): value = os.environ.get('ELECTRON_' + name, '') if not value: diff --git a/script/upload.py b/script/upload.py index 2ceffbe1dd..7d689f6cae 100755 --- a/script/upload.py +++ b/script/upload.py @@ -10,8 +10,8 @@ import sys import tempfile from io import StringIO -from lib.config import PLATFORM, get_target_arch, get_chromedriver_version, \ - get_env_var, s3_config, get_zip_name +from lib.config import PLATFORM, get_target_arch, get_env_var, s3_config, \ + get_zip_name from lib.util import electron_gyp, execute, get_electron_version, \ parse_version, scoped_cwd, s3put from lib.github import GitHub @@ -81,6 +81,7 @@ def main(): if PLATFORM == 'darwin': upload_electron(github, release, os.path.join(DIST_DIR, 'electron-api.json')) + upload_electron(github, release, os.path.join(DIST_DIR, 'electron.d.ts')) upload_electron(github, release, os.path.join(DIST_DIR, DSYM_NAME)) elif PLATFORM == 'win32': upload_electron(github, release, os.path.join(DIST_DIR, PDB_NAME)) @@ -91,7 +92,7 @@ def main(): # Upload chromedriver and mksnapshot for minor version update. if parse_version(args.version)[2] == '0': - chromedriver = get_zip_name('chromedriver', get_chromedriver_version()) + chromedriver = get_zip_name('chromedriver', ELECTRON_VERSION) upload_electron(github, release, os.path.join(DIST_DIR, chromedriver)) mksnapshot = get_zip_name('mksnapshot', ELECTRON_VERSION) upload_electron(github, release, os.path.join(DIST_DIR, mksnapshot)) diff --git a/script/verify-ffmpeg.py b/script/verify-ffmpeg.py new file mode 100755 index 0000000000..9d9dba0670 --- /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 085522a0cc..1bcc1a5c62 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -9,6 +9,8 @@ const {closeWindow} = require('./window-helpers') const {app, BrowserWindow, ipcMain} = remote +const isCI = remote.getGlobal('isCi') + describe('electron module', function () { it('does not expose internal modules to require', function () { assert.throws(function () { @@ -135,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 () { @@ -456,4 +468,82 @@ describe('app module', function () { assert.equal(app.isDefaultProtocolClient(protocol), false) }) }) + + describe('getFileIcon() API', function () { + // FIXME Get these specs running on Linux CI + if (process.platform === 'linux' && isCI) return + + const iconPath = path.join(__dirname, 'fixtures/assets/icon.ico') + const sizes = { + small: 16, + normal: 32, + large: process.platform === 'win32' ? 32 : 48 + } + + it('fetches a non-empty icon', function (done) { + app.getFileIcon(iconPath, function (err, icon) { + assert.equal(err, null) + assert.equal(icon.isEmpty(), false) + done() + }) + }) + + it('fetches normal icon size by default', function (done) { + app.getFileIcon(iconPath, function (err, icon) { + const size = icon.getSize() + assert.equal(err, null) + assert.equal(size.height, sizes.normal) + assert.equal(size.width, sizes.normal) + done() + }) + }) + + describe('size option', function () { + it('fetches a small icon', function (done) { + app.getFileIcon(iconPath, { size: 'small' }, function (err, icon) { + const size = icon.getSize() + assert.equal(err, null) + assert.equal(size.height, sizes.small) + assert.equal(size.width, sizes.small) + done() + }) + }) + + it('fetches a normal icon', function (done) { + app.getFileIcon(iconPath, { size: 'normal' }, function (err, icon) { + const size = icon.getSize() + assert.equal(err, null) + assert.equal(size.height, sizes.normal) + assert.equal(size.width, sizes.normal) + done() + }) + }) + + it('fetches a large icon', function (done) { + // macOS does not support large icons + if (process.platform === 'darwin') return done() + + app.getFileIcon(iconPath, { size: 'large' }, function (err, icon) { + const size = icon.getSize() + assert.equal(err, null) + assert.equal(size.height, sizes.large) + assert.equal(size.width, sizes.large) + done() + }) + }) + }) + }) + + describe('getAppMemoryInfo() API', function () { + it('returns the process memory of all running electron processes', function () { + const appMemoryInfo = app.getAppMemoryInfo() + assert.ok(appMemoryInfo.length > 0, 'App memory info object is not > 0') + for (const {memory, pid} of appMemoryInfo) { + assert.ok(memory.workingSetSize > 0, 'working set size is not > 0') + assert.ok(memory.privateBytes > 0, 'private bytes is not > 0') + assert.ok(memory.sharedBytes > 0, 'shared bytes is not > 0') + assert.ok(pid > 0, 'pid is not > 0') + } + }) + }) }) diff --git a/spec/api-auto-updater-spec.js b/spec/api-auto-updater-spec.js index 0716c2245d..df77822e45 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 0000000000..3ccb9502c2 --- /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 81d512212c..0acbd4dd77 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 () { @@ -229,6 +297,11 @@ describe('BrowserWindow module', function () { w.loadURL(`data:image/png;base64,${data}`) }) + it('should not crash when there is a pending navigation entry', function (done) { + ipcRenderer.once('navigated-with-pending-entry', () => done()) + ipcRenderer.send('navigate-with-pending-entry', w.id) + }) + describe('POST navigations', function () { afterEach(() => { w.webContents.session.webRequest.onBeforeSendHeaders(null) @@ -278,6 +351,14 @@ describe('BrowserWindow module', function () { w.loadURL(server.url) }) }) + + it('should support support base url for data urls', (done) => { + ipcMain.once('answer', function (event, test) { + assert.equal(test, 'test') + done() + }) + w.loadURL('data:text/html,', {baseURLForDataURL: `file://${path.join(fixtures, 'api')}${path.sep}`}) + }) }) describe('will-navigate event', function () { @@ -679,7 +760,7 @@ describe('BrowserWindow module', function () { }) }) - describe('"title-bar-style" option', function () { + describe('"titleBarStyle" option', function () { if (process.platform !== 'darwin') { return } @@ -759,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') @@ -801,8 +896,9 @@ describe('BrowserWindow module', function () { describe('"node-integration" option', function () { it('disables node integration when specified to false', function (done) { var preload = path.join(fixtures, 'module', 'send-later.js') - ipcMain.once('answer', function (event, test) { - assert.equal(test, 'undefined') + ipcMain.once('answer', function (event, typeofProcess, typeofBuffer) { + assert.equal(typeofProcess, 'undefined') + assert.equal(typeofBuffer, 'undefined') done() }) w.destroy() @@ -1054,6 +1150,29 @@ describe('BrowserWindow module', function () { }) w.loadURL('file://' + path.join(fixtures, 'pages', 'window-open.html')) }) + + it('releases memory after popup is closed', (done) => { + w.destroy() + w = new BrowserWindow({ + show: false, + webPreferences: { + preload: preload, + sandbox: true + } + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?allocate-memory')) + w.webContents.openDevTools({mode: 'detach'}) + ipcMain.once('answer', function (event, {bytesBeforeOpen, bytesAfterOpen, bytesAfterClose}) { + const memoryIncreaseByOpen = bytesAfterOpen - bytesBeforeOpen + const memoryDecreaseByClose = bytesAfterOpen - bytesAfterClose + // decreased memory should be less than increased due to factors we + // can't control, but given the amount of memory allocated in the + // fixture, we can reasonably expect decrease to be at least 70% of + // increase + assert(memoryDecreaseByClose > memoryIncreaseByOpen * 0.7) + done() + }) + }) }) }) @@ -1163,6 +1282,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 @@ -1446,13 +1613,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() + }) }) }) @@ -1837,7 +2010,7 @@ describe('BrowserWindow module', function () { }) }) - it('resolves the returned promise with the result', function (done) { + it('resolves the returned promise with the result when a callback is specified', function (done) { ipcRenderer.send('executeJavaScript', code, true) ipcRenderer.once('executeJavaScript-promise-response', function (event, result) { assert.equal(result, expected) @@ -1845,6 +2018,14 @@ describe('BrowserWindow module', function () { }) }) + it('resolves the returned promise with the result when no callback is specified', function (done) { + ipcRenderer.send('executeJavaScript', code, false) + ipcRenderer.once('executeJavaScript-promise-response', function (event, result) { + assert.equal(result, expected) + done() + }) + }) + it('resolves the returned promise with the result if the code returns an asyncronous promise', function (done) { ipcRenderer.send('executeJavaScript', asyncCode, true) ipcRenderer.once('executeJavaScript-promise-response', function (event, result) { diff --git a/spec/api-clipboard-spec.js b/spec/api-clipboard-spec.js index 2b4aac9fae..a4a0e96430 100644 --- a/spec/api-clipboard-spec.js +++ b/spec/api-clipboard-spec.js @@ -1,8 +1,8 @@ const assert = require('assert') const path = require('path') +const {Buffer} = require('buffer') -const clipboard = require('electron').clipboard -const nativeImage = require('electron').nativeImage +const {clipboard, nativeImage} = require('electron') describe('clipboard module', function () { var fixtures = path.resolve(__dirname, 'fixtures') @@ -45,10 +45,10 @@ describe('clipboard module', function () { it('returns title and url', function () { if (process.platform === 'linux') return - clipboard.writeBookmark('a title', 'http://electron.atom.io') + clipboard.writeBookmark('a title', 'https://electron.atom.io') assert.deepEqual(clipboard.readBookmark(), { title: 'a title', - url: 'http://electron.atom.io' + url: 'https://electron.atom.io' }) clipboard.writeText('no bookmark') @@ -93,4 +93,14 @@ describe('clipboard module', function () { assert.equal(clipboard.readFindText(), 'find this') }) }) + + describe('clipboard.readBuffer(format)', function () { + it('returns a Buffer of the content for the specified format', function () { + if (process.platform !== 'darwin') return + + const buffer = Buffer.from('this is binary', 'utf8') + clipboard.writeText(buffer.toString()) + assert(buffer.equals(clipboard.readBuffer('public.utf8-plain-text'))) + }) + }) }) diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index 0871670de3..92020dbea6 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,65 +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.platform !== 'darwin') 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.platform !== 'darwin') 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 - }) + generateSpecs('without sandbox', {}) + generateSpecs('with sandbox', { + webPreferences: { + sandbox: true, + preload: path.join(fixtures, 'module', 'preload-sandbox.js') + } }) describe('.start(options)', function () { @@ -111,7 +249,7 @@ describe('crashReporter module', function () { crashReporter.start({ companyName: 'Umbrella Corporation', submitURL: 'http://127.0.0.1/crashes', - autoSubmit: true + uploadToServer: true }) assert.equal(crashReporter.getUploadToServer(), true) crashReporter.setUploadToServer(false) @@ -143,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 @@ -155,6 +292,7 @@ const startServer = ({callback, processType, done}) => { assert.equal(fields.platform, process.platform) assert.equal(fields.extra1, 'extra1') assert.equal(fields.extra2, 'extra2') + assert.equal(fields.extra3, undefined) assert.equal(fields._productName, 'Zombies') assert.equal(fields._companyName, 'Umbrella Corporation') assert.equal(fields._version, app.getVersion()) @@ -165,11 +303,21 @@ const startServer = ({callback, processType, done}) => { assert.equal(crashReporter.getLastCrashReport().id, reportId) assert.notEqual(crashReporter.getUploadedReports().length, 0) assert.equal(crashReporter.getUploadedReports()[0].id, reportId) + req.socket.destroy() done() }, 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 @@ -182,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 f8597c0340..83b114f723 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 9b90b0353d..601e28ca53 100644 --- a/spec/api-dialog-spec.js +++ b/spec/api-dialog-spec.js @@ -19,6 +19,10 @@ describe('dialog module', () => { assert.throws(() => { dialog.showOpenDialog({defaultPath: {}}) }, /Default path must be a string/) + + assert.throws(() => { + dialog.showOpenDialog({message: {}}) + }, /Message must be a string/) }) }) @@ -35,6 +39,14 @@ describe('dialog module', () => { assert.throws(() => { dialog.showSaveDialog({defaultPath: {}}) }, /Default path must be a string/) + + assert.throws(() => { + dialog.showSaveDialog({message: {}}) + }, /Message must be a string/) + + assert.throws(() => { + dialog.showSaveDialog({nameFieldLabel: {}}) + }, /Name field label must be a string/) }) }) @@ -59,6 +71,10 @@ describe('dialog module', () => { assert.throws(() => { dialog.showMessageBox({detail: 3.14}) }, /Detail must be a string/) + + assert.throws(() => { + dialog.showMessageBox({checkboxLabel: false}) + }, /checkboxLabel must be a string/) }) }) @@ -77,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 b1ca29c6a1..fb4278d180 100644 --- a/spec/api-ipc-spec.js +++ b/spec/api-ipc-spec.js @@ -85,6 +85,13 @@ describe('ipc module', function () { assert.equal(foo.baz(), 123) }) + it('includes the length of functions specified as arguments', function () { + var a = remote.require(path.join(fixtures, 'module', 'function-with-args.js')) + assert.equal(a(function (a, b, c, d, f) {}), 5) + assert.equal(a((a) => {}), 1) + assert.equal(a((...args) => {}), 0) + }) + it('handles circular references in arrays and objects', function () { var a = remote.require(path.join(fixtures, 'module', 'circular.js')) @@ -147,12 +154,35 @@ describe('ipc module', function () { }) }) + describe('remote modules', function () { + it('includes browser process modules as properties', function () { + assert.equal(typeof remote.app.getPath, 'function') + assert.equal(typeof remote.webContents.getFocusedWebContents, 'function') + assert.equal(typeof remote.clipboard.readText, 'function') + assert.equal(typeof remote.shell.openExternal, 'function') + }) + + it('returns toString() of original function via toString()', function () { + const {readText} = remote.clipboard + assert(readText.toString().startsWith('function')) + + var {functionWithToStringProperty} = remote.require(path.join(fixtures, 'module', 'to-string-non-function.js')) + assert.equal(functionWithToStringProperty.toString, 'hello') + }) + }) + describe('remote object in renderer', 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') @@ -163,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 bf5c9871cd..9b1f3de369 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -1,7 +1,8 @@ const assert = require('assert') const {ipcRenderer, remote} = require('electron') -const {Menu, MenuItem} = remote +const {BrowserWindow, Menu, MenuItem} = remote +const {closeWindow} = require('./window-helpers') describe('menu module', function () { describe('Menu.buildFromTemplate', function () { @@ -216,6 +217,30 @@ describe('menu module', function () { }) }) + describe('Menu.popup', function () { + let w = null + + afterEach(function () { + return closeWindow(w).then(function () { w = null }) + }) + + describe('when called with async: true', function () { + it('returns immediately', function () { + w = new BrowserWindow({show: false, width: 200, height: 200}) + const menu = Menu.buildFromTemplate([ + { + label: '1' + }, { + label: '2' + }, { + label: '3' + } + ]) + menu.popup(w, {x: 100, y: 100, async: true}) + menu.closePopup(w) + }) + }) + }) describe('MenuItem.click', function () { it('should be called with the item object passed', function (done) { var menu = Menu.buildFromTemplate([ @@ -429,26 +454,77 @@ describe('menu module', function () { assert.equal(item.getDefaultRoleAccelerator(), process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z') }) }) -}) -describe('MenuItem with custom properties in constructor', function () { - it('preserves the custom properties', function () { - var template = [{ - label: 'menu 1', - customProp: 'foo', - submenu: [] - }] + 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') - var menu = Menu.buildFromTemplate(template) - menu.items[0].submenu.append(new MenuItem({ - label: 'item 1', - customProp: 'bar', - overrideProperty: 'oops not allowed' - })) + 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') + } - assert.equal(menu.items[0].customProp, 'foo') - assert.equal(menu.items[0].submenu.items[0].label, 'item 1') - assert.equal(menu.items[0].submenu.items[0].customProp, 'bar') - assert.equal(typeof menu.items[0].submenu.items[0].overrideProperty, 'function') + 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 = [{ + label: 'menu 1', + customProp: 'foo', + submenu: [] + }] + + var menu = Menu.buildFromTemplate(template) + menu.items[0].submenu.append(new MenuItem({ + label: 'item 1', + customProp: 'bar', + overrideProperty: 'oops not allowed' + })) + + assert.equal(menu.items[0].customProp, 'foo') + assert.equal(menu.items[0].submenu.items[0].label, 'item 1') + assert.equal(menu.items[0].submenu.items[0].customProp, 'bar') + assert.equal(typeof menu.items[0].submenu.items[0].overrideProperty, 'function') + }) }) }) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js index e28b37bebf..9b8e5c3a2e 100644 --- a/spec/api-native-image-spec.js +++ b/spec/api-native-image-spec.js @@ -11,11 +11,15 @@ describe('nativeImage module', () => { assert.equal(empty.isEmpty(), true) assert.equal(empty.getAspectRatio(), 1) assert.equal(empty.toDataURL(), 'data:image/png;base64,') + assert.equal(empty.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,') assert.deepEqual(empty.getSize(), {width: 0, height: 0}) assert.deepEqual(empty.getBitmap(), []) + assert.deepEqual(empty.getBitmap({scaleFactor: 2.0}), []) assert.deepEqual(empty.toBitmap(), []) + assert.deepEqual(empty.toBitmap({scaleFactor: 2.0}), []) assert.deepEqual(empty.toJPEG(100), []) assert.deepEqual(empty.toPNG(), []) + assert.deepEqual(empty.toPNG({scaleFactor: 2.0}), []) if (process.platform === 'darwin') { assert.deepEqual(empty.getNativeHandle(), []) @@ -79,6 +83,60 @@ describe('nativeImage module', () => { }) }) + describe('toDataURL()', () => { + it('returns a PNG data URL', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')) + assert.equal(imageA.toDataURL(), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + assert.equal(imageA.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + }) + + it('returns a data URL at 1x scale factor by default', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromBuffer(imageA.toPNG(), { + width: imageA.getSize().width, + height: imageA.getSize().height, + scaleFactor: 2.0 + }) + assert.deepEqual(imageB.getSize(), {width: 269, height: 95}) + + const imageC = nativeImage.createFromDataURL(imageB.toDataURL()) + assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + assert(imageB.toBitmap().equals(imageC.toBitmap())) + }) + + it('supports a scale factor', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromDataURL(imageA.toDataURL({scaleFactor: 1.0})) + assert.deepEqual(imageB.getSize(), {width: 538, height: 190}) + const imageC = nativeImage.createFromDataURL(imageA.toDataURL({scaleFactor: 2.0})) + assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + }) + }) + + describe('toPNG()', () => { + it('returns a buffer at 1x scale factor by default', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromBuffer(imageA.toPNG(), { + width: imageA.getSize().width, + height: imageA.getSize().height, + scaleFactor: 2.0 + }) + assert.deepEqual(imageB.getSize(), {width: 269, height: 95}) + + const imageC = nativeImage.createFromBuffer(imageB.toPNG()) + assert.deepEqual(imageC.getSize(), {width: 538, height: 190}) + assert(imageB.toBitmap().equals(imageC.toBitmap())) + }) + + it('supports a scale factor', () => { + const imageA = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')) + const imageB = nativeImage.createFromBuffer(imageA.toPNG({scaleFactor: 1.0})) + assert.deepEqual(imageB.getSize(), {width: 538, height: 190}) + const imageC = nativeImage.createFromBuffer(imageA.toPNG({scaleFactor: 2.0}), {scaleFactor: 2.0}) + assert.deepEqual(imageC.getSize(), {width: 269, height: 95}) + }) + }) + describe('createFromPath(path)', () => { it('returns an empty image for invalid paths', () => { assert(nativeImage.createFromPath('').isEmpty()) @@ -191,4 +249,75 @@ describe('nativeImage module', () => { assert.equal(nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', 'logo.png')).getAspectRatio(), 2.8315789699554443) }) }) + + describe('addRepresentation()', () => { + it('supports adding a buffer representation for a scale factor', () => { + const image = nativeImage.createEmpty() + image.addRepresentation({ + scaleFactor: 1.0, + buffer: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')).toPNG() + }) + image.addRepresentation({ + scaleFactor: 2.0, + buffer: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '2x2.jpg')).toPNG() + }) + image.addRepresentation({ + scaleFactor: 3.0, + buffer: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '3x3.png')).toPNG() + }) + image.addRepresentation({ + scaleFactor: 4.0, + buffer: 'invalid' + }) + + assert.equal(image.isEmpty(), false) + assert.deepEqual(image.getSize(), {width: 1, height: 1}) + assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC') + assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') + assert.equal(image.toDataURL({scaleFactor: 4.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') + }) + + it('supports adding a data URL representation for a scale factor', () => { + const image = nativeImage.createEmpty() + image.addRepresentation({ + scaleFactor: 1.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')).toDataURL() + }) + image.addRepresentation({ + scaleFactor: 2.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '2x2.jpg')).toDataURL() + }) + image.addRepresentation({ + scaleFactor: 3.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '3x3.png')).toDataURL() + }) + image.addRepresentation({ + scaleFactor: 4.0, + dataURL: 'invalid' + }) + + assert.equal(image.isEmpty(), false) + assert.deepEqual(image.getSize(), {width: 1, height: 1}) + assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC') + assert.equal(image.toDataURL({scaleFactor: 3.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') + assert.equal(image.toDataURL({scaleFactor: 4.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAADElEQVQYlWNgIAoAAAAnAAGZWEMnAAAAAElFTkSuQmCC') + }) + + it('supports adding a representation to an existing image', () => { + const image = nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '1x1.png')) + image.addRepresentation({ + scaleFactor: 2.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '2x2.jpg')).toDataURL() + }) + image.addRepresentation({ + scaleFactor: 2.0, + dataURL: nativeImage.createFromPath(path.join(__dirname, 'fixtures', 'assets', '3x3.png')).toDataURL() + }) + + assert.equal(image.toDataURL({scaleFactor: 1.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=') + assert.equal(image.toDataURL({scaleFactor: 2.0}), 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFUlEQVQYlWP8////fwYGBgYmBigAAD34BABBrq9BAAAAAElFTkSuQmCC') + }) + }) }) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 49fe2edadc..a5c3df8210 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-process-spec.js b/spec/api-process-spec.js new file mode 100644 index 0000000000..5f4ad1730b --- /dev/null +++ b/spec/api-process-spec.js @@ -0,0 +1,26 @@ +const assert = require('assert') + +describe('process module', function () { + describe('process.getCPUUsage()', function () { + it('returns a cpu usage object', function () { + const cpuUsage = process.getCPUUsage() + assert.equal(typeof cpuUsage.percentCPUUsage, 'number') + assert.equal(typeof cpuUsage.idleWakeupsPerSecond, 'number') + }) + }) + + describe('process.getIOCounters()', function () { + it('returns an io counters object', function () { + if (process.platform === 'darwin') { + return + } + const ioCounters = process.getIOCounters() + assert.equal(typeof ioCounters.readOperationCount, 'number') + assert.equal(typeof ioCounters.writeOperationCount, 'number') + assert.equal(typeof ioCounters.otherOperationCount, 'number') + assert.equal(typeof ioCounters.readTransferCount, 'number') + assert.equal(typeof ioCounters.writeTransferCount, 'number') + assert.equal(typeof ioCounters.otherTransferCount, 'number') + }) + }) +}) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index 1f31ff7123..d361c10d94 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')) @@ -313,11 +328,11 @@ describe('session module', function () { fs.unlinkSync(downloadFilePath) } - it('can download using BrowserWindow.loadURL', function (done) { + it('can download using WebContents.downloadURL', function (done) { downloadServer.listen(0, '127.0.0.1', function () { var port = downloadServer.address().port ipcRenderer.sendSync('set-download-option', false, false) - w.loadURL(url + ':' + port) + w.webContents.downloadURL(url + ':' + port) ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, @@ -355,7 +370,7 @@ describe('session module', function () { downloadServer.listen(0, '127.0.0.1', function () { var port = downloadServer.address().port ipcRenderer.sendSync('set-download-option', true, false) - w.loadURL(url + ':' + port + '/') + w.webContents.downloadURL(url + ':' + port + '/') ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, @@ -378,7 +393,7 @@ describe('session module', function () { downloadServer.listen(0, '127.0.0.1', function () { var port = downloadServer.address().port ipcRenderer.sendSync('set-download-option', true, false) - w.loadURL(url + ':' + port + '/?testFilename') + w.webContents.downloadURL(url + ':' + port + '/?testFilename') ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, @@ -556,9 +571,10 @@ describe('session module', function () { server.close() }) - it('accepts the request when the callback is called with true', function (done) { - session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { - callback(true) + it('accepts the request when the callback is called with 0', function (done) { + session.defaultSession.setCertificateVerifyProc(function ({hostname, certificate, verificationResult}, callback) { + assert(['net::ERR_CERT_AUTHORITY_INVALID', 'net::ERR_CERT_COMMON_NAME_INVALID'].includes(verificationResult), verificationResult) + callback(0) }) w.webContents.once('did-finish-load', function () { @@ -568,8 +584,37 @@ describe('session module', function () { w.loadURL(`https://127.0.0.1:${server.address().port}`) }) - it('rejects the request when the callback is called with false', function (done) { - session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { + describe('deprecated function signature', function () { + it('supports accepting the request', function (done) { + session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { + assert.equal(hostname, '127.0.0.1') + callback(true) + }) + + w.webContents.once('did-finish-load', function () { + assert.equal(w.webContents.getTitle(), 'hello') + done() + }) + w.loadURL(`https://127.0.0.1:${server.address().port}`) + }) + + it('supports rejecting the request', function (done) { + session.defaultSession.setCertificateVerifyProc(function (hostname, certificate, callback) { + assert.equal(hostname, '127.0.0.1') + callback(false) + }) + + var url = `https://127.0.0.1:${server.address().port}` + w.webContents.once('did-finish-load', function () { + assert.equal(w.webContents.getTitle(), url) + done() + }) + w.loadURL(url) + }) + }) + + it('rejects the request when the callback is called with -2', function (done) { + session.defaultSession.setCertificateVerifyProc(function ({hostname, certificate, verificationResult}, callback) { assert.equal(hostname, '127.0.0.1') assert.equal(certificate.issuerName, 'Intermediate CA') assert.equal(certificate.subjectName, 'localhost') @@ -580,7 +625,8 @@ describe('session module', function () { assert.equal(certificate.issuerCert.issuerCert.issuer.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.subject.commonName, 'Root CA') assert.equal(certificate.issuerCert.issuerCert.issuerCert, undefined) - callback(false) + assert(['net::ERR_CERT_AUTHORITY_INVALID', 'net::ERR_CERT_COMMON_NAME_INVALID'].includes(verificationResult), verificationResult) + callback(-2) }) var url = `https://127.0.0.1:${server.address().port}` diff --git a/spec/api-touch-bar-spec.js b/spec/api-touch-bar-spec.js new file mode 100644 index 0000000000..6177e3cb85 --- /dev/null +++ b/spec/api-touch-bar-spec.js @@ -0,0 +1,112 @@ +const assert = require('assert') +const path = require('path') +const {BrowserWindow, TouchBar} = require('electron').remote +const {closeWindow} = require('./window-helpers') + +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 options object', function () { + assert.throws(() => { + const touchBar = new TouchBar() + touchBar.toString() + }, /Must specify options object as first argument/) + }) + + it('throws an error when created with invalid items', function () { + assert.throws(() => { + 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 + + beforeEach(function () { + window = new BrowserWindow() + }) + + afterEach(function () { + window.setTouchBar(null) + return closeWindow(window).then(function () { window = null }) + }) + + it('can be added to and removed from a window', 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, + new TouchBarPopover({items: new TouchBar([new TouchBarButton({label: 'pop'})])}), + new TouchBarSlider({label: 'slide', value: 5, minValue: 2, maxValue: 75, change: () => {}}), + new TouchBarSpacer({size: 'large'}), + new TouchBarSegmentedControl({ + segmentStyle: 'capsule', + segments: [{label: 'baz', enabled: false}], + selectedIndex: 5 + }), + new TouchBarSegmentedControl({segments: []}), + new TouchBarScrubber({ + items: [{label: 'foo'}, {label: 'bar'}, {label: 'baz'}], + selectedStyle: 'outline', + mode: 'fixed', + 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 f80e04ac7c..7eac9d3e0e 100644 --- a/spec/api-web-contents-spec.js +++ b/spec/api-web-contents-spec.js @@ -1,11 +1,12 @@ 'use strict' const assert = require('assert') +const http = require('http') const path = require('path') const {closeWindow} = require('./window-helpers') const {ipcRenderer, remote} = require('electron') -const {BrowserWindow, webContents, ipcMain} = remote +const {BrowserWindow, webContents, ipcMain, session} = remote const isCi = remote.getGlobal('isCi') @@ -308,4 +309,303 @@ describe('webContents module', function () { } }) }) + + describe('focus()', function () { + describe('when the web contents is hidden', function () { + it('does not blur the focused window', function (done) { + ipcMain.once('answer', (event, parentFocused, childFocused) => { + assert.equal(parentFocused, true) + assert.equal(childFocused, false) + done() + }) + w.show() + w.loadURL('file://' + path.join(__dirname, 'fixtures', 'pages', 'focus-web-contents.html')) + }) + }) + }) + + describe('zoom api', () => { + const zoomScheme = remote.getGlobal('zoomScheme') + const hostZoomMap = { + host1: 0.3, + host2: 0.7, + host3: 0.2 + } + + before((done) => { + const protocol = session.defaultSession.protocol + protocol.registerStringProtocol(zoomScheme, (request, callback) => { + const response = `` + callback({data: response, mimeType: 'text/html'}) + }, (error) => done(error)) + }) + + after((done) => { + const protocol = session.defaultSession.protocol + protocol.unregisterProtocol(zoomScheme, (error) => done(error)) + }) + + it('can set the correct zoom level', (done) => { + w.loadURL('about:blank') + w.webContents.on('did-finish-load', () => { + w.webContents.getZoomLevel((zoomLevel) => { + assert.equal(zoomLevel, 0.0) + w.webContents.setZoomLevel(0.5) + w.webContents.getZoomLevel((zoomLevel) => { + assert.equal(zoomLevel, 0.5) + w.webContents.setZoomLevel(0) + done() + }) + }) + }) + }) + + it('can persist zoom level across navigation', (done) => { + let finalNavigation = false + ipcMain.on('set-zoom', (e, host) => { + const zoomLevel = hostZoomMap[host] + if (!finalNavigation) { + w.webContents.setZoomLevel(zoomLevel) + } + e.sender.send(`${host}-zoom-set`) + }) + ipcMain.on('host1-zoom-level', (e, zoomLevel) => { + const expectedZoomLevel = hostZoomMap.host1 + assert.equal(zoomLevel, expectedZoomLevel) + if (finalNavigation) { + done() + } else { + w.loadURL(`${zoomScheme}://host2`) + } + }) + ipcMain.once('host2-zoom-level', (e, zoomLevel) => { + const expectedZoomLevel = hostZoomMap.host2 + assert.equal(zoomLevel, expectedZoomLevel) + finalNavigation = true + w.webContents.goBack() + }) + w.loadURL(`${zoomScheme}://host1`) + }) + + it('can propagate zoom level across same session', (done) => { + const w2 = new BrowserWindow({ + show: false + }) + w2.webContents.on('did-finish-load', () => { + w.webContents.getZoomLevel((zoomLevel1) => { + assert.equal(zoomLevel1, hostZoomMap.host3) + w2.webContents.getZoomLevel((zoomLevel2) => { + assert.equal(zoomLevel1, zoomLevel2) + w2.setClosable(true) + w2.close() + done() + }) + }) + }) + w.webContents.on('did-finish-load', () => { + w.webContents.setZoomLevel(hostZoomMap.host3) + w2.loadURL(`${zoomScheme}://host3`) + }) + w.loadURL(`${zoomScheme}://host3`) + }) + + it('cannot propagate zoom level across different session', (done) => { + const w2 = new BrowserWindow({ + show: false, + webPreferences: { + partition: 'temp' + } + }) + const protocol = w2.webContents.session.protocol + protocol.registerStringProtocol(zoomScheme, (request, callback) => { + callback('hello') + }, (error) => { + if (error) return done(error) + w2.webContents.on('did-finish-load', () => { + w.webContents.getZoomLevel((zoomLevel1) => { + assert.equal(zoomLevel1, hostZoomMap.host3) + w2.webContents.getZoomLevel((zoomLevel2) => { + assert.equal(zoomLevel2, 0) + assert.notEqual(zoomLevel1, zoomLevel2) + protocol.unregisterProtocol(zoomScheme, (error) => { + if (error) return done(error) + w2.setClosable(true) + w2.close() + done() + }) + }) + }) + }) + w.webContents.on('did-finish-load', () => { + w.webContents.setZoomLevel(hostZoomMap.host3) + w2.loadURL(`${zoomScheme}://host3`) + }) + w.loadURL(`${zoomScheme}://host3`) + }) + }) + + it('can persist when it contains iframe', (done) => { + const server = http.createServer(function (req, res) { + setTimeout(() => { + res.end() + }, 200) + }) + server.listen(0, '127.0.0.1', function () { + const url = 'http://127.0.0.1:' + server.address().port + const content = `` + w.webContents.on('did-frame-finish-load', (e, isMainFrame) => { + if (!isMainFrame) { + w.webContents.getZoomLevel((zoomLevel) => { + assert.equal(zoomLevel, 2.0) + w.webContents.setZoomLevel(0) + server.close() + done() + }) + } + }) + w.webContents.on('dom-ready', () => { + w.webContents.setZoomLevel(2.0) + }) + w.loadURL(`data:text/html,${content}`) + }) + }) + + it('cannot propagate when used with webframe', (done) => { + let finalZoomLevel = 0 + const w2 = new BrowserWindow({ + show: false + }) + w2.webContents.on('did-finish-load', () => { + w.webContents.getZoomLevel((zoomLevel1) => { + assert.equal(zoomLevel1, finalZoomLevel) + w2.webContents.getZoomLevel((zoomLevel2) => { + assert.equal(zoomLevel2, 0) + assert.notEqual(zoomLevel1, zoomLevel2) + w2.setClosable(true) + w2.close() + done() + }) + }) + }) + ipcMain.once('temporary-zoom-set', (e, zoomLevel) => { + w2.loadURL(`file://${fixtures}/pages/c.html`) + finalZoomLevel = zoomLevel + }) + w.loadURL(`file://${fixtures}/pages/webframe-zoom.html`) + }) + + it('cannot persist zoom level after navigation with webFrame', (done) => { + let initialNavigation = true + const source = ` + const {ipcRenderer, webFrame} = require('electron') + webFrame.setZoomLevel(0.6) + ipcRenderer.send('zoom-level-set', webFrame.getZoomLevel()) + ` + w.webContents.on('did-finish-load', () => { + if (initialNavigation) { + w.webContents.executeJavaScript(source, () => {}) + } else { + w.webContents.getZoomLevel((zoomLevel) => { + assert.equal(zoomLevel, 0) + done() + }) + } + }) + ipcMain.once('zoom-level-set', (e, zoomLevel) => { + assert.equal(zoomLevel, 0.6) + w.loadURL(`file://${fixtures}/pages/d.html`) + initialNavigation = false + }) + w.loadURL(`file://${fixtures}/pages/c.html`) + }) + }) + + describe('webrtc ip policy api', () => { + it('can set and get webrtc ip policies', () => { + const policies = [ + 'default', + 'default_public_interface_only', + 'default_public_and_private_interfaces', + 'disable_non_proxied_udp' + ] + policies.forEach((policy) => { + w.webContents.setWebRTCIPHandlingPolicy(policy) + assert.equal(w.webContents.getWebRTCIPHandlingPolicy(), policy) + }) + }) + }) + + 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 bb33b272ec..1302fbbc43 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -1,9 +1,10 @@ const assert = require('assert') +const fs = require('fs') const http = require('http') const path = require('path') const ws = require('ws') const url = require('url') -const {ipcRenderer, remote} = require('electron') +const {ipcRenderer, remote, webFrame} = require('electron') const {closeWindow} = require('./window-helpers') const {app, BrowserWindow, ipcMain, protocol, session, webContents} = remote @@ -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,8 +23,13 @@ describe('chromium feature', function () { listener = null }) - xdescribe('heap snapshot', function () { + afterEach(function () { + return closeWindow(w).then(function () { w = null }) + }) + + describe('heap snapshot', function () { it('does not crash', function () { + if (process.env.TRAVIS === 'true') return process.atomBinding('v8_util').takeHeapSnapshot() }) }) @@ -43,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({ @@ -89,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 } @@ -106,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) }) @@ -118,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 @@ -154,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({ @@ -187,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) @@ -245,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 = { @@ -338,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({ @@ -520,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 () { @@ -554,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 () { @@ -589,6 +687,27 @@ describe('chromium feature', function () { worker.postMessage(message) }) + it('Worker has no node integration by default', function (done) { + let worker = new Worker('../fixtures/workers/worker_node.js') + worker.onmessage = function (event) { + assert.equal(event.data, 'undefined undefined undefined undefined') + worker.terminate() + done() + } + }) + + it('Worker has node integration with nodeIntegrationInWorker', function (done) { + let webview = new WebView() + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, 'object function object function') + webview.remove() + done() + }) + webview.src = 'file://' + fixtures + '/pages/worker.html' + webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker') + document.body.appendChild(webview) + }) + it('SharedWorker can work', function (done) { var worker = new SharedWorker('../fixtures/workers/shared_worker.js') var message = 'ping' @@ -598,6 +717,29 @@ describe('chromium feature', function () { } worker.port.postMessage(message) }) + + it('SharedWorker has no node integration by default', function (done) { + let worker = new SharedWorker('../fixtures/workers/shared_worker_node.js') + worker.port.onmessage = function (event) { + assert.equal(event.data, 'undefined undefined undefined undefined') + done() + } + }) + + it('SharedWorker has node integration with nodeIntegrationInWorker', function (done) { + let webview = new WebView() + webview.addEventListener('console-message', function (e) { + console.log(e) + }) + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, 'object function object function') + webview.remove() + done() + }) + webview.src = 'file://' + fixtures + '/pages/shared_worker.html' + webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker') + document.body.appendChild(webview) + }) }) describe('iframe', function () { @@ -802,4 +944,91 @@ describe('chromium feature', function () { }) }) }) + + describe('PDF Viewer', function () { + const pdfSource = url.format({ + pathname: path.join(fixtures, 'assets', 'cat.pdf').replace(/\\/g, '/'), + protocol: 'file', + slashes: true + }) + + beforeEach(function () { + w = new BrowserWindow({ + show: false, + webPreferences: { + preload: path.join(fixtures, 'module', 'preload-inject-ipc.js') + } + }) + }) + + it('opens when loading a pdf resource as top level navigation', function (done) { + ipcMain.once('pdf-loaded', function (event, success) { + if (success) done() + }) + w.webContents.on('page-title-updated', function () { + const source = ` + if (window.viewer) { + window.viewer.setLoadCallback(function(success) { + window.ipcRenderer.send('pdf-loaded', success); + }); + } + ` + const parsedURL = url.parse(w.webContents.getURL(), true) + assert.equal(parsedURL.protocol, 'chrome:') + assert.equal(parsedURL.hostname, 'pdf-viewer') + assert.equal(parsedURL.query.src, pdfSource) + assert.equal(w.webContents.getTitle(), 'cat.pdf') + w.webContents.executeJavaScript(source) + }) + w.webContents.loadURL(pdfSource) + }) + + it('should not open when pdf is requested as sub resource', function (done) { + webFrame.registerURLSchemeAsPrivileged('file', { + secure: false, + bypassCSP: false, + allowServiceWorkers: false, + corsEnabled: false + }) + fetch(pdfSource).then(function (res) { + assert.equal(res.status, 200) + assert.notEqual(document.title, 'cat.pdf') + done() + }).catch(function (e) { + done(e) + }) + }) + }) + + 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/allocate-memory.html b/spec/fixtures/api/allocate-memory.html new file mode 100644 index 0000000000..ce3140ab0c --- /dev/null +++ b/spec/fixtures/api/allocate-memory.html @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/spec/fixtures/api/crash-restart.html b/spec/fixtures/api/crash-restart.html new file mode 100644 index 0000000000..22f3b45b5c --- /dev/null +++ b/spec/fixtures/api/crash-restart.html @@ -0,0 +1,50 @@ + + + + + diff --git a/spec/fixtures/api/crash.html b/spec/fixtures/api/crash.html index c1fb621426..8b64eddd25 100644 --- a/spec/fixtures/api/crash.html +++ b/spec/fixtures/api/crash.html @@ -1,13 +1,15 @@ 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 0000000000..c97d8d1f19 --- /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 0000000000..ae52532315 --- /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/api/isolated-preload.js b/spec/fixtures/api/isolated-preload.js index 9ff121929c..0f19cdea82 100644 --- a/spec/fixtures/api/isolated-preload.js +++ b/spec/fixtures/api/isolated-preload.js @@ -1,3 +1,8 @@ +// Ensure fetch works from isolated world origin +fetch('http://localhost:1234') +fetch('https://localhost:1234') +fetch(`file://${__filename}`) + const {ipcRenderer, webFrame} = require('electron') window.foo = 3 diff --git a/spec/fixtures/api/loaded-from-dataurl.js b/spec/fixtures/api/loaded-from-dataurl.js new file mode 100644 index 0000000000..c4dbdd044b --- /dev/null +++ b/spec/fixtures/api/loaded-from-dataurl.js @@ -0,0 +1 @@ +require('electron').ipcRenderer.send('answer', 'test') diff --git a/spec/fixtures/api/sandbox.html b/spec/fixtures/api/sandbox.html index a74a1d5d60..f8d7aa7921 100644 --- a/spec/fixtures/api/sandbox.html +++ b/spec/fixtures/api/sandbox.html @@ -1,5 +1,18 @@ + + + + + diff --git a/spec/fixtures/pages/shared_worker.html b/spec/fixtures/pages/shared_worker.html new file mode 100644 index 0000000000..7a0d0757ab --- /dev/null +++ b/spec/fixtures/pages/shared_worker.html @@ -0,0 +1,12 @@ + + + + + diff --git a/spec/fixtures/pages/webframe-zoom.html b/spec/fixtures/pages/webframe-zoom.html new file mode 100644 index 0000000000..81006aad64 --- /dev/null +++ b/spec/fixtures/pages/webframe-zoom.html @@ -0,0 +1,9 @@ + + + + + diff --git a/spec/fixtures/pages/webview-custom-zoom-level.html b/spec/fixtures/pages/webview-custom-zoom-level.html new file mode 100644 index 0000000000..24f246fdd2 --- /dev/null +++ b/spec/fixtures/pages/webview-custom-zoom-level.html @@ -0,0 +1,26 @@ + + + + + + diff --git a/spec/fixtures/pages/webview-in-page-navigate.html b/spec/fixtures/pages/webview-in-page-navigate.html new file mode 100644 index 0000000000..5b061e387b --- /dev/null +++ b/spec/fixtures/pages/webview-in-page-navigate.html @@ -0,0 +1,32 @@ + + + + + + diff --git a/spec/fixtures/pages/webview-origin-zoom-level.html b/spec/fixtures/pages/webview-origin-zoom-level.html new file mode 100644 index 0000000000..2c83f7ae56 --- /dev/null +++ b/spec/fixtures/pages/webview-origin-zoom-level.html @@ -0,0 +1,20 @@ + + + + + diff --git a/spec/fixtures/pages/window-no-javascript.html b/spec/fixtures/pages/window-no-javascript.html new file mode 100644 index 0000000000..9c38c9e0bb --- /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 0000000000..aa7e48ea0e --- /dev/null +++ b/spec/fixtures/pages/window-opener-targetOrigin.html @@ -0,0 +1,24 @@ + + + + + diff --git a/spec/fixtures/pages/worker.html b/spec/fixtures/pages/worker.html new file mode 100644 index 0000000000..c84ef52065 --- /dev/null +++ b/spec/fixtures/pages/worker.html @@ -0,0 +1,12 @@ + + + + + diff --git a/spec/fixtures/pages/zoom-factor.html b/spec/fixtures/pages/zoom-factor.html index b9f8f988ca..c27b5ea495 100644 --- a/spec/fixtures/pages/zoom-factor.html +++ b/spec/fixtures/pages/zoom-factor.html @@ -2,7 +2,7 @@ diff --git a/spec/fixtures/workers/shared_worker_node.js b/spec/fixtures/workers/shared_worker_node.js new file mode 100644 index 0000000000..0a52d60fbf --- /dev/null +++ b/spec/fixtures/workers/shared_worker_node.js @@ -0,0 +1,5 @@ +self.onconnect = function (event) { + let port = event.ports[0] + port.start() + port.postMessage([typeof process, typeof setImmediate, typeof global, typeof Buffer].join(' ')) +} diff --git a/spec/fixtures/workers/worker_node.js b/spec/fixtures/workers/worker_node.js new file mode 100644 index 0000000000..5d59d2d0c3 --- /dev/null +++ b/spec/fixtures/workers/worker_node.js @@ -0,0 +1 @@ +self.postMessage([typeof process, typeof setImmediate, typeof global, typeof Buffer].join(' ')) diff --git a/spec/modules-spec.js b/spec/modules-spec.js index 92771b2f53..ba87ed3466 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -1,49 +1,61 @@ 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']] - }) - 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('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) }) }) }) @@ -60,57 +72,86 @@ describe('third-party module', function () { assert.strictEqual(require('./fixtures/module/declare-global'), 'declared global') }) }) - }) -}) -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 = 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', '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') - ]) + describe('Buffer', function () { + it('can be declared in a module', function () { + assert.strictEqual(require('./fixtures/module/declare-buffer'), 'declared Buffer') + }) }) }) - 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('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 = 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', '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') + ]) + }) + }) + + 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 98db1efeb0..678b8e9490 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/package.json b/spec/package.json index 46aa8404c6..1211b3ea8b 100644 --- a/spec/package.json +++ b/spec/package.json @@ -5,6 +5,7 @@ "version": "0.1.0", "devDependencies": { "basic-auth": "^1.0.4", + "coffee-script": "^1.12.3", "graceful-fs": "^4.1.9", "mkdirp": "^0.5.1", "mocha": "^3.1.0", diff --git a/spec/static/main.js b/spec/static/main.js index f29bd00e68..ba31b61ae0 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') @@ -93,12 +91,19 @@ if (global.isCi) { // Register app as standard scheme. global.standardScheme = 'app' -protocol.registerStandardSchemes([global.standardScheme], { secure: true }) +global.zoomScheme = 'zoom' +protocol.registerStandardSchemes([global.standardScheme, global.zoomScheme], { secure: true }) 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 () {}) @@ -192,16 +197,23 @@ app.on('ready', function () { }) ipcMain.on('executeJavaScript', function (event, code, hasCallback) { + let promise + if (hasCallback) { - window.webContents.executeJavaScript(code, (result) => { + promise = window.webContents.executeJavaScript(code, (result) => { window.webContents.send('executeJavaScript-response', result) - }).then((result) => { - window.webContents.send('executeJavaScript-promise-response', result) - }).catch((err) => { - window.webContents.send('executeJavaScript-promise-error', err) }) } else { - window.webContents.executeJavaScript(code) + promise = window.webContents.executeJavaScript(code) + } + + promise.then((result) => { + window.webContents.send('executeJavaScript-promise-response', result) + }).catch((error) => { + window.webContents.send('executeJavaScript-promise-error', error) + }) + + if (!hasCallback) { event.returnValue = 'success' } }) @@ -250,6 +262,17 @@ ipcMain.on('prevent-next-new-window', (event, id) => { webContents.fromId(id).once('new-window', event => event.preventDefault()) }) +ipcMain.on('prevent-next-will-attach-webview', (event) => { + event.sender.once('will-attach-webview', event => event.preventDefault()) +}) + +ipcMain.on('disable-node-on-next-will-attach-webview', (event, id) => { + event.sender.once('will-attach-webview', (event, webPreferences, params) => { + params.src = `file://${path.join(__dirname, '..', 'fixtures', 'pages', 'c.html')}` + webPreferences.nodeIntegration = false + }) +}) + ipcMain.on('try-emit-web-contents-event', (event, id, eventName) => { const consoleWarn = console.warn let warningMessage = null @@ -292,6 +315,50 @@ ipcMain.on('handle-unhandled-rejection', (event, message) => { }) }) +ipcMain.on('navigate-with-pending-entry', (event, id) => { + const w = BrowserWindow.fromId(id) + + w.webContents.on('did-start-loading', () => { + w.loadURL('about:blank') + }) + + w.webContents.on('did-navigate', (e, url) => { + if (url === 'about:blank') { + event.sender.send('navigated-with-pending-entry') + } + }) + + w.webContents.session.clearHostResolverCache(() => { + w.loadURL('http://host') + }) +}) + +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 9af3e01a4c..4ea9369d66 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -2,7 +2,7 @@ const assert = require('assert') const path = require('path') const http = require('http') const url = require('url') -const {remote} = require('electron') +const {ipcRenderer, remote} = require('electron') const {app, session, getGuestWebContents, ipcMain, BrowserWindow, webContents} = remote const {closeWindow} = require('./window-helpers') @@ -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() @@ -171,7 +174,7 @@ describe(' tag', function () { describe('preload attribute', function () { it('loads the script before other scripts in window', function (done) { var listener = function (e) { - assert.equal(e.message, 'function object object') + assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } @@ -181,9 +184,9 @@ describe(' tag', function () { document.body.appendChild(webview) }) - it('preload script can still use "process" in required modules when nodeintegration is off', function (done) { + it('preload script can still use "process" and "Buffer" when nodeintegration is off', function (done) { webview.addEventListener('console-message', function (e) { - assert.equal(e.message, 'object undefined object') + assert.equal(e.message, 'object undefined object function') done() }) webview.setAttribute('preload', fixtures + '/module/preload-node-off.js') @@ -191,6 +194,16 @@ describe(' tag', function () { document.body.appendChild(webview) }) + it('preload script can require modules that still use "process" and "Buffer" when nodeintegration is off', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'object undefined object function undefined') + done() + }) + webview.setAttribute('preload', fixtures + '/module/preload-node-off-wrapper.js') + webview.src = 'file://' + fixtures + '/api/blank.html' + document.body.appendChild(webview) + }) + it('receives ipc message in preload script', function (done) { var message = 'boom!' var listener = function (e) { @@ -212,7 +225,7 @@ describe(' tag', function () { it('works without script tag in page', function (done) { var listener = function (e) { - assert.equal(e.message, 'function object object') + assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } @@ -224,7 +237,7 @@ describe(' tag', function () { it('resolves relative URLs', function (done) { var listener = function (e) { - assert.equal(e.message, 'function object object') + assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } @@ -318,7 +331,7 @@ describe(' tag', function () { it('does not break preload script', function (done) { var listener = function (e) { - assert.equal(e.message, 'function object object') + assert.equal(e.message, 'function object object function') webview.removeEventListener('console-message', listener) done() } @@ -523,8 +536,11 @@ describe(' tag', function () { it('emits when favicon urls are received', function (done) { webview.addEventListener('page-favicon-updated', function (e) { assert.equal(e.favicons.length, 2) - var pageUrl = process.platform === 'win32' ? 'file:///C:/favicon.png' : 'file:///favicon.png' - assert.equal(e.favicons[0], pageUrl) + if (process.platform === 'win32') { + assert(/^file:\/\/\/[A-Z]:\/favicon.png$/i.test(e.favicons[0])) + } else { + assert.equal(e.favicons[0], 'file:///favicon.png') + } done() }) webview.src = 'file://' + fixtures + '/pages/a.html' @@ -1064,21 +1080,6 @@ describe(' tag', function () { }) }) - it('inherits the zoomFactor of the parent window', function (done) { - w = new BrowserWindow({ - show: false, - webPreferences: { - zoomFactor: 1.2 - } - }) - ipcMain.once('pong', function (event, zoomFactor, zoomLevel) { - assert.equal(zoomFactor, 1.2) - assert.equal(zoomLevel, 1) - done() - }) - w.loadURL('file://' + fixtures + '/pages/webview-zoom-factor.html') - }) - it('inherits the parent window visibility state and receives visibilitychange events', function (done) { w = new BrowserWindow({ show: false @@ -1088,18 +1089,40 @@ describe(' tag', function () { assert.equal(visibilityState, 'hidden') assert.equal(hidden, true) - w.webContents.send('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', 'visible') - ipcMain.once('pong', function (event, visibilityState, hidden) { assert.equal(visibilityState, 'visible') assert.equal(hidden, false) done() }) + + w.webContents.emit('-window-visibility-change', 'visible') }) w.loadURL('file://' + fixtures + '/pages/webview-visibilitychange.html') }) + describe('will-attach-webview event', () => { + it('supports changing the web preferences', (done) => { + ipcRenderer.send('disable-node-on-next-will-attach-webview') + webview.addEventListener('console-message', (event) => { + assert.equal(event.message, 'undefined undefined undefined undefined') + done() + }) + webview.setAttribute('nodeintegration', 'yes') + webview.src = 'file://' + fixtures + '/pages/a.html' + document.body.appendChild(webview) + }) + + it('supports preventing a webview from being created', (done) => { + ipcRenderer.send('prevent-next-will-attach-webview') + webview.addEventListener('destroyed', () => { + done() + }) + webview.src = 'file://' + fixtures + '/pages/c.html' + document.body.appendChild(webview) + }) + }) + it('loads devtools extensions registered on the parent window', function (done) { w = new BrowserWindow({ show: false @@ -1515,4 +1538,85 @@ describe(' tag', function () { }) }) }) + + describe('zoom behavior', () => { + const zoomScheme = remote.getGlobal('zoomScheme') + const webviewSession = session.fromPartition('webview-temp') + + before((done) => { + const protocol = webviewSession.protocol + protocol.registerStringProtocol(zoomScheme, (request, callback) => { + callback('hello') + }, (error) => done(error)) + }) + + after((done) => { + const protocol = webviewSession.protocol + protocol.unregisterProtocol(zoomScheme, (error) => done(error)) + }) + + it('inherits the zoomFactor of the parent window', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + zoomFactor: 1.2 + } + }) + ipcMain.once('webview-parent-zoom-level', (event, zoomFactor, zoomLevel) => { + assert.equal(zoomFactor, 1.2) + assert.equal(zoomLevel, 1) + done() + }) + w.loadURL(`file://${fixtures}/pages/webview-zoom-factor.html`) + }) + + it('maintains zoom level on navigation', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + zoomFactor: 1.2 + } + }) + ipcMain.on('webview-zoom-level', (event, zoomLevel, zoomFactor, newHost, final) => { + if (!newHost) { + assert.equal(zoomFactor, 1.44) + assert.equal(zoomLevel, 2.0) + } else { + assert.equal(zoomFactor, 1.2) + assert.equal(zoomLevel, 1) + } + if (final) done() + }) + w.loadURL(`file://${fixtures}/pages/webview-custom-zoom-level.html`) + }) + + it('maintains zoom level when navigating within same page', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + zoomFactor: 1.2 + } + }) + ipcMain.on('webview-zoom-in-page', (event, zoomLevel, zoomFactor, final) => { + assert.equal(zoomFactor, 1.44) + assert.equal(zoomLevel, 2.0) + if (final) done() + }) + w.loadURL(`file://${fixtures}/pages/webview-in-page-navigate.html`) + }) + + it('inherits zoom level for the origin when available', (done) => { + w = new BrowserWindow({ + show: false, + webPreferences: { + zoomFactor: 1.2 + } + }) + ipcMain.once('webview-origin-zoom-level', (event, zoomLevel) => { + assert.equal(zoomLevel, 2.0) + done() + }) + w.loadURL(`file://${fixtures}/pages/webview-origin-zoom-level.html`) + }) + }) }) diff --git a/tools/list-browserify-deps.py b/tools/list-browserify-deps.py new file mode 100755 index 0000000000..c25007d2a1 --- /dev/null +++ b/tools/list-browserify-deps.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +import os +import subprocess +import sys + + +SOURCE_ROOT = os.path.dirname(os.path.dirname(__file__)) +BROWSERIFY = os.path.join(SOURCE_ROOT, 'node_modules', '.bin', 'browserify') +if sys.platform == 'win32': + BROWSERIFY += '.cmd' + +deps = subprocess.check_output([BROWSERIFY, '--list'] + sys.argv[1:]) +for dep in deps.split('\n'): + if dep: + dep = os.path.relpath(dep, SOURCE_ROOT) + if sys.platform == 'win32': + print('/'.join(dep.split('\\'))) + else: + print(dep) diff --git a/vendor/brightray b/vendor/brightray index 509fd37d89..909c492654 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 509fd37d890ce24d4b1ef0790e7d5e1382d27d0c +Subproject commit 909c49265493bd095c27cefd999567be2107899a diff --git a/vendor/native_mate b/vendor/native_mate index 7197368c6d..fd0e7dc4ab 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit 7197368c6d9e36696d23d33ec603701789da329d +Subproject commit fd0e7dc4ab778f0d1ccda6c9640464ea06ee771e diff --git a/vendor/node b/vendor/node index be4f9967b2..3fe90cfcf5 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit be4f9967b2c5e8ce78647f37d44928e028e9f750 +Subproject commit 3fe90cfcf54dd946980e59daf550a7cdb2317c8f diff --git a/vendor/pdf_viewer b/vendor/pdf_viewer new file mode 160000 index 0000000000..a050a339cf --- /dev/null +++ b/vendor/pdf_viewer @@ -0,0 +1 @@ +Subproject commit a050a339cfeabcfb5f07c313161d2ee27b6c3a39