diff --git a/.gitignore b/.gitignore index eb9aedb4e2f..a92239801a4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /external_binaries/ /out/ /vendor/brightray/vendor/download/ +/vendor/debian_wheezy_amd64-sysroot/ /vendor/debian_wheezy_arm-sysroot/ /vendor/debian_wheezy_i386-sysroot/ /vendor/python_26/ diff --git a/.node-version b/.node-version new file mode 100644 index 00000000000..346d9cae72c --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v5.1.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67c1323205e..c7c221863dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ possible with your report. If you can, please include: ## Submitting Pull Requests * Include screenshots and animated GIFs in your pull request whenever possible. -* Follow the CoffeeScript, JavaScript, C++ and Python [coding style defined in docs](/docs/development/coding-style.md). +* Follow the JavaScript, C++, and Python [coding style defined in docs](/docs/development/coding-style.md). * Write documentation in [Markdown](https://daringfireball.net/projects/markdown). See the [Documentation Styleguide](/docs/styleguide.md). * Use short, present tense commit messages. See [Commit Message Styleguide](#git-commit-messages). diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..a78e30d8620 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,2 @@ +* Electron version: +* Operating system: diff --git a/README-ko.md b/README-ko.md index a2db99929c0..5bef003966d 100644 --- a/README-ko.md +++ b/README-ko.md @@ -1,6 +1,7 @@ [![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) [![Build Status](https://travis-ci.org/atom/electron.svg?branch=master)](https://travis-ci.org/atom/electron) +[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/qtmod45u0cc1ouov/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) [![devDependency Status](https://david-dm.org/atom/electron/dev-status.svg)](https://david-dm.org/atom/electron#info=devDependencies) [![Join the Electron Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/) @@ -57,6 +58,7 @@ API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝 - [중국어 번체](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) - [우크라이나어](https://github.com/atom/electron/tree/master/docs-translations/uk-UA) - [러시아어](https://github.com/atom/electron/tree/master/docs-translations/ru-RU) +- [프랑스어](https://github.com/atom/electron/tree/master/docs-translations/fr-FR) ## 시작하기 @@ -70,7 +72,9 @@ API 레퍼런스가 있습니다. Electron을 빌드 하는 방법과 프로젝 - Atom 포럼의 [`electron`](http://discuss.atom.io/c/electron) 카테고리 - Freenode 채팅의 `#atom-shell` 채널 - Slack의 [`Atom`](http://atom-slack.herokuapp.com/) 채널 -- [`electron-br`](https://electron-br.slack.com) *(브라질 포르투갈어)* +- [`electron-br`](https://electron-br.slack.com) *(브라질)* 커뮤니티 +- [`electron-kr`](http://www.meetup.com/electron-kr/) *(한국)* 커뮤니티 +- [`electron-jp`](https://electron-jp-slackin.herokuapp.com/) *(일본)* 커뮤니티 [awesome-electron](https://github.com/sindresorhus/awesome-electron) 프로젝트에 커뮤니티가 운영중인 유용한 예제 어플리케이션과 도구, 리소스가 있으니 한번 참고해 보시기 diff --git a/README.md b/README.md index a7306ddabbb..f7b5cb79000 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![Electron Logo](http://electron.atom.io/images/electron-logo.svg)](http://electron.atom.io/) -[![Build Status](https://travis-ci.org/atom/electron.svg?branch=master)](https://travis-ci.org/atom/electron) +[![Travis Build Status](https://travis-ci.org/atom/electron.svg?branch=master)](https://travis-ci.org/atom/electron) +[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/qtmod45u0cc1ouov/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/electron) [![devDependency Status](https://david-dm.org/atom/electron/dev-status.svg)](https://david-dm.org/atom/electron#info=devDependencies) [![Join the Electron Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/) @@ -54,6 +55,7 @@ contains documents describing how to build and contribute to Electron. - [Traditional Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) - [Ukrainian](https://github.com/atom/electron/tree/master/docs-translations/uk-UA) - [Russian](https://github.com/atom/electron/tree/master/docs-translations/ru-RU) +- [French](https://github.com/atom/electron/tree/master/docs-translations/fr-FR) ## Quick Start @@ -70,6 +72,7 @@ forums - [`Atom`](http://atom-slack.herokuapp.com/) channel on Slack - [`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)* Check out [awesome-electron](https://github.com/sindresorhus/awesome-electron) for a community maintained list of useful example apps, tools and resources. diff --git a/atom/app/atom_library_main.h b/atom/app/atom_library_main.h index 899861f610b..e1603fa5942 100644 --- a/atom/app/atom_library_main.h +++ b/atom/app/atom_library_main.h @@ -5,7 +5,7 @@ #ifndef ATOM_APP_ATOM_LIBRARY_MAIN_H_ #define ATOM_APP_ATOM_LIBRARY_MAIN_H_ -#include "base/basictypes.h" +#include "build/build_config.h" #if defined(OS_MACOSX) extern "C" { diff --git a/atom/app/atom_main.cc b/atom/app/atom_main.cc index 1ffab8d9225..cbd0a85d576 100644 --- a/atom/app/atom_main.cc +++ b/atom/app/atom_main.cc @@ -16,7 +16,7 @@ #include "atom/common/crash_reporter/win/crash_service_main.h" #include "base/environment.h" #include "base/win/windows_version.h" -#include "content/public/app/startup_helper_win.h" +#include "content/public/app/sandbox_helper_win.h" #include "sandbox/win/src/sandbox_types.h" #include "ui/gfx/win/dpi.h" #elif defined(OS_LINUX) // defined(OS_WIN) diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index 698da4f4def..221d59c1619 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -135,7 +135,7 @@ content::ContentUtilityClient* AtomMainDelegate::CreateContentUtilityClient() { } scoped_ptr AtomMainDelegate::CreateContentClient() { - return scoped_ptr(new AtomContentClient).Pass(); + return scoped_ptr(new AtomContentClient); } } // namespace atom diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index b0c87064ea6..52d6ec3d338 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -18,6 +18,7 @@ #include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" +#include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "base/command_line.h" @@ -34,6 +35,7 @@ #include "native_mate/object_template_builder.h" #include "net/ssl/ssl_cert_request_info.h" #include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/image/image.h" #if defined(OS_WIN) #include "base/strings/utf_string_conversions.h" @@ -130,19 +132,20 @@ void OnClientCertificateSelected( std::shared_ptr delegate, mate::Arguments* args) { mate::Dictionary cert_data; - if (!(args->Length() == 1 && args->GetNext(&cert_data))) { + if (!args->GetNext(&cert_data)) { args->ThrowError(); return; } - std::string encoded_data; - cert_data.Get("data", &encoded_data); + v8::Local data; + if (!cert_data.Get("data", &data)) + return; - auto certs = - net::X509Certificate::CreateCertificateListFromBytes( - encoded_data.data(), encoded_data.size(), - net::X509Certificate::FORMAT_AUTO); - delegate->ContinueWithCertificate(certs[0].get()); + auto certs = net::X509Certificate::CreateCertificateListFromBytes( + node::Buffer::Data(data), node::Buffer::Length(data), + net::X509Certificate::FORMAT_AUTO); + if (certs.size() > 0) + delegate->ContinueWithCertificate(certs[0].get()); } void PassLoginInformation(scoped_refptr login_handler, @@ -226,9 +229,25 @@ void App::OnLogin(LoginHandler* login_handler) { login_handler->CancelAuth(); } +void App::OnCreateWindow(const GURL& target_url, + const std::string& frame_name, + WindowOpenDisposition disposition, + int render_process_id, + int render_frame_id) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + content::RenderFrameHost* rfh = + content::RenderFrameHost::FromID(render_process_id, render_frame_id); + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost(rfh); + if (web_contents) { + auto api_web_contents = WebContents::CreateFrom(isolate(), web_contents); + api_web_contents->OnCreateWindow(target_url, frame_name, disposition); + } +} + void App::AllowCertificateError( - int pid, - int fid, + content::WebContents* web_contents, int cert_error, const net::SSLInfo& ssl_info, const GURL& request_url, @@ -238,9 +257,6 @@ void App::AllowCertificateError( bool expired_previous_decision, const base::Callback& callback, content::CertificateRequestResultType* request) { - auto rfh = content::RenderFrameHost::FromID(pid, fid); - auto web_contents = content::WebContents::FromRenderFrameHost(rfh); - v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); bool prevent_default = Emit("certificate-error", @@ -280,6 +296,12 @@ void App::OnGpuProcessCrashed(base::TerminationStatus exit_code) { Emit("gpu-process-crashed"); } +#if defined(OS_MACOSX) +void App::OnPlatformThemeChanged() { + Emit("platform-theme-changed"); +} +#endif + base::FilePath App::GetPath(mate::Arguments* args, const std::string& name) { bool succeed = false; base::FilePath path; @@ -365,6 +387,16 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( base::Bind(&Browser::ClearRecentDocuments, browser)) .SetMethod("setAppUserModelId", base::Bind(&Browser::SetAppUserModelID, browser)) + .SetMethod("setAsDefaultProtocolClient", + base::Bind(&Browser::SetAsDefaultProtocolClient, browser)) + .SetMethod("removeAsDefaultProtocolClient", + base::Bind(&Browser::RemoveAsDefaultProtocolClient, browser)) +#if defined(OS_MACOSX) + .SetMethod("hide", base::Bind(&Browser::Hide, browser)) + .SetMethod("show", base::Bind(&Browser::Show, browser)) + .SetMethod("isDarkMode", + base::Bind(&Browser::IsDarkMode, browser)) +#endif #if defined(OS_WIN) .SetMethod("setUserTasks", base::Bind(&Browser::SetUserTasks, browser)) @@ -448,6 +480,7 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("dockHide", base::Bind(&Browser::DockHide, browser)); dict.SetMethod("dockShow", base::Bind(&Browser::DockShow, browser)); dict.SetMethod("dockSetMenu", &DockSetMenu); + dict.SetMethod("dockSetIcon", base::Bind(&Browser::DockSetIcon, browser)); #endif } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index c59b9154df1..5025a3869dd 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -34,6 +34,13 @@ class App : public AtomBrowserClient::Delegate, public: static mate::Handle Create(v8::Isolate* isolate); + // Called when window with disposition needs to be created. + void OnCreateWindow(const GURL& target_url, + const std::string& frame_name, + WindowOpenDisposition disposition, + int render_process_id, + int render_frame_id); + protected: App(); virtual ~App(); @@ -52,8 +59,7 @@ class App : public AtomBrowserClient::Delegate, // content::ContentBrowserClient: void AllowCertificateError( - int render_process_id, - int render_frame_id, + content::WebContents* web_contents, int cert_error, const net::SSLInfo& ssl_info, const GURL& request_url, @@ -71,6 +77,10 @@ class App : public AtomBrowserClient::Delegate, // content::GpuDataManagerObserver: void OnGpuProcessCrashed(base::TerminationStatus exit_code) override; +#if defined(OS_MACOSX) + void OnPlatformThemeChanged() override; +#endif + // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; diff --git a/atom/browser/api/atom_api_content_tracing.cc b/atom/browser/api/atom_api_content_tracing.cc index f29946d1f46..937a87ea213 100644 --- a/atom/browser/api/atom_api_content_tracing.cc +++ b/atom/browser/api/atom_api_content_tracing.cc @@ -53,13 +53,7 @@ scoped_refptr GetTraceDataSink( void StopRecording(const base::FilePath& path, const CompletionCallback& callback) { - TracingController::GetInstance()->DisableRecording( - GetTraceDataSink(path, callback)); -} - -void CaptureMonitoringSnapshot(const base::FilePath& path, - const CompletionCallback& callback) { - TracingController::GetInstance()->CaptureMonitoringSnapshot( + TracingController::GetInstance()->StopTracing( GetTraceDataSink(path, callback)); } @@ -70,13 +64,8 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("getCategories", base::Bind( &TracingController::GetCategories, controller)); dict.SetMethod("startRecording", base::Bind( - &TracingController::EnableRecording, controller)); + &TracingController::StartTracing, controller)); dict.SetMethod("stopRecording", &StopRecording); - dict.SetMethod("startMonitoring", base::Bind( - &TracingController::EnableMonitoring, controller)); - dict.SetMethod("stopMonitoring", base::Bind( - &TracingController::DisableMonitoring, controller)); - dict.SetMethod("captureMonitoringSnapshot", &CaptureMonitoringSnapshot); dict.SetMethod("getTraceBufferUsage", base::Bind( &TracingController::GetTraceBufferUsage, controller)); dict.SetMethod("setWatchEvent", base::Bind( diff --git a/atom/browser/api/atom_api_cookies.cc b/atom/browser/api/atom_api_cookies.cc index 6323e5110c1..e49a2ee2f67 100644 --- a/atom/browser/api/atom_api_cookies.cc +++ b/atom/browser/api/atom_api_cookies.cc @@ -180,7 +180,8 @@ void SetCookieOnIO(scoped_refptr getter, GetCookieStore(getter)->GetCookieMonster()->SetCookieWithDetailsAsync( GURL(url), name, value, domain, path, expiration_time, secure, http_only, - false, net::COOKIE_PRIORITY_DEFAULT, base::Bind(OnSetCookie, callback)); + false, false, false, net::COOKIE_PRIORITY_DEFAULT, + base::Bind(OnSetCookie, callback)); } } // namespace diff --git a/atom/browser/api/atom_api_debugger.cc b/atom/browser/api/atom_api_debugger.cc new file mode 100644 index 00000000000..eab60311f3d --- /dev/null +++ b/atom/browser/api/atom_api_debugger.cc @@ -0,0 +1,195 @@ +// Copyright (c) 2016 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_debugger.h" + +#include + +#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 "atom/common/node_includes.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" +#include "native_mate/dictionary.h" +#include "native_mate/object_template_builder.h" + +using content::DevToolsAgentHost; + +namespace atom { + +namespace api { + +namespace { + +// The wrapDebugger funtion which is implemented in JavaScript. +using WrapDebuggerCallback = base::Callback)>; +WrapDebuggerCallback g_wrap_debugger; + +} // namespace + +Debugger::Debugger(content::WebContents* web_contents) + : web_contents_(web_contents), + previous_request_id_(0) { +} + +Debugger::~Debugger() { +} + +void Debugger::AgentHostClosed(DevToolsAgentHost* agent_host, + bool replaced_with_another_client) { + std::string detach_reason = "target closed"; + if (replaced_with_another_client) + detach_reason = "replaced with devtools"; + Emit("detach", detach_reason); +} + +void Debugger::DispatchProtocolMessage(DevToolsAgentHost* agent_host, + const std::string& message) { + DCHECK(agent_host == agent_host_.get()); + + scoped_ptr parsed_message(base::JSONReader::Read(message)); + if (!parsed_message->IsType(base::Value::TYPE_DICTIONARY)) + return; + + base::DictionaryValue* dict = + static_cast(parsed_message.get()); + int id; + if (!dict->GetInteger("id", &id)) { + std::string method; + if (!dict->GetString("method", &method)) + return; + base::DictionaryValue* params_value = nullptr; + base::DictionaryValue params; + if (dict->GetDictionary("params", ¶ms_value)) + params.Swap(params_value); + Emit("message", method, params); + } else { + auto send_command_callback = pending_requests_[id]; + pending_requests_.erase(id); + if (send_command_callback.is_null()) + return; + base::DictionaryValue* error_body = nullptr; + base::DictionaryValue error; + if (dict->GetDictionary("error", &error_body)) + error.Swap(error_body); + + base::DictionaryValue* result_body = nullptr; + base::DictionaryValue result; + if (dict->GetDictionary("result", &result_body)) + result.Swap(result_body); + send_command_callback.Run(error, result); + } +} + +void Debugger::Attach(mate::Arguments* args) { + std::string protocol_version; + args->GetNext(&protocol_version); + + if (!protocol_version.empty() && + !DevToolsAgentHost::IsSupportedProtocolVersion(protocol_version)) { + args->ThrowError("Requested protocol version is not supported"); + return; + } + agent_host_ = DevToolsAgentHost::GetOrCreateFor(web_contents_); + if (!agent_host_.get()) { + args->ThrowError("No target available"); + return; + } + if (agent_host_->IsAttached()) { + args->ThrowError("Another debugger is already attached to this target"); + return; + } + + agent_host_->AttachClient(this); +} + +bool Debugger::IsAttached() { + return agent_host_.get() ? agent_host_->IsAttached() : false; +} + +void Debugger::Detach() { + if (!agent_host_.get()) + return; + agent_host_->DetachClient(); + AgentHostClosed(agent_host_.get(), false); + agent_host_ = nullptr; +} + +void Debugger::SendCommand(mate::Arguments* args) { + if (!agent_host_.get()) + return; + + std::string method; + if (!args->GetNext(&method)) { + args->ThrowError(); + return; + } + base::DictionaryValue command_params; + args->GetNext(&command_params); + SendCommandCallback callback; + args->GetNext(&callback); + + base::DictionaryValue request; + int request_id = ++previous_request_id_; + pending_requests_[request_id] = callback; + request.SetInteger("id", request_id); + request.SetString("method", method); + if (!command_params.empty()) + request.Set("params", command_params.DeepCopy()); + + std::string json_args; + base::JSONWriter::Write(request, &json_args); + agent_host_->DispatchProtocolMessage(json_args); +} + +// static +mate::Handle Debugger::Create( + v8::Isolate* isolate, + content::WebContents* web_contents) { + auto handle = mate::CreateHandle(isolate, new Debugger(web_contents)); + g_wrap_debugger.Run(handle.ToV8()); + return handle; +} + +// static +void Debugger::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype) + .SetMethod("attach", &Debugger::Attach) + .SetMethod("isAttached", &Debugger::IsAttached) + .SetMethod("detach", &Debugger::Detach) + .SetMethod("sendCommand", &Debugger::SendCommand); +} + +void ClearWrapDebugger() { + g_wrap_debugger.Reset(); +} + +void SetWrapDebugger(const WrapDebuggerCallback& callback) { + g_wrap_debugger = callback; + + // Cleanup the wrapper on exit. + atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback( + base::Bind(ClearWrapDebugger)); +} + +} // namespace api + +} // namespace atom + +namespace { + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.SetMethod("_setWrapDebugger", &atom::api::SetWrapDebugger); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_debugger, Initialize); diff --git a/atom/browser/api/atom_api_debugger.h b/atom/browser/api/atom_api_debugger.h new file mode 100644 index 00000000000..5454108e8b0 --- /dev/null +++ b/atom/browser/api/atom_api_debugger.h @@ -0,0 +1,75 @@ +// Copyright (c) 2016 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_DEBUGGER_H_ +#define ATOM_BROWSER_API_ATOM_API_DEBUGGER_H_ + +#include +#include + +#include "atom/browser/api/trackable_object.h" +#include "base/callback.h" +#include "base/values.h" +#include "content/public/browser/devtools_agent_host_client.h" +#include "native_mate/handle.h" + +namespace content { +class DevToolsAgentHost; +class WebContents; +} + +namespace mate { +class Arguments; +} + +namespace atom { + +namespace api { + +class Debugger: public mate::TrackableObject, + public content::DevToolsAgentHostClient { + public: + using SendCommandCallback = + base::Callback; + + static mate::Handle Create( + v8::Isolate* isolate, content::WebContents* web_contents); + + // mate::TrackableObject: + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + protected: + explicit Debugger(content::WebContents* web_contents); + ~Debugger(); + + // content::DevToolsAgentHostClient: + void AgentHostClosed(content::DevToolsAgentHost* agent_host, + bool replaced_with_another_client) override; + void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host, + const std::string& message) override; + + private: + using PendingRequestMap = std::map; + + void Attach(mate::Arguments* args); + bool IsAttached(); + void Detach(); + void SendCommand(mate::Arguments* args); + + content::WebContents* web_contents_; // Weak Reference. + scoped_refptr agent_host_; + + PendingRequestMap pending_requests_; + int previous_request_id_; + + DISALLOW_COPY_AND_ASSIGN(Debugger); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_DEBUGGER_H_ diff --git a/atom/browser/api/atom_api_desktop_capturer.cc b/atom/browser/api/atom_api_desktop_capturer.cc index ceb69deca45..cdae6f0c44c 100644 --- a/atom/browser/api/atom_api_desktop_capturer.cc +++ b/atom/browser/api/atom_api_desktop_capturer.cc @@ -5,7 +5,6 @@ #include "atom/browser/api/atom_api_desktop_capturer.h" #include "atom/common/api/atom_api_native_image.h" -#include "atom/common/node_includes.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/media/desktop_media_list.h" @@ -14,6 +13,8 @@ #include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" #include "third_party/webrtc/modules/desktop_capture/window_capturer.h" +#include "atom/common/node_includes.h" + namespace mate { template<> @@ -63,8 +64,8 @@ void DesktopCapturer::StartHandling(bool capture_window, capture_screen ? webrtc::ScreenCapturer::Create(options) : nullptr); scoped_ptr window_capturer( capture_window ? webrtc::WindowCapturer::Create(options) : nullptr); - media_list_.reset(new NativeDesktopMediaList(screen_capturer.Pass(), - window_capturer.Pass())); + media_list_.reset(new NativeDesktopMediaList( + std::move(screen_capturer), std::move(window_capturer))); media_list_->SetThumbnailSize(thumbnail_size); media_list_->StartUpdating(this); diff --git a/atom/browser/api/atom_api_download_item.cc b/atom/browser/api/atom_api_download_item.cc index f186821e7d3..3edd5f9c254 100644 --- a/atom/browser/api/atom_api_download_item.cc +++ b/atom/browser/api/atom_api_download_item.cc @@ -12,6 +12,7 @@ #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/node_includes.h" #include "base/memory/linked_ptr.h" +#include "base/message_loop/message_loop.h" #include "base/strings/utf_string_conversions.h" #include "native_mate/dictionary.h" #include "net/base/filename_util.h" @@ -47,80 +48,49 @@ namespace atom { namespace api { namespace { + // The wrapDownloadItem funtion which is implemented in JavaScript using WrapDownloadItemCallback = base::Callback)>; WrapDownloadItemCallback g_wrap_download_item; -char kDownloadItemSavePathKey[] = "DownloadItemSavePathKey"; +std::map>> g_download_item_objects; -std::map>> g_download_item_objects; } // namespace -DownloadItem::SavePathData::SavePathData(const base::FilePath& path) : - path_(path) { -} - -const base::FilePath& DownloadItem::SavePathData::path() { - return path_; -} - -DownloadItem::DownloadItem(content::DownloadItem* download_item) : - download_item_(download_item) { +DownloadItem::DownloadItem(content::DownloadItem* download_item) + : download_item_(download_item) { download_item_->AddObserver(this); + AttachAsUserData(download_item); } DownloadItem::~DownloadItem() { - if (download_item_) - OnDownloadDestroyed(download_item_); + if (download_item_) { + // Destroyed by either garbage collection or destroy(). + download_item_->RemoveObserver(this); + download_item_->Remove(); + } + + // Remove from the global map. + auto iter = g_download_item_objects.find(weak_map_id()); + if (iter != g_download_item_objects.end()) + g_download_item_objects.erase(iter); } void DownloadItem::OnDownloadUpdated(content::DownloadItem* item) { - download_item_->IsDone() ? Emit("done", item->GetState()) : Emit("updated"); + if (download_item_->IsDone()) { + Emit("done", item->GetState()); + + // Destroy the item once item is downloaded. + base::MessageLoop::current()->PostTask(FROM_HERE, GetDestroyClosure()); + } else { + Emit("updated"); + } } void DownloadItem::OnDownloadDestroyed(content::DownloadItem* download_item) { - download_item_->RemoveObserver(this); - auto iter = g_download_item_objects.find(download_item_->GetId()); - if (iter != g_download_item_objects.end()) - g_download_item_objects.erase(iter); download_item_ = nullptr; -} - -int64 DownloadItem::GetReceivedBytes() { - return download_item_->GetReceivedBytes(); -} - -int64 DownloadItem::GetTotalBytes() { - return download_item_->GetTotalBytes(); -} - -const GURL& DownloadItem::GetURL() { - return download_item_->GetURL(); -} - -std::string DownloadItem::GetMimeType() { - return download_item_->GetMimeType(); -} - -bool DownloadItem::HasUserGesture() { - return download_item_->HasUserGesture(); -} - -std::string DownloadItem::GetFilename() { - return base::UTF16ToUTF8(net::GenerateFileName(GetURL(), - GetContentDisposition(), - std::string(), - download_item_->GetSuggestedFilename(), - GetMimeType(), - std::string()).LossyDisplayName()); -} - -std::string DownloadItem::GetContentDisposition() { - return download_item_->GetContentDisposition(); -} - -void DownloadItem::SetSavePath(const base::FilePath& path) { - download_item_->SetUserData(UserDataKey(), new SavePathData(path)); + // Destroy the native class immediately when downloadItem is destroyed. + delete this; } void DownloadItem::Pause() { @@ -133,6 +103,48 @@ void DownloadItem::Resume() { void DownloadItem::Cancel() { download_item_->Cancel(true); + download_item_->Remove(); +} + +int64_t DownloadItem::GetReceivedBytes() const { + return download_item_->GetReceivedBytes(); +} + +int64_t DownloadItem::GetTotalBytes() const { + return download_item_->GetTotalBytes(); +} + +std::string DownloadItem::GetMimeType() const { + return download_item_->GetMimeType(); +} + +bool DownloadItem::HasUserGesture() const { + return download_item_->HasUserGesture(); +} + +std::string DownloadItem::GetFilename() const { + return base::UTF16ToUTF8(net::GenerateFileName(GetURL(), + GetContentDisposition(), + std::string(), + download_item_->GetSuggestedFilename(), + GetMimeType(), + std::string()).LossyDisplayName()); +} + +std::string DownloadItem::GetContentDisposition() const { + return download_item_->GetContentDisposition(); +} + +const GURL& DownloadItem::GetURL() const { + return download_item_->GetURL(); +} + +void DownloadItem::SetSavePath(const base::FilePath& path) { + save_path_ = path; +} + +base::FilePath DownloadItem::GetSavePath() const { + return save_path_; } // static @@ -145,29 +157,31 @@ void DownloadItem::BuildPrototype(v8::Isolate* isolate, .SetMethod("cancel", &DownloadItem::Cancel) .SetMethod("getReceivedBytes", &DownloadItem::GetReceivedBytes) .SetMethod("getTotalBytes", &DownloadItem::GetTotalBytes) - .SetMethod("getURL", &DownloadItem::GetURL) .SetMethod("getMimeType", &DownloadItem::GetMimeType) .SetMethod("hasUserGesture", &DownloadItem::HasUserGesture) .SetMethod("getFilename", &DownloadItem::GetFilename) .SetMethod("getContentDisposition", &DownloadItem::GetContentDisposition) - .SetMethod("setSavePath", &DownloadItem::SetSavePath); + .SetMethod("getURL", &DownloadItem::GetURL) + .SetMethod("setSavePath", &DownloadItem::SetSavePath) + .SetMethod("getSavePath", &DownloadItem::GetSavePath); } // static mate::Handle DownloadItem::Create( v8::Isolate* isolate, content::DownloadItem* item) { + auto existing = TrackableObject::FromWrappedClass(isolate, item); + if (existing) + return mate::CreateHandle(isolate, static_cast(existing)); + auto handle = mate::CreateHandle(isolate, new DownloadItem(item)); g_wrap_download_item.Run(handle.ToV8()); - g_download_item_objects[item->GetId()] = make_linked_ptr( + + // Reference this object in case it got garbage collected. + g_download_item_objects[handle->weak_map_id()] = make_linked_ptr( new v8::Global(isolate, handle.ToV8())); return handle; } -// static -void* DownloadItem::UserDataKey() { - return &kDownloadItemSavePathKey; -} - void ClearWrapDownloadItem() { g_wrap_download_item.Reset(); } diff --git a/atom/browser/api/atom_api_download_item.h b/atom/browser/api/atom_api_download_item.h index 471913c2266..64469b9b34d 100644 --- a/atom/browser/api/atom_api_download_item.h +++ b/atom/browser/api/atom_api_download_item.h @@ -20,22 +20,26 @@ namespace api { class DownloadItem : public mate::TrackableObject, public content::DownloadItem::Observer { public: - class SavePathData : public base::SupportsUserData::Data { - public: - explicit SavePathData(const base::FilePath& path); - const base::FilePath& path(); - private: - base::FilePath path_; - }; - static mate::Handle Create(v8::Isolate* isolate, content::DownloadItem* item); - static void* UserDataKey(); // mate::TrackableObject: static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); + void Pause(); + void Resume(); + void Cancel(); + int64_t GetReceivedBytes() const; + int64_t GetTotalBytes() const; + std::string GetMimeType() const; + bool HasUserGesture() const; + std::string GetFilename() const; + std::string GetContentDisposition() const; + const GURL& GetURL() const; + void SetSavePath(const base::FilePath& path); + base::FilePath GetSavePath() const; + protected: explicit DownloadItem(content::DownloadItem* download_item); ~DownloadItem(); @@ -44,19 +48,8 @@ class DownloadItem : public mate::TrackableObject, void OnDownloadUpdated(content::DownloadItem* download) override; void OnDownloadDestroyed(content::DownloadItem* download) override; - void Pause(); - void Resume(); - void Cancel(); - int64 GetReceivedBytes(); - int64 GetTotalBytes(); - std::string GetMimeType(); - bool HasUserGesture(); - std::string GetFilename(); - std::string GetContentDisposition(); - const GURL& GetURL(); - void SetSavePath(const base::FilePath& path); - private: + base::FilePath save_path_; content::DownloadItem* download_item_; DISALLOW_COPY_AND_ASSIGN(DownloadItem); diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 96cba3fe991..e40ba17f464 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -169,8 +169,7 @@ void Menu::BuildPrototype(v8::Isolate* isolate, .SetMethod("isItemCheckedAt", &Menu::IsItemCheckedAt) .SetMethod("isEnabledAt", &Menu::IsEnabledAt) .SetMethod("isVisibleAt", &Menu::IsVisibleAt) - .SetMethod("_popup", &Menu::Popup) - .SetMethod("_popupAt", &Menu::PopupAt); + .SetMethod("popupAt", &Menu::PopupAt); } } // namespace api diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 17bb9073a71..1ae708863a7 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -51,8 +51,9 @@ class Menu : public mate::TrackableObject, void ExecuteCommand(int command_id, int event_flags) override; void MenuWillShow(ui::SimpleMenuModel* source) override; - virtual void Popup(Window* window) = 0; - virtual void PopupAt(Window* window, int x, int y) = 0; + virtual void PopupAt(Window* window, + int x = -1, int y = -1, + int positioning_item = 0) = 0; scoped_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 5a086776a63..85227fa2a9d 100644 --- a/atom/browser/api/atom_api_menu_mac.h +++ b/atom/browser/api/atom_api_menu_mac.h @@ -19,8 +19,7 @@ class MenuMac : public Menu { protected: MenuMac(); - void Popup(Window* window) override; - void PopupAt(Window* window, int x, int y) override; + void PopupAt(Window* window, int x, int y, int positioning_item = 0) override; base::scoped_nsobject menu_controller_; diff --git a/atom/browser/api/atom_api_menu_mac.mm b/atom/browser/api/atom_api_menu_mac.mm index 071753218d6..71c677b0476 100644 --- a/atom/browser/api/atom_api_menu_mac.mm +++ b/atom/browser/api/atom_api_menu_mac.mm @@ -18,39 +18,7 @@ namespace api { MenuMac::MenuMac() { } -void MenuMac::Popup(Window* window) { - NativeWindow* native_window = window->window(); - if (!native_window) - return; - content::WebContents* web_contents = native_window->web_contents(); - if (!web_contents) - return; - - NSWindow* nswindow = native_window->GetNativeWindow(); - base::scoped_nsobject menu_controller( - [[AtomMenuController alloc] initWithModel:model_.get()]); - - // Fake out a context menu event. - NSEvent* currentEvent = [NSApp currentEvent]; - NSPoint position = [nswindow mouseLocationOutsideOfEventStream]; - NSTimeInterval eventTime = [currentEvent timestamp]; - NSEvent* clickEvent = [NSEvent mouseEventWithType:NSRightMouseDown - location:position - modifierFlags:NSRightMouseDownMask - timestamp:eventTime - windowNumber:[nswindow windowNumber] - context:nil - eventNumber:0 - clickCount:1 - pressure:1.0]; - - // Show the menu. - [NSMenu popUpContextMenu:[menu_controller menu] - withEvent:clickEvent - forView:web_contents->GetContentNativeView()]; -} - -void MenuMac::PopupAt(Window* window, int x, int y) { +void MenuMac::PopupAt(Window* window, int x, int y, int positioning_item) { NativeWindow* native_window = window->window(); if (!native_window) return; @@ -63,10 +31,23 @@ void MenuMac::PopupAt(Window* window, int x, int y) { NSMenu* menu = [menu_controller menu]; NSView* view = web_contents->GetContentNativeView(); + // Which menu item to show. + NSMenuItem* item = nil; + if (positioning_item < [menu numberOfItems] && positioning_item >= 0) + item = [menu itemAtIndex:positioning_item]; + + // (-1, -1) means showing on mouse location. + NSPoint position; + if (x == -1 || y == -1) { + NSWindow* nswindow = native_window->GetNativeWindow(); + position = [view convertPoint:[nswindow mouseLocationOutsideOfEventStream] + fromView:nil]; + } else { + position = NSMakePoint(x, [view frame].size.height - y); + } + // Show the menu. - [menu popUpMenuPositioningItem:[menu itemAtIndex:0] - atLocation:NSMakePoint(x, [view frame].size.height - y) - inView:view]; + [menu popUpMenuPositioningItem:item atLocation:position inView:view]; } // static diff --git a/atom/browser/api/atom_api_menu_views.cc b/atom/browser/api/atom_api_menu_views.cc index 45904dd8c03..4a3a97dd906 100644 --- a/atom/browser/api/atom_api_menu_views.cc +++ b/atom/browser/api/atom_api_menu_views.cc @@ -16,11 +16,7 @@ namespace api { MenuViews::MenuViews() { } -void MenuViews::Popup(Window* window) { - PopupAtPoint(window, gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); -} - -void MenuViews::PopupAt(Window* window, int x, int y) { +void MenuViews::PopupAt(Window* window, int x, int y, int positioning_item) { NativeWindow* native_window = static_cast(window->window()); if (!native_window) return; @@ -31,18 +27,23 @@ void MenuViews::PopupAt(Window* window, int x, int y) { if (!view) return; - gfx::Point origin = view->GetViewBounds().origin(); - PopupAtPoint(window, gfx::Point(origin.x() + x, origin.y() + y)); -} + // (-1, -1) means showing on mouse location. + gfx::Point location; + if (x == -1 || y == -1) { + location = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint(); + } else { + gfx::Point origin = view->GetViewBounds().origin(); + location = gfx::Point(origin.x() + x, origin.y() + y); + } -void MenuViews::PopupAtPoint(Window* window, const gfx::Point& point) { + // Show the menu. views::MenuRunner menu_runner( model(), views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); ignore_result(menu_runner.RunMenuAt( static_cast(window->window())->widget(), NULL, - gfx::Rect(point, gfx::Size()), + gfx::Rect(location, gfx::Size()), views::MENU_ANCHOR_TOPLEFT, ui::MENU_SOURCE_MOUSE)); } diff --git a/atom/browser/api/atom_api_menu_views.h b/atom/browser/api/atom_api_menu_views.h index de0d2e8b014..e4d17c77ca6 100644 --- a/atom/browser/api/atom_api_menu_views.h +++ b/atom/browser/api/atom_api_menu_views.h @@ -17,12 +17,9 @@ class MenuViews : public Menu { MenuViews(); protected: - void Popup(Window* window) override; - void PopupAt(Window* window, int x, int y) override; + void PopupAt(Window* window, int x, int y, int positioning_item = 0) override; private: - void PopupAtPoint(Window* window, const gfx::Point& point); - DISALLOW_COPY_AND_ASSIGN(MenuViews); }; diff --git a/atom/browser/api/atom_api_protocol.h b/atom/browser/api/atom_api_protocol.h index 8aef406fbc3..107fbf1ce71 100644 --- a/atom/browser/api/atom_api_protocol.h +++ b/atom/browser/api/atom_api_protocol.h @@ -117,7 +117,7 @@ class Protocol : public mate::Wrappable { scoped_ptr> protocol_handler( new CustomProtocolHandler( isolate(), request_context_getter_, handler)); - if (job_factory_->SetProtocolHandler(scheme, protocol_handler.Pass())) + if (job_factory_->SetProtocolHandler(scheme, std::move(protocol_handler))) return PROTOCOL_OK; else return PROTOCOL_FAIL; @@ -161,7 +161,7 @@ class Protocol : public mate::Wrappable { isolate(), request_context_getter_, handler)); original_protocols_.set( scheme, - job_factory_->ReplaceProtocol(scheme, protocol_handler.Pass())); + job_factory_->ReplaceProtocol(scheme, std::move(protocol_handler))); return PROTOCOL_OK; } diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 1cc76ebfa23..0f104c76072 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -14,8 +14,10 @@ #include "atom/browser/api/save_page_handler.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/atom_permission_manager.h" #include "atom/browser/net/atom_cert_verifier.h" #include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/content_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/net_converter.h" @@ -34,6 +36,7 @@ #include "native_mate/object_template_builder.h" #include "net/base/load_flags.h" #include "net/disk_cache/disk_cache.h" +#include "net/dns/host_cache.h" #include "net/proxy/proxy_service.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/url_request/url_request_context.h" @@ -46,12 +49,12 @@ namespace { struct ClearStorageDataOptions { GURL origin; - uint32 storage_types = StoragePartition::REMOVE_DATA_MASK_ALL; - uint32 quota_types = StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL; + uint32_t storage_types = StoragePartition::REMOVE_DATA_MASK_ALL; + uint32_t quota_types = StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL; }; -uint32 GetStorageMask(const std::vector& storage_types) { - uint32 storage_mask = 0; +uint32_t GetStorageMask(const std::vector& storage_types) { + uint32_t storage_mask = 0; for (const auto& it : storage_types) { auto type = base::ToLowerASCII(it); if (type == "appcache") @@ -74,8 +77,8 @@ uint32 GetStorageMask(const std::vector& storage_types) { return storage_mask; } -uint32 GetQuotaMask(const std::vector& quota_types) { - uint32 quota_mask = 0; +uint32_t GetQuotaMask(const std::vector& quota_types) { + uint32_t quota_mask = 0; for (const auto& it : quota_types) { auto type = base::ToLowerASCII(it); if (type == "temporary") @@ -269,6 +272,19 @@ void SetProxyInIO(net::URLRequestContextGetter* getter, RunCallbackInUI(callback); } +void ClearHostResolverCacheInIO( + const scoped_refptr& context_getter, + const base::Closure& callback) { + auto request_context = context_getter->GetURLRequestContext(); + auto cache = request_context->host_resolver()->GetHostCache(); + if (cache) { + cache->clear(); + DCHECK_EQ(0u, cache->size()); + if (!callback.is_null()) + RunCallbackInUI(callback); + } +} + } // namespace Session::Session(AtomBrowserContext* browser_context) @@ -397,6 +413,28 @@ void Session::SetCertVerifyProc(v8::Local val, browser_context_->cert_verifier()->SetVerifyProc(proc); } +void Session::SetPermissionRequestHandler(v8::Local val, + mate::Arguments* args) { + AtomPermissionManager::RequestHandler handler; + if (!(val->IsNull() || mate::ConvertFromV8(args->isolate(), val, &handler))) { + args->ThrowError("Must pass null or function"); + return; + } + auto permission_manager = static_cast( + browser_context()->GetPermissionManager()); + permission_manager->SetPermissionRequestHandler(handler); +} + +void Session::ClearHostResolverCache(mate::Arguments* args) { + base::Closure callback; + args->GetNext(&callback); + + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&ClearHostResolverCacheInIO, + make_scoped_refptr(browser_context_->GetRequestContext()), + callback)); +} + v8::Local Session::Cookies(v8::Isolate* isolate) { if (cookies_.IsEmpty()) { auto handle = atom::api::Cookies::Create(isolate, browser_context()); @@ -448,6 +486,9 @@ void Session::BuildPrototype(v8::Isolate* isolate, .SetMethod("enableNetworkEmulation", &Session::EnableNetworkEmulation) .SetMethod("disableNetworkEmulation", &Session::DisableNetworkEmulation) .SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc) + .SetMethod("setPermissionRequestHandler", + &Session::SetPermissionRequestHandler) + .SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache) .SetProperty("cookies", &Session::Cookies) .SetProperty("webRequest", &Session::WebRequest); } diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 37a5a45a6c9..02d8ba5cdec 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -76,6 +76,9 @@ class Session: public mate::TrackableObject, void EnableNetworkEmulation(const mate::Dictionary& options); void DisableNetworkEmulation(); void SetCertVerifyProc(v8::Local proc, mate::Arguments* args); + void SetPermissionRequestHandler(v8::Local val, + mate::Arguments* args); + void ClearHostResolverCache(mate::Arguments* args); v8::Local Cookies(v8::Isolate* isolate); v8::Local WebRequest(v8::Isolate* isolate); diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 0173abf4eef..8c0f7d571f3 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -7,12 +7,14 @@ #include #include +#include "atom/browser/api/atom_api_debugger.h" #include "atom/browser/api/atom_api_session.h" #include "atom/browser/api/atom_api_window.h" #include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/native_window.h" +#include "atom/browser/web_contents_permission_helper.h" #include "atom/browser/web_contents_preferences.h" #include "atom/browser/web_view_guest_delegate.h" #include "atom/common/api/api_messages.h" @@ -26,6 +28,7 @@ #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/mouse_util.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "brightray/browser/inspectable_web_contents.h" @@ -41,6 +44,7 @@ #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/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/resource_request_details.h" #include "content/public/browser/service_worker_context.h" @@ -147,7 +151,7 @@ struct Converter { } else { scoped_ptr values(new base::ListValue()); values->AppendString(value); - response_headers.Set(key, values.Pass()); + response_headers.Set(key, std::move(values)); } } } @@ -215,7 +219,8 @@ WebContents::WebContents(content::WebContents* web_contents) WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options) - : request_id_(0) { + : embedder_(nullptr), + request_id_(0) { // Whether it is a guest WebContents. bool is_guest = false; options.Get("isGuest", &is_guest); @@ -261,16 +266,19 @@ WebContents::WebContents(v8::Isolate* isolate, // Save the preferences in C++. new WebContentsPreferences(web_contents, options); + // Intialize permission helper. + WebContentsPermissionHelper::CreateForWebContents(web_contents); + web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); if (is_guest) { guest_delegate_->Initialize(this); NativeWindow* owner_window = nullptr; - WebContents* embedder = nullptr; - if (options.Get("embedder", &embedder) && embedder) { + if (options.Get("embedder", &embedder_) && embedder_) { // New WebContents's owner_window is the embedder's owner_window. - auto relay = NativeWindowRelay::FromWebContents(embedder->web_contents()); + auto relay = + NativeWindowRelay::FromWebContents(embedder_->web_contents()); if (relay) owner_window = relay->window.get(); } @@ -290,14 +298,15 @@ WebContents::~WebContents() { // 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(); } } bool WebContents::AddMessageToConsole(content::WebContents* source, - int32 level, + int32_t level, const base::string16& message, - int32 line_no, + int32_t line_no, const base::string16& source_id) { if (type_ == BROWSER_WINDOW) { return false; @@ -307,20 +316,13 @@ bool WebContents::AddMessageToConsole(content::WebContents* source, } } -bool WebContents::ShouldCreateWebContents( - content::WebContents* web_contents, - int route_id, - int main_frame_route_id, - WindowContainerType window_container_type, - const std::string& frame_name, - const GURL& target_url, - const std::string& partition_id, - content::SessionStorageNamespace* session_storage_namespace) { +void WebContents::OnCreateWindow(const GURL& target_url, + const std::string& frame_name, + WindowOpenDisposition disposition) { if (type_ == BROWSER_WINDOW) - Emit("-new-window", target_url, frame_name, NEW_FOREGROUND_TAB); + Emit("-new-window", target_url, frame_name, disposition); else - Emit("new-window", target_url, frame_name, NEW_FOREGROUND_TAB); - return false; + Emit("new-window", target_url, frame_name, disposition); } content::WebContents* WebContents::OpenURLFromTab( @@ -385,6 +387,18 @@ void WebContents::HandleKeyboardEvent( void WebContents::EnterFullscreenModeForTab(content::WebContents* source, const GURL& origin) { + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(source); + auto callback = base::Bind(&WebContents::OnEnterFullscreenModeForTab, + base::Unretained(this), source, origin); + permission_helper->RequestFullscreenPermission(callback); +} + +void WebContents::OnEnterFullscreenModeForTab(content::WebContents* source, + const GURL& origin, + bool allowed) { + if (!allowed) + return; CommonWebContentsDelegate::EnterFullscreenModeForTab(source, origin); Emit("enter-html-full-screen"); } @@ -434,6 +448,7 @@ void WebContents::FindReply(content::WebContents* web_contents, result.Set("requestId", request_id); result.Set("selectionArea", selection_rect); result.Set("finalUpdate", final_update); + result.Set("activeMatchOrdinal", active_match_ordinal); Emit("found-in-page", result); } else if (final_update) { result.Set("requestId", request_id); @@ -443,23 +458,38 @@ void WebContents::FindReply(content::WebContents* web_contents, } } +bool WebContents::CheckMediaAccessPermission( + content::WebContents* web_contents, + const GURL& security_origin, + content::MediaStreamType type) { + return true; +} + +void WebContents::RequestMediaAccessPermission( + content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback) { + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(web_contents); + permission_helper->RequestMediaAccessPermission(request, callback); +} + +void WebContents::RequestToLockMouse( + content::WebContents* web_contents, + bool user_gesture, + bool last_unlocked_by_target) { + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(web_contents); + permission_helper->RequestPointerLockPermission(user_gesture); +} + void WebContents::BeforeUnloadFired(const base::TimeTicks& proceed_time) { // Do nothing, we override this method just to avoid compilation error since // there are two virtual functions named BeforeUnloadFired. } void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) { - int process_id = render_view_host->GetProcess()->GetID(); - Emit("render-view-deleted", process_id); - - // process.emit('ATOM_BROWSER_RELEASE_RENDER_VIEW', processId); - // Tell the rpc server that a render view has been deleted and we need to - // release all objects owned by it. - v8::Locker locker(isolate()); - v8::HandleScope handle_scope(isolate()); - node::Environment* env = node::Environment::GetCurrent(isolate()); - mate::EmitEvent(isolate(), env->process_object(), - "ATOM_BROWSER_RELEASE_RENDER_VIEW", process_id); + Emit("render-view-deleted", render_view_host->GetProcess()->GetID()); } void WebContents::RenderProcessGone(base::TerminationStatus status) { @@ -474,11 +504,11 @@ void WebContents::PluginCrashed(const base::FilePath& plugin_path, Emit("plugin-crashed", info.name, info.version); } -void WebContents::MediaStartedPlaying() { +void WebContents::MediaStartedPlaying(const MediaPlayerId& id) { Emit("media-started-playing"); } -void WebContents::MediaPaused() { +void WebContents::MediaStoppedPlaying(const MediaPlayerId& id) { Emit("media-paused"); } @@ -618,6 +648,8 @@ 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_CODE(ViewHostMsg_SetCursor, OnCursorChange, + handled = false) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -637,9 +669,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() { - // The RenderViewDeleted was not called when the WebContents is destroyed. - RenderViewDeleted(web_contents()->GetRenderViewHost()); - // This event is only for internal use, which is emitted when WebContents is // being destroyed. Emit("will-destroy"); @@ -672,6 +701,14 @@ bool WebContents::Equal(const WebContents* web_contents) const { } void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { + if (!url.is_valid()) { + Emit("did-fail-load", + static_cast(net::ERR_INVALID_URL), + net::ErrorToShortString(net::ERR_INVALID_URL), + url.possibly_invalid_spec()); + return; + } + content::NavigationController::LoadURLParams params(url); GURL http_referrer; @@ -956,8 +993,8 @@ void WebContents::ReplaceMisspelling(const base::string16& word) { web_contents()->ReplaceMisspelling(word); } -uint32 WebContents::FindInPage(mate::Arguments* args) { - uint32 request_id = GetNextRequestId(); +uint32_t WebContents::FindInPage(mate::Arguments* args) { + uint32_t request_id = GetNextRequestId(); base::string16 search_text; blink::WebFindOptions options; if (!args->GetNext(&search_text) || search_text.empty()) { @@ -1027,8 +1064,8 @@ void WebContents::BeginFrameSubscription( const auto view = web_contents()->GetRenderWidgetHostView(); if (view) { scoped_ptr frame_subscriber(new FrameSubscriber( - isolate(), view->GetVisibleViewportSize(), callback)); - view->BeginFrameSubscription(frame_subscriber.Pass()); + isolate(), view, callback)); + view->BeginFrameSubscription(std::move(frame_subscriber)); } } @@ -1038,16 +1075,24 @@ void WebContents::EndFrameSubscription() { view->EndFrameSubscription(); } +void WebContents::OnCursorChange(const content::WebCursor& cursor) { + content::WebCursor::CursorInfo info; + cursor.GetCursorInfo(&info); + + if (cursor.IsCustom()) { + Emit("cursor-changed", CursorTypeToString(info), + gfx::Image::CreateFrom1xBitmap(info.custom_image), + info.image_scale_factor); + } else { + Emit("cursor-changed", CursorTypeToString(info)); + } +} + void WebContents::SetSize(const SetSizeParams& params) { if (guest_delegate_) guest_delegate_->SetSize(params); } -void WebContents::SetAllowTransparency(bool allow) { - if (guest_delegate_) - guest_delegate_->SetAllowTransparency(allow); -} - bool WebContents::IsGuest() const { return type_ == WEB_VIEW; } @@ -1069,6 +1114,12 @@ v8::Local WebContents::Session(v8::Isolate* isolate) { return v8::Local::New(isolate, session_); } +content::WebContents* WebContents::HostWebContents() { + if (!embedder_) + return nullptr; + return embedder_->web_contents(); +} + v8::Local WebContents::DevToolsWebContents(v8::Isolate* isolate) { if (devtools_web_contents_.IsEmpty()) return v8::Null(isolate); @@ -1076,6 +1127,14 @@ v8::Local WebContents::DevToolsWebContents(v8::Isolate* isolate) { return v8::Local::New(isolate, devtools_web_contents_); } +v8::Local WebContents::Debugger(v8::Isolate* isolate) { + if (debugger_.IsEmpty()) { + auto handle = atom::api::Debugger::Create(isolate, web_contents()); + debugger_.Reset(isolate, handle.ToV8()); + } + return v8::Local::New(isolate, debugger_); +} + // static void WebContents::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { @@ -1131,7 +1190,6 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, &WebContents::BeginFrameSubscription) .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription) .SetMethod("setSize", &WebContents::SetSize) - .SetMethod("setAllowTransparency", &WebContents::SetAllowTransparency) .SetMethod("isGuest", &WebContents::IsGuest) .SetMethod("getWebPreferences", &WebContents::GetWebPreferences) .SetMethod("getOwnerBrowserWindow", &WebContents::GetOwnerBrowserWindow) @@ -1144,7 +1202,9 @@ void WebContents::BuildPrototype(v8::Isolate* isolate, .SetMethod("addWorkSpace", &WebContents::AddWorkSpace) .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) .SetProperty("session", &WebContents::Session) - .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents); + .SetProperty("hostWebContents", &WebContents::HostWebContents) + .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents) + .SetProperty("debugger", &WebContents::Debugger); } AtomBrowserContext* WebContents::GetBrowserContext() const { diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index bcef57b9a4a..5fb1947f579 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -14,6 +14,7 @@ #include "atom/browser/common_web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/common/favicon_url.h" +#include "content/common/cursors/webcursor.h" #include "native_mate/handle.h" #include "ui/gfx/image/image.h" @@ -109,7 +110,7 @@ class WebContents : public mate::TrackableObject, void Unselect(); void Replace(const base::string16& word); void ReplaceMisspelling(const base::string16& word); - uint32 FindInPage(mate::Arguments* args); + uint32_t FindInPage(mate::Arguments* args); void StopFindInPage(content::StopFindAction action); // Focus. @@ -130,9 +131,18 @@ class WebContents : public mate::TrackableObject, // Methods for creating . void SetSize(const SetSizeParams& params); - void SetAllowTransparency(bool allow); bool IsGuest() const; + // Callback triggered on permission response. + void OnEnterFullscreenModeForTab(content::WebContents* source, + const GURL& origin, + bool allowed); + + // Create window with the given disposition. + void OnCreateWindow(const GURL& target_url, + const std::string& frame_name, + WindowOpenDisposition disposition); + // Returns the web preferences of current WebContents. v8::Local GetWebPreferences(v8::Isolate* isolate); @@ -141,7 +151,9 @@ class WebContents : public mate::TrackableObject, // Properties. v8::Local Session(v8::Isolate* isolate); + content::WebContents* HostWebContents(); v8::Local DevToolsWebContents(v8::Isolate* isolate); + v8::Local Debugger(v8::Isolate* isolate); // mate::TrackableObject: static void BuildPrototype(v8::Isolate* isolate, @@ -154,19 +166,10 @@ class WebContents : public mate::TrackableObject, // content::WebContentsDelegate: bool AddMessageToConsole(content::WebContents* source, - int32 level, + int32_t level, const base::string16& message, - int32 line_no, + int32_t line_no, const base::string16& source_id) override; - bool ShouldCreateWebContents( - content::WebContents* web_contents, - int route_id, - int main_frame_route_id, - WindowContainerType window_container_type, - const std::string& frame_name, - const GURL& target_url, - const std::string& partition_id, - content::SessionStorageNamespace* session_storage_namespace) override; content::WebContents* OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) override; @@ -194,6 +197,18 @@ class WebContents : public mate::TrackableObject, const gfx::Rect& selection_rect, int active_match_ordinal, bool final_update) override; + bool CheckMediaAccessPermission( + content::WebContents* web_contents, + const GURL& security_origin, + content::MediaStreamType type) override; + void RequestMediaAccessPermission( + content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback) override; + void RequestToLockMouse( + content::WebContents* web_contents, + bool user_gesture, + bool last_unlocked_by_target) override; // content::WebContentsObserver: void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; @@ -232,8 +247,8 @@ class WebContents : public mate::TrackableObject, const std::vector& urls) override; void PluginCrashed(const base::FilePath& plugin_path, base::ProcessId plugin_pid) override; - void MediaStartedPlaying() override; - void MediaPaused() override; + void MediaStartedPlaying(const MediaPlayerId& id) override; + void MediaStoppedPlaying(const MediaPlayerId& id) override; void DidChangeThemeColor(SkColor theme_color) override; // brightray::InspectableWebContentsViewDelegate: @@ -250,10 +265,13 @@ class WebContents : public mate::TrackableObject, AtomBrowserContext* GetBrowserContext() const; - uint32 GetNextRequestId() { + uint32_t GetNextRequestId() { return ++request_id_; } + // Called when we receive a CursorChange message from chromium. + void OnCursorChange(const content::WebCursor& cursor); + // Called when received a message from renderer. void OnRendererMessage(const base::string16& channel, const base::ListValue& args); @@ -265,14 +283,18 @@ class WebContents : public mate::TrackableObject, v8::Global session_; v8::Global devtools_web_contents_; + v8::Global debugger_; scoped_ptr guest_delegate_; + // The host webcontents that may contain this webcontents. + WebContents* embedder_; + // The type of current WebContents. Type type_; // Request id used for findInPage request. - uint32 request_id_; + uint32_t request_id_; DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index 09d88edb9b7..a10f46fe75e 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -52,6 +52,11 @@ namespace api { namespace { +// This function is implemented in JavaScript +using DeprecatedOptionsCheckCallback = + base::Callback)>; +DeprecatedOptionsCheckCallback g_deprecated_options_check; + void OnCapturePageDone( v8::Isolate* isolate, const base::Callback& callback, @@ -109,7 +114,7 @@ void TranslateOldOptions(v8::Isolate* isolate, v8::Local options) { // Converts binary data to Buffer. v8::Local ToBuffer(v8::Isolate* isolate, void* val, int size) { - auto buffer = node::Buffer::New(isolate, static_cast(val), size); + auto buffer = node::Buffer::Copy(isolate, static_cast(val), size); if (buffer.IsEmpty()) return v8::Null(isolate); else @@ -191,6 +196,14 @@ void Window::OnWindowFocus() { Emit("focus"); } +void Window::OnWindowShow() { + Emit("show"); +} + +void Window::OnWindowHide() { + Emit("hide"); +} + void Window::OnWindowMaximize() { Emit("maximize"); } @@ -227,6 +240,18 @@ void Window::OnWindowLeaveFullScreen() { Emit("leave-full-screen"); } +void Window::OnWindowScrollTouchBegin() { + Emit("scroll-touch-begin"); +} + +void Window::OnWindowScrollTouchEnd() { + Emit("scroll-touch-end"); +} + +void Window::OnWindowSwipe(const std::string& direction) { + Emit("swipe", direction); +} + void Window::OnWindowEnterHtmlFullScreen() { Emit("enter-html-full-screen"); } @@ -275,6 +300,13 @@ mate::Wrappable* Window::New(v8::Isolate* isolate, mate::Arguments* args) { options = mate::Dictionary::CreateEmpty(isolate); } + std::string deprecation_message = g_deprecated_options_check.Run( + options.GetHandle()); + if (deprecation_message.length() > 0) { + args->ThrowError(deprecation_message); + return nullptr; + } + return new Window(isolate, options); } @@ -286,6 +318,10 @@ void Window::Focus() { window_->Focus(true); } +void Window::Blur() { + window_->Focus(false); +} + bool Window::IsFocused() { return window_->IsFocused(); } @@ -408,6 +444,46 @@ bool Window::IsResizable() { return window_->IsResizable(); } +void Window::SetMovable(bool movable) { + window_->SetMovable(movable); +} + +bool Window::IsMovable() { + return window_->IsMovable(); +} + +void Window::SetMinimizable(bool minimizable) { + window_->SetMinimizable(minimizable); +} + +bool Window::IsMinimizable() { + return window_->IsMinimizable(); +} + +void Window::SetMaximizable(bool maximizable) { + window_->SetMaximizable(maximizable); +} + +bool Window::IsMaximizable() { + return window_->IsMaximizable(); +} + +void Window::SetFullScreenable(bool fullscreenable) { + window_->SetFullScreenable(fullscreenable); +} + +bool Window::IsFullScreenable() { + return window_->IsFullScreenable(); +} + +void Window::SetClosable(bool closable) { + window_->SetClosable(closable); +} + +bool Window::IsClosable() { + return window_->IsClosable(); +} + void Window::SetAlwaysOnTop(bool top) { window_->SetAlwaysOnTop(top); } @@ -462,6 +538,14 @@ void Window::SetBackgroundColor(const std::string& color_name) { window_->SetBackgroundColor(color_name); } +void Window::SetHasShadow(bool has_shadow) { + window_->SetHasShadow(has_shadow); +} + +bool Window::HasShadow() { + return window_->HasShadow(); +} + void Window::FocusOnWebView() { window_->FocusOnWebView(); } @@ -632,6 +716,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .MakeDestroyable() .SetMethod("close", &Window::Close) .SetMethod("focus", &Window::Focus) + .SetMethod("blur", &Window::Blur) .SetMethod("isFocused", &Window::IsFocused) .SetMethod("show", &Window::Show) .SetMethod("showInactive", &Window::ShowInactive) @@ -659,6 +744,16 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("getMaximumSize", &Window::GetMaximumSize) .SetMethod("setResizable", &Window::SetResizable) .SetMethod("isResizable", &Window::IsResizable) + .SetMethod("setMovable", &Window::SetMovable) + .SetMethod("isMovable", &Window::IsMovable) + .SetMethod("setMinimizable", &Window::SetMinimizable) + .SetMethod("isMinimizable", &Window::IsMinimizable) + .SetMethod("setMaximizable", &Window::SetMaximizable) + .SetMethod("isMaximizable", &Window::IsMaximizable) + .SetMethod("setFullScreenable", &Window::SetFullScreenable) + .SetMethod("isFullScreenable", &Window::IsFullScreenable) + .SetMethod("setClosable", &Window::SetClosable) + .SetMethod("isClosable", &Window::IsClosable) .SetMethod("setAlwaysOnTop", &Window::SetAlwaysOnTop) .SetMethod("isAlwaysOnTop", &Window::IsAlwaysOnTop) .SetMethod("center", &Window::Center) @@ -671,6 +766,8 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("setKiosk", &Window::SetKiosk) .SetMethod("isKiosk", &Window::IsKiosk) .SetMethod("setBackgroundColor", &Window::SetBackgroundColor) + .SetMethod("setHasShadow", &Window::SetHasShadow) + .SetMethod("hasShadow", &Window::HasShadow) .SetMethod("setRepresentedFilename", &Window::SetRepresentedFilename) .SetMethod("getRepresentedFilename", &Window::GetRepresentedFilename) .SetMethod("setDocumentEdited", &Window::SetDocumentEdited) @@ -716,6 +813,10 @@ v8::Local Window::From(v8::Isolate* isolate, return v8::Null(isolate); } +void SetDeprecatedOptionsCheck(const DeprecatedOptionsCheckCallback& callback) { + g_deprecated_options_check = callback; +} + } // namespace api } // namespace atom @@ -738,6 +839,8 @@ void Initialize(v8::Local exports, v8::Local unused, mate::Dictionary dict(isolate, exports); dict.Set("BrowserWindow", browser_window); + dict.SetMethod("_setDeprecatedOptionsCheck", + &atom::api::SetDeprecatedOptionsCheck); } } // namespace diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index d8e25965d89..d26ff5b3674 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -58,6 +58,8 @@ class Window : public mate::TrackableObject, void OnWindowClosed() override; void OnWindowBlur() override; void OnWindowFocus() override; + void OnWindowShow() override; + void OnWindowHide() override; void OnWindowMaximize() override; void OnWindowUnmaximize() override; void OnWindowMinimize() override; @@ -65,6 +67,9 @@ class Window : public mate::TrackableObject, void OnWindowResize() override; void OnWindowMove() override; void OnWindowMoved() override; + void OnWindowScrollTouchBegin() override; + void OnWindowScrollTouchEnd() override; + void OnWindowSwipe(const std::string& direction) override; void OnWindowEnterFullScreen() override; void OnWindowLeaveFullScreen() override; void OnWindowEnterHtmlFullScreen() override; @@ -81,6 +86,7 @@ class Window : public mate::TrackableObject, // APIs for NativeWindow. void Close(); void Focus(); + void Blur(); bool IsFocused(); void Show(); void ShowInactive(); @@ -106,6 +112,16 @@ class Window : public mate::TrackableObject, std::vector GetMaximumSize(); void SetResizable(bool resizable); bool IsResizable(); + void SetMovable(bool movable); + bool IsMovable(); + void SetMinimizable(bool minimizable); + bool IsMinimizable(); + void SetMaximizable(bool maximizable); + bool IsMaximizable(); + void SetFullScreenable(bool fullscreenable); + bool IsFullScreenable(); + void SetClosable(bool closable); + bool IsClosable(); void SetAlwaysOnTop(bool top); bool IsAlwaysOnTop(); void Center(); @@ -118,6 +134,8 @@ class Window : public mate::TrackableObject, void SetKiosk(bool kiosk); bool IsKiosk(); void SetBackgroundColor(const std::string& color_name); + void SetHasShadow(bool has_shadow); + bool HasShadow(); void FocusOnWebView(); void BlurWebView(); bool IsWebViewFocused(); diff --git a/atom/browser/api/frame_subscriber.cc b/atom/browser/api/frame_subscriber.cc index 5b7241486b7..f81a8bea8b1 100644 --- a/atom/browser/api/frame_subscriber.cc +++ b/atom/browser/api/frame_subscriber.cc @@ -4,19 +4,18 @@ #include "atom/browser/api/frame_subscriber.h" -#include "atom/common/node_includes.h" #include "base/bind.h" -#include "media/base/video_frame.h" -#include "media/base/yuv_convert.h" +#include "atom/common/node_includes.h" +#include "content/public/browser/render_widget_host.h" namespace atom { namespace api { FrameSubscriber::FrameSubscriber(v8::Isolate* isolate, - const gfx::Size& size, + content::RenderWidgetHostView* view, const FrameCaptureCallback& callback) - : isolate_(isolate), size_(size), callback_(callback) { + : isolate_(isolate), view_(view), callback_(callback), weak_factory_(this) { } bool FrameSubscriber::ShouldCaptureFrame( @@ -24,39 +23,39 @@ bool FrameSubscriber::ShouldCaptureFrame( base::TimeTicks present_time, scoped_refptr* storage, DeliverFrameCallback* callback) { - *storage = media::VideoFrame::CreateFrame( - media::PIXEL_FORMAT_YV12, - size_, gfx::Rect(size_), size_, base::TimeDelta()); - *callback = base::Bind(&FrameSubscriber::OnFrameDelivered, - base::Unretained(this), *storage); - return true; + const auto host = view_ ? view_->GetRenderWidgetHost() : nullptr; + if (!view_ || !host) + return false; + + const auto size = view_->GetVisibleViewportSize(); + + host->CopyFromBackingStore( + gfx::Rect(size), + size, + base::Bind(&FrameSubscriber::OnFrameDelivered, + weak_factory_.GetWeakPtr(), callback_), + kBGRA_8888_SkColorType); + + return false; } -void FrameSubscriber::OnFrameDelivered( - scoped_refptr frame, base::TimeTicks, bool result) { - if (!result) +void FrameSubscriber::OnFrameDelivered(const FrameCaptureCallback& callback, + const SkBitmap& bitmap, content::ReadbackResponse response) { + if (bitmap.computeSize64() == 0) return; v8::Locker locker(isolate_); v8::HandleScope handle_scope(isolate_); - gfx::Rect rect = frame->visible_rect(); - size_t rgb_arr_size = rect.width() * rect.height() * 4; + size_t rgb_arr_size = bitmap.width() * bitmap.height() * + bitmap.bytesPerPixel(); v8::MaybeLocal buffer = node::Buffer::New(isolate_, rgb_arr_size); if (buffer.IsEmpty()) return; - // Convert a frame of YUV to 32 bit ARGB. - media::ConvertYUVToRGB32(frame->data(media::VideoFrame::kYPlane), - frame->data(media::VideoFrame::kUPlane), - frame->data(media::VideoFrame::kVPlane), - reinterpret_cast( - node::Buffer::Data(buffer.ToLocalChecked())), - rect.width(), rect.height(), - frame->stride(media::VideoFrame::kYPlane), - frame->stride(media::VideoFrame::kUVPlane), - rect.width() * 4, - media::YV12); + bitmap.copyPixelsTo( + reinterpret_cast(node::Buffer::Data(buffer.ToLocalChecked())), + rgb_arr_size); callback_.Run(buffer.ToLocalChecked()); } diff --git a/atom/browser/api/frame_subscriber.h b/atom/browser/api/frame_subscriber.h index f7748aa5790..a803d75dff2 100644 --- a/atom/browser/api/frame_subscriber.h +++ b/atom/browser/api/frame_subscriber.h @@ -6,7 +6,11 @@ #define ATOM_BROWSER_API_FRAME_SUBSCRIBER_H_ #include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/render_widget_host_view_frame_subscriber.h" +#include "content/public/browser/readback_types.h" +#include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/geometry/size.h" #include "v8/include/v8.h" @@ -19,7 +23,7 @@ class FrameSubscriber : public content::RenderWidgetHostViewFrameSubscriber { using FrameCaptureCallback = base::Callback)>; FrameSubscriber(v8::Isolate* isolate, - const gfx::Size& size, + content::RenderWidgetHostView* view, const FrameCaptureCallback& callback); bool ShouldCaptureFrame(const gfx::Rect& damage_rect, @@ -28,13 +32,15 @@ class FrameSubscriber : public content::RenderWidgetHostViewFrameSubscriber { DeliverFrameCallback* callback) override; private: - void OnFrameDelivered( - scoped_refptr frame, base::TimeTicks, bool); + void OnFrameDelivered(const FrameCaptureCallback& callback, + const SkBitmap& bitmap, content::ReadbackResponse response); v8::Isolate* isolate_; - gfx::Size size_; + content::RenderWidgetHostView* view_; FrameCaptureCallback callback_; + base::WeakPtrFactory weak_factory_; + DISALLOW_COPY_AND_ASSIGN(FrameSubscriber); }; diff --git a/atom/browser/api/lib/app.js b/atom/browser/api/lib/app.js deleted file mode 100644 index b99c069b558..00000000000 --- a/atom/browser/api/lib/app.js +++ /dev/null @@ -1,122 +0,0 @@ -const deprecate = require('electron').deprecate; -const session = require('electron').session; -const Menu = require('electron').Menu; -const EventEmitter = require('events').EventEmitter; - -const bindings = process.atomBinding('app'); -const downloadItemBindings = process.atomBinding('download_item'); -const app = bindings.app; - -var slice = [].slice; - -app.__proto__ = EventEmitter.prototype; - -app.setApplicationMenu = function(menu) { - return Menu.setApplicationMenu(menu); -}; - -app.getApplicationMenu = function() { - return Menu.getApplicationMenu(); -}; - -app.commandLine = { - appendSwitch: bindings.appendSwitch, - appendArgument: bindings.appendArgument -}; - -if (process.platform === 'darwin') { - app.dock = { - bounce: function(type) { - if (type == null) { - type = 'informational'; - } - return bindings.dockBounce(type); - }, - cancelBounce: bindings.dockCancelBounce, - setBadge: bindings.dockSetBadgeText, - getBadge: bindings.dockGetBadgeText, - hide: bindings.dockHide, - show: bindings.dockShow, - setMenu: bindings.dockSetMenu - }; -} - -var appPath = null; - -app.setAppPath = function(path) { - return appPath = path; -}; - -app.getAppPath = function() { - return appPath; -}; - -// Routes the events to webContents. -var ref1 = ['login', 'certificate-error', 'select-client-certificate']; -var fn = function(name) { - return app.on(name, function() { - var args, event, webContents; - event = arguments[0], webContents = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - return webContents.emit.apply(webContents, [name, event].concat(slice.call(args))); - }); -}; -var i, len; -for (i = 0, len = ref1.length; i < len; i++) { - fn(ref1[i]); -} - -// Deprecated. - -app.getHomeDir = deprecate('app.getHomeDir', 'app.getPath', function() { - return this.getPath('home'); -}); - -app.getDataPath = deprecate('app.getDataPath', 'app.getPath', function() { - return this.getPath('userData'); -}); - -app.setDataPath = deprecate('app.setDataPath', 'app.setPath', function(path) { - return this.setPath('userData', path); -}); - -app.resolveProxy = deprecate('app.resolveProxy', 'session.defaultSession.resolveProxy', function(url, callback) { - return session.defaultSession.resolveProxy(url, callback); -}); - -deprecate.rename(app, 'terminate', 'quit'); - -deprecate.event(app, 'finish-launching', 'ready', function() { - - // give default app a chance to setup default menu. - return setImmediate((function(_this) { - return function() { - return _this.emit('finish-launching'); - }; - })(this)); -}); - -deprecate.event(app, 'activate-with-no-open-windows', 'activate', function(event, hasVisibleWindows) { - if (!hasVisibleWindows) { - return this.emit('activate-with-no-open-windows', event); - } -}); - -deprecate.event(app, 'select-certificate', 'select-client-certificate'); - -// Wrappers for native classes. -var wrapDownloadItem = function(downloadItem) { - - // downloadItem is an EventEmitter. - downloadItem.__proto__ = EventEmitter.prototype; - - // Deprecated. - deprecate.property(downloadItem, 'url', 'getURL'); - deprecate.property(downloadItem, 'filename', 'getFilename'); - deprecate.property(downloadItem, 'mimeType', 'getMimeType'); - return deprecate.rename(downloadItem, 'getUrl', 'getURL'); -}; - -downloadItemBindings._setWrapDownloadItem(wrapDownloadItem); - -// Only one App object pemitted. -module.exports = app; diff --git a/atom/browser/api/lib/auto-updater.js b/atom/browser/api/lib/auto-updater.js deleted file mode 100644 index 9cc1fada067..00000000000 --- a/atom/browser/api/lib/auto-updater.js +++ /dev/null @@ -1,7 +0,0 @@ -const deprecate = require('electron').deprecate; -const autoUpdater = process.platform === 'win32' ? require('./auto-updater/auto-updater-win') : require('./auto-updater/auto-updater-native'); - -// Deprecated. -deprecate.rename(autoUpdater, 'setFeedUrl', 'setFeedURL'); - -module.exports = autoUpdater; diff --git a/atom/browser/api/lib/auto-updater/auto-updater-native.js b/atom/browser/api/lib/auto-updater/auto-updater-native.js deleted file mode 100644 index 20c69cdb5a2..00000000000 --- a/atom/browser/api/lib/auto-updater/auto-updater-native.js +++ /dev/null @@ -1,6 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const autoUpdater = process.atomBinding('auto_updater').autoUpdater; - -autoUpdater.__proto__ = EventEmitter.prototype; - -module.exports = autoUpdater; diff --git a/atom/browser/api/lib/auto-updater/auto-updater-win.js b/atom/browser/api/lib/auto-updater/auto-updater-win.js deleted file mode 100644 index e7d6daca927..00000000000 --- a/atom/browser/api/lib/auto-updater/auto-updater-win.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -const app = require('electron').app; -const EventEmitter = require('events').EventEmitter; -const url = require('url'); -const squirrelUpdate = require('./squirrel-update-win'); - -class AutoUpdater extends EventEmitter { - constructor() { - super(); - } - - quitAndInstall() { - squirrelUpdate.processStart(); - return app.quit(); - } - - setFeedURL(updateURL) { - return this.updateURL = updateURL; - } - - checkForUpdates() { - if (!this.updateURL) { - return this.emitError('Update URL is not set'); - } - if (!squirrelUpdate.supported()) { - return this.emitError('Can not find Squirrel'); - } - this.emit('checking-for-update'); - return squirrelUpdate.download(this.updateURL, (function(_this) { - return function(error, update) { - if (error != null) { - return _this.emitError(error); - } - if (update == null) { - return _this.emit('update-not-available'); - } - _this.emit('update-available'); - return squirrelUpdate.update(_this.updateURL, function(error) { - var date, releaseNotes, version; - if (error != null) { - return _this.emitError(error); - } - releaseNotes = update.releaseNotes, version = update.version; - - // Following information is not available on Windows, so fake them. - date = new Date; - url = _this.updateURL; - return _this.emit('update-downloaded', {}, releaseNotes, version, date, url, function() { - return _this.quitAndInstall(); - }); - }); - }; - })(this)); - } - - // Private: Emit both error object and message, this is to keep compatibility - // with Old APIs. - emitError(message) { - return this.emit('error', new Error(message), message); - } -} - -module.exports = new AutoUpdater; diff --git a/atom/browser/api/lib/auto-updater/squirrel-update-win.js b/atom/browser/api/lib/auto-updater/squirrel-update-win.js deleted file mode 100644 index fbab354b264..00000000000 --- a/atom/browser/api/lib/auto-updater/squirrel-update-win.js +++ /dev/null @@ -1,98 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const spawn = require('child_process').spawn; - -// i.e. my-app/app-0.1.13/ -const appFolder = path.dirname(process.execPath); - -// i.e. my-app/Update.exe -const updateExe = path.resolve(appFolder, '..', 'Update.exe'); - -const exeName = path.basename(process.execPath); - -// Spawn a command and invoke the callback when it completes with an error -// and the output from standard out. -var spawnUpdate = function(args, detached, callback) { - var error, errorEmitted, spawnedProcess, stderr, stdout; - try { - spawnedProcess = spawn(updateExe, args, { - detached: detached - }); - } catch (error1) { - error = error1; - - // Shouldn't happen, but still guard it. - process.nextTick(function() { - return callback(error); - }); - return; - } - stdout = ''; - stderr = ''; - spawnedProcess.stdout.on('data', function(data) { - return stdout += data; - }); - spawnedProcess.stderr.on('data', function(data) { - return stderr += data; - }); - errorEmitted = false; - spawnedProcess.on('error', function(error) { - errorEmitted = true; - return callback(error); - }); - return spawnedProcess.on('exit', function(code, signal) { - - // We may have already emitted an error. - if (errorEmitted) { - return; - } - - // Process terminated with error. - if (code !== 0) { - return callback("Command failed: " + (signal != null ? signal : code) + "\n" + stderr); - } - - // Success. - return callback(null, stdout); - }); -}; - -// Start an instance of the installed app. -exports.processStart = function() { - return spawnUpdate(['--processStart', exeName], true, function() {}); -}; - -// Download the releases specified by the URL and write new results to stdout. -exports.download = function(updateURL, callback) { - return spawnUpdate(['--download', updateURL], false, function(error, stdout) { - var json, ref, ref1, update; - if (error != null) { - return callback(error); - } - try { - // Last line of output is the JSON details about the releases - json = stdout.trim().split('\n').pop(); - update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === "function" ? ref1.pop() : void 0 : void 0 : void 0; - } catch (jsonError) { - return callback("Invalid result:\n" + stdout); - } - return callback(null, update); - }); -}; - - -// Update the application to the latest remote version specified by URL. -exports.update = function(updateURL, callback) { - return spawnUpdate(['--update', updateURL], false, callback); -}; - - -// Is the Update.exe installed with the current application? -exports.supported = function() { - try { - fs.accessSync(updateExe, fs.R_OK); - return true; - } catch (error) { - return false; - } -}; diff --git a/atom/browser/api/lib/browser-window.js b/atom/browser/api/lib/browser-window.js deleted file mode 100644 index b71c85a1910..00000000000 --- a/atom/browser/api/lib/browser-window.js +++ /dev/null @@ -1,241 +0,0 @@ -const ipcMain = require('electron').ipcMain; -const deprecate = require('electron').deprecate; -const EventEmitter = require('events').EventEmitter; -const BrowserWindow = process.atomBinding('window').BrowserWindow; - -BrowserWindow.prototype.__proto__ = EventEmitter.prototype; - -BrowserWindow.prototype._init = function() { - - // avoid recursive require. - var app, menu; - app = require('electron').app; - - // Simulate the application menu on platforms other than OS X. - if (process.platform !== 'darwin') { - menu = app.getApplicationMenu(); - if (menu != null) { - this.setMenu(menu); - } - } - - // Make new windows requested by links behave like "window.open" - this.webContents.on('-new-window', function(event, url, frameName) { - var options; - options = { - show: true, - width: 800, - height: 600 - }; - return ipcMain.emit('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, options); - }); - - // window.resizeTo(...) - // window.moveTo(...) - this.webContents.on('move', (function(_this) { - return function(event, size) { - return _this.setBounds(size); - }; - })(this)); - - // Hide the auto-hide menu when webContents is focused. - this.webContents.on('activate', (function(_this) { - return function() { - if (process.platform !== 'darwin' && _this.isMenuBarAutoHide() && _this.isMenuBarVisible()) { - return _this.setMenuBarVisibility(false); - } - }; - })(this)); - - // Forward the crashed event. - this.webContents.on('crashed', (function(_this) { - return function() { - return _this.emit('crashed'); - }; - })(this)); - - // Change window title to page title. - this.webContents.on('page-title-updated', (function(_this) { - return function(event, title) { - _this.emit('page-title-updated', event, title); - if (!event.defaultPrevented) { - return _this.setTitle(title); - } - }; - })(this)); - - // Sometimes the webContents doesn't get focus when window is shown, so we have - // to force focusing on webContents in this case. The safest way is to focus it - // when we first start to load URL, if we do it earlier it won't have effect, - // if we do it later we might move focus in the page. - // Though this hack is only needed on OS X when the app is launched from - // Finder, we still do it on all platforms in case of other bugs we don't know. - this.webContents.once('load-url', function() { - return this.focus(); - }); - - // Redirect focus/blur event to app instance too. - this.on('blur', (function(_this) { - return function(event) { - return app.emit('browser-window-blur', event, _this); - }; - })(this)); - this.on('focus', (function(_this) { - return function(event) { - return app.emit('browser-window-focus', event, _this); - }; - })(this)); - - // Notify the creation of the window. - app.emit('browser-window-created', {}, this); - - // Be compatible with old APIs. - this.webContents.on('devtools-focused', (function(_this) { - return function() { - return _this.emit('devtools-focused'); - }; - })(this)); - this.webContents.on('devtools-opened', (function(_this) { - return function() { - return _this.emit('devtools-opened'); - }; - })(this)); - this.webContents.on('devtools-closed', (function(_this) { - return function() { - return _this.emit('devtools-closed'); - }; - })(this)); - return Object.defineProperty(this, 'devToolsWebContents', { - enumerable: true, - configurable: false, - get: function() { - return this.webContents.devToolsWebContents; - } - }); -}; - -BrowserWindow.getFocusedWindow = function() { - var i, len, window, windows; - windows = BrowserWindow.getAllWindows(); - for (i = 0, len = windows.length; i < len; i++) { - window = windows[i]; - if (window.isFocused()) { - return window; - } - } - return null; -}; - -BrowserWindow.fromWebContents = function(webContents) { - var i, len, ref1, window, windows; - windows = BrowserWindow.getAllWindows(); - for (i = 0, len = windows.length; i < len; i++) { - window = windows[i]; - if ((ref1 = window.webContents) != null ? ref1.equal(webContents) : void 0) { - return window; - } - } -}; - -BrowserWindow.fromDevToolsWebContents = function(webContents) { - var i, len, ref1, window, windows; - windows = BrowserWindow.getAllWindows(); - for (i = 0, len = windows.length; i < len; i++) { - window = windows[i]; - if ((ref1 = window.devToolsWebContents) != null ? ref1.equal(webContents) : void 0) { - return window; - } - } -}; - -// Helpers. - -BrowserWindow.prototype.loadURL = function() { - return this.webContents.loadURL.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.getURL = function() { - return this.webContents.getURL(); -}; - -BrowserWindow.prototype.reload = function() { - return this.webContents.reload.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.send = function() { - return this.webContents.send.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.openDevTools = function() { - return this.webContents.openDevTools.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.closeDevTools = function() { - return this.webContents.closeDevTools(); -}; - -BrowserWindow.prototype.isDevToolsOpened = function() { - return this.webContents.isDevToolsOpened(); -}; - -BrowserWindow.prototype.isDevToolsFocused = function() { - return this.webContents.isDevToolsFocused(); -}; - -BrowserWindow.prototype.toggleDevTools = function() { - return this.webContents.toggleDevTools(); -}; - -BrowserWindow.prototype.inspectElement = function() { - return this.webContents.inspectElement.apply(this.webContents, arguments); -}; - -BrowserWindow.prototype.inspectServiceWorker = function() { - return this.webContents.inspectServiceWorker(); -}; - -// Deprecated. - -deprecate.member(BrowserWindow, 'undo', 'webContents'); - -deprecate.member(BrowserWindow, 'redo', 'webContents'); - -deprecate.member(BrowserWindow, 'cut', 'webContents'); - -deprecate.member(BrowserWindow, 'copy', 'webContents'); - -deprecate.member(BrowserWindow, 'paste', 'webContents'); - -deprecate.member(BrowserWindow, 'selectAll', 'webContents'); - -deprecate.member(BrowserWindow, 'reloadIgnoringCache', 'webContents'); - -deprecate.member(BrowserWindow, 'isLoading', 'webContents'); - -deprecate.member(BrowserWindow, 'isWaitingForResponse', 'webContents'); - -deprecate.member(BrowserWindow, 'stop', 'webContents'); - -deprecate.member(BrowserWindow, 'isCrashed', 'webContents'); - -deprecate.member(BrowserWindow, 'print', 'webContents'); - -deprecate.member(BrowserWindow, 'printToPDF', 'webContents'); - -deprecate.rename(BrowserWindow, 'restart', 'reload'); - -deprecate.rename(BrowserWindow, 'loadUrl', 'loadURL'); - -deprecate.rename(BrowserWindow, 'getUrl', 'getURL'); - -BrowserWindow.prototype.executeJavaScriptInDevTools = deprecate('executeJavaScriptInDevTools', 'devToolsWebContents.executeJavaScript', function(code) { - var ref1; - return (ref1 = this.devToolsWebContents) != null ? ref1.executeJavaScript(code) : void 0; -}); - -BrowserWindow.prototype.getPageTitle = deprecate('getPageTitle', 'webContents.getTitle', function() { - var ref1; - return (ref1 = this.webContents) != null ? ref1.getTitle() : void 0; -}); - -module.exports = BrowserWindow; diff --git a/atom/browser/api/lib/content-tracing.js b/atom/browser/api/lib/content-tracing.js deleted file mode 100644 index b00c5666df5..00000000000 --- a/atom/browser/api/lib/content-tracing.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = process.atomBinding('content_tracing'); diff --git a/atom/browser/api/lib/dialog.js b/atom/browser/api/lib/dialog.js deleted file mode 100644 index 102aaa3897d..00000000000 --- a/atom/browser/api/lib/dialog.js +++ /dev/null @@ -1,170 +0,0 @@ -const app = require('electron').app; -const BrowserWindow = require('electron').BrowserWindow; -const binding = process.atomBinding('dialog'); -const v8Util = process.atomBinding('v8_util'); - -var slice = [].slice; -var includes = [].includes; - -var fileDialogProperties = { - openFile: 1 << 0, - openDirectory: 1 << 1, - multiSelections: 1 << 2, - createDirectory: 1 << 3 -}; - -var messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']; - -var messageBoxOptions = { - noLink: 1 << 0 -}; - -var parseArgs = function(window, options, callback) { - if (!(window === null || (window != null ? window.constructor : void 0) === BrowserWindow)) { - // Shift. - callback = options; - options = window; - window = null; - } - if ((callback == null) && typeof options === 'function') { - // Shift. - callback = options; - options = null; - } - return [window, options, callback]; -}; - -var checkAppInitialized = function() { - if (!app.isReady()) { - throw new Error('dialog module can only be used after app is ready'); - } -}; - -module.exports = { - showOpenDialog: function() { - var args, callback, options, prop, properties, ref1, value, window, wrappedCallback; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - checkAppInitialized(); - ref1 = parseArgs.apply(null, args), window = ref1[0], options = ref1[1], callback = ref1[2]; - if (options == null) { - options = { - title: 'Open', - properties: ['openFile'] - }; - } - if (options.properties == null) { - options.properties = ['openFile']; - } - if (!Array.isArray(options.properties)) { - throw new TypeError('Properties need to be array'); - } - properties = 0; - for (prop in fileDialogProperties) { - value = fileDialogProperties[prop]; - if (includes.call(options.properties, prop)) { - properties |= value; - } - } - if (options.title == null) { - options.title = ''; - } - if (options.defaultPath == null) { - options.defaultPath = ''; - } - if (options.filters == null) { - options.filters = []; - } - wrappedCallback = typeof callback === 'function' ? function(success, result) { - return callback(success ? result : void 0); - } : null; - return binding.showOpenDialog(String(options.title), String(options.defaultPath), options.filters, properties, window, wrappedCallback); - }, - showSaveDialog: function() { - var args, callback, options, ref1, window, wrappedCallback; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - checkAppInitialized(); - ref1 = parseArgs.apply(null, args), window = ref1[0], options = ref1[1], callback = ref1[2]; - if (options == null) { - options = { - title: 'Save' - }; - } - if (options.title == null) { - options.title = ''; - } - if (options.defaultPath == null) { - options.defaultPath = ''; - } - if (options.filters == null) { - options.filters = []; - } - wrappedCallback = typeof callback === 'function' ? function(success, result) { - return callback(success ? result : void 0); - } : null; - return binding.showSaveDialog(String(options.title), String(options.defaultPath), options.filters, window, wrappedCallback); - }, - showMessageBox: function() { - var args, callback, flags, i, j, len, messageBoxType, options, ref1, ref2, ref3, text, window; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - checkAppInitialized(); - ref1 = parseArgs.apply(null, args), window = ref1[0], options = ref1[1], callback = ref1[2]; - if (options == null) { - options = { - type: 'none' - }; - } - if (options.type == null) { - options.type = 'none'; - } - messageBoxType = messageBoxTypes.indexOf(options.type); - if (!(messageBoxType > -1)) { - throw new TypeError('Invalid message box type'); - } - if (!Array.isArray(options.buttons)) { - throw new TypeError('Buttons need to be array'); - } - if (options.title == null) { - options.title = ''; - } - if (options.message == null) { - options.message = ''; - } - if (options.detail == null) { - options.detail = ''; - } - if (options.icon == null) { - options.icon = null; - } - if (options.defaultId == null) { - options.defaultId = -1; - } - - // Choose a default button to get selected when dialog is cancelled. - if (options.cancelId == null) { - options.cancelId = 0; - ref2 = options.buttons; - for (i = j = 0, len = ref2.length; j < len; i = ++j) { - text = ref2[i]; - if ((ref3 = text.toLowerCase()) === 'cancel' || ref3 === 'no') { - options.cancelId = i; - break; - } - } - } - flags = options.noLink ? messageBoxOptions.noLink : 0; - return binding.showMessageBox(messageBoxType, options.buttons, options.defaultId, options.cancelId, flags, options.title, options.message, options.detail, options.icon, window, callback); - }, - showErrorBox: function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return binding.showErrorBox.apply(binding, args); - } -}; - -// Mark standard asynchronous functions. -var ref1 = ['showMessageBox', 'showOpenDialog', 'showSaveDialog']; -var j, len, api; -for (j = 0, len = ref1.length; j < len; j++) { - api = ref1[j]; - v8Util.setHiddenValue(module.exports[api], 'asynchronous', true); -} diff --git a/atom/browser/api/lib/exports/electron.js b/atom/browser/api/lib/exports/electron.js deleted file mode 100644 index 7f97fcdbc4c..00000000000 --- a/atom/browser/api/lib/exports/electron.js +++ /dev/null @@ -1,112 +0,0 @@ -const common = require('../../../../common/api/lib/exports/electron'); - - -// 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'); - } - }, - Tray: { - enumerable: true, - get: function() { - return require('../tray'); - } - }, - - // The internal modules, invisible unless you know their names. - NavigationController: { - get: function() { - return require('../navigation-controller'); - } - }, - webContents: { - get: function() { - return require('../web-contents'); - } - } -}); diff --git a/atom/browser/api/lib/ipc-main.js b/atom/browser/api/lib/ipc-main.js deleted file mode 100644 index e253e03eaab..00000000000 --- a/atom/browser/api/lib/ipc-main.js +++ /dev/null @@ -1,3 +0,0 @@ -const EventEmitter = require('events').EventEmitter; - -module.exports = new EventEmitter; diff --git a/atom/browser/api/lib/ipc.js b/atom/browser/api/lib/ipc.js deleted file mode 100644 index 6e971515423..00000000000 --- a/atom/browser/api/lib/ipc.js +++ /dev/null @@ -1,7 +0,0 @@ -const deprecate = require('electron').deprecate; -const ipcMain = require('electron').ipcMain; - -// This module is deprecated, we mirror everything from ipcMain. -deprecate.warn('ipc module', 'require("electron").ipcMain'); - -module.exports = ipcMain; diff --git a/atom/browser/api/lib/menu-item.js b/atom/browser/api/lib/menu-item.js deleted file mode 100644 index d58a0fad2c7..00000000000 --- a/atom/browser/api/lib/menu-item.js +++ /dev/null @@ -1,102 +0,0 @@ -var MenuItem, methodInBrowserWindow, nextCommandId, rolesMap; - -nextCommandId = 0; - -// Maps role to methods of webContents -rolesMap = { - undo: 'undo', - redo: 'redo', - cut: 'cut', - copy: 'copy', - paste: 'paste', - selectall: 'selectAll', - minimize: 'minimize', - close: 'close' -}; - -// Maps methods that should be called directly on the BrowserWindow instance -methodInBrowserWindow = { - minimize: true, - close: true -}; - -MenuItem = (function() { - MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio']; - - function MenuItem(options) { - var click, ref; - const Menu = require('electron').Menu; - click = options.click, this.selector = options.selector, this.type = options.type, this.role = options.role, this.label = options.label, this.sublabel = options.sublabel, this.accelerator = options.accelerator, this.icon = options.icon, this.enabled = options.enabled, this.visible = options.visible, this.checked = options.checked, this.submenu = options.submenu; - if ((this.submenu != null) && this.submenu.constructor !== Menu) { - this.submenu = Menu.buildFromTemplate(this.submenu); - } - if ((this.type == null) && (this.submenu != null)) { - this.type = 'submenu'; - } - if (this.type === 'submenu' && ((ref = this.submenu) != null ? ref.constructor : void 0) !== Menu) { - throw new Error('Invalid submenu'); - } - this.overrideReadOnlyProperty('type', 'normal'); - this.overrideReadOnlyProperty('role'); - this.overrideReadOnlyProperty('accelerator'); - this.overrideReadOnlyProperty('icon'); - this.overrideReadOnlyProperty('submenu'); - this.overrideProperty('label', ''); - this.overrideProperty('sublabel', ''); - this.overrideProperty('enabled', true); - this.overrideProperty('visible', true); - this.overrideProperty('checked', false); - if (MenuItem.types.indexOf(this.type) === -1) { - throw new Error("Unknown menu type " + this.type); - } - this.commandId = ++nextCommandId; - this.click = (function(_this) { - return function(focusedWindow) { - - // Manually flip the checked flags when clicked. - var methodName, ref1, ref2; - if ((ref1 = _this.type) === 'checkbox' || ref1 === 'radio') { - _this.checked = !_this.checked; - } - if (_this.role && rolesMap[_this.role] && process.platform !== 'darwin' && (focusedWindow != null)) { - methodName = rolesMap[_this.role]; - if (methodInBrowserWindow[methodName]) { - return focusedWindow[methodName](); - } else { - return (ref2 = focusedWindow.webContents) != null ? ref2[methodName]() : void 0; - } - } else if (typeof click === 'function') { - return click(_this, focusedWindow); - } else if (typeof _this.selector === 'string') { - return Menu.sendActionToFirstResponder(_this.selector); - } - }; - })(this); - } - - MenuItem.prototype.overrideProperty = function(name, defaultValue) { - if (defaultValue == null) { - defaultValue = null; - } - return this[name] != null ? this[name] : this[name] = defaultValue; - }; - - MenuItem.prototype.overrideReadOnlyProperty = function(name, defaultValue) { - if (defaultValue == null) { - defaultValue = null; - } - if (this[name] == null) { - this[name] = defaultValue; - } - return Object.defineProperty(this, name, { - enumerable: true, - writable: false, - value: this[name] - }); - }; - - return MenuItem; - -})(); - -module.exports = MenuItem; diff --git a/atom/browser/api/lib/menu.js b/atom/browser/api/lib/menu.js deleted file mode 100644 index a1473f7acad..00000000000 --- a/atom/browser/api/lib/menu.js +++ /dev/null @@ -1,332 +0,0 @@ -const BrowserWindow = require('electron').BrowserWindow; -const MenuItem = require('electron').MenuItem; -const EventEmitter = require('events').EventEmitter; -const v8Util = process.atomBinding('v8_util'); -const bindings = process.atomBinding('menu'); - -// Automatically generated radio menu item's group id. -var nextGroupId = 0; - -// Search between seperators to find a radio menu item and return its group id, -// otherwise generate a group id. -var generateGroupId = function(items, pos) { - var i, item, j, k, ref1, ref2, ref3; - if (pos > 0) { - for (i = j = ref1 = pos - 1; ref1 <= 0 ? j <= 0 : j >= 0; i = ref1 <= 0 ? ++j : --j) { - item = items[i]; - if (item.type === 'radio') { - return item.groupId; - } - if (item.type === 'separator') { - break; - } - } - } else if (pos < items.length) { - for (i = k = ref2 = pos, ref3 = items.length - 1; ref2 <= ref3 ? k <= ref3 : k >= ref3; i = ref2 <= ref3 ? ++k : --k) { - item = items[i]; - if (item.type === 'radio') { - return item.groupId; - } - if (item.type === 'separator') { - break; - } - } - } - return ++nextGroupId; -}; - -// Returns the index of item according to |id|. -var indexOfItemById = function(items, id) { - var i, item, j, len; - for (i = j = 0, len = items.length; j < len; i = ++j) { - item = items[i]; - if (item.id === id) { - return i; - } - } - return -1; -}; - -// Returns the index of where to insert the item according to |position|. -var indexToInsertByPosition = function(items, position) { - var id, insertIndex, query, ref1; - if (!position) { - return items.length; - } - ref1 = position.split('='), query = ref1[0], id = ref1[1]; - insertIndex = indexOfItemById(items, id); - if (insertIndex === -1 && query !== 'endof') { - console.warn("Item with id '" + id + "' is not found"); - return items.length; - } - switch (query) { - case 'after': - insertIndex++; - break; - case 'endof': - - // If the |id| doesn't exist, then create a new group with the |id|. - if (insertIndex === -1) { - items.push({ - id: id, - type: 'separator' - }); - insertIndex = items.length - 1; - } - - // Find the end of the group. - insertIndex++; - while (insertIndex < items.length && items[insertIndex].type !== 'separator') { - insertIndex++; - } - } - return insertIndex; -}; - -const Menu = bindings.Menu; - -Menu.prototype.__proto__ = EventEmitter.prototype; - -Menu.prototype._init = function() { - this.commandsMap = {}; - this.groupsMap = {}; - this.items = []; - return this.delegate = { - isCommandIdChecked: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.checked : void 0; - }; - })(this), - isCommandIdEnabled: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.enabled : void 0; - }; - })(this), - isCommandIdVisible: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.visible : void 0; - }; - })(this), - getAcceleratorForCommandId: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.accelerator : void 0; - }; - })(this), - getIconForCommandId: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.icon : void 0; - }; - })(this), - executeCommand: (function(_this) { - return function(commandId) { - var ref1; - return (ref1 = _this.commandsMap[commandId]) != null ? ref1.click(BrowserWindow.getFocusedWindow()) : void 0; - }; - })(this), - menuWillShow: (function(_this) { - return function() { - - // Make sure radio groups have at least one menu item seleted. - var checked, group, id, j, len, radioItem, ref1, results; - ref1 = _this.groupsMap; - results = []; - for (id in ref1) { - group = ref1[id]; - checked = false; - for (j = 0, len = group.length; j < len; j++) { - radioItem = group[j]; - if (!radioItem.checked) { - continue; - } - checked = true; - break; - } - if (!checked) { - results.push(v8Util.setHiddenValue(group[0], 'checked', true)); - } else { - results.push(void 0); - } - } - return results; - }; - })(this) - }; -}; - -Menu.prototype.popup = function(window, x, y) { - if ((window != null ? window.constructor : void 0) !== BrowserWindow) { - // Shift. - y = x; - x = window; - window = BrowserWindow.getFocusedWindow(); - } - if ((x != null) && (y != null)) { - return this._popupAt(window, x, y); - } else { - return this._popup(window); - } -}; - -Menu.prototype.append = function(item) { - return this.insert(this.getItemCount(), item); -}; - -Menu.prototype.insert = function(pos, item) { - var base, name; - if ((item != null ? item.constructor : void 0) !== MenuItem) { - throw new TypeError('Invalid item'); - } - switch (item.type) { - case 'normal': - this.insertItem(pos, item.commandId, item.label); - break; - case 'checkbox': - this.insertCheckItem(pos, item.commandId, item.label); - break; - case 'separator': - this.insertSeparator(pos); - break; - case 'submenu': - this.insertSubMenu(pos, item.commandId, item.label, item.submenu); - break; - case 'radio': - // Grouping radio menu items. - item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos)); - if ((base = this.groupsMap)[name = item.groupId] == null) { - base[name] = []; - } - this.groupsMap[item.groupId].push(item); - - // Setting a radio menu item should flip other items in the group. - v8Util.setHiddenValue(item, 'checked', item.checked); - Object.defineProperty(item, 'checked', { - enumerable: true, - get: function() { - return v8Util.getHiddenValue(item, 'checked'); - }, - set: (function(_this) { - return function() { - var j, len, otherItem, ref1; - ref1 = _this.groupsMap[item.groupId]; - for (j = 0, len = ref1.length; j < len; j++) { - otherItem = ref1[j]; - if (otherItem !== item) { - v8Util.setHiddenValue(otherItem, 'checked', false); - } - } - return v8Util.setHiddenValue(item, 'checked', true); - }; - })(this) - }); - this.insertRadioItem(pos, item.commandId, item.label, item.groupId); - } - if (item.sublabel != null) { - this.setSublabel(pos, item.sublabel); - } - if (item.icon != null) { - this.setIcon(pos, item.icon); - } - if (item.role != null) { - this.setRole(pos, item.role); - } - - // Make menu accessable to items. - item.overrideReadOnlyProperty('menu', this); - - // Remember the items. - this.items.splice(pos, 0, item); - return this.commandsMap[item.commandId] = item; -}; - - -// Force menuWillShow to be called -Menu.prototype._callMenuWillShow = function() { - var item, j, len, ref1, ref2, results; - if ((ref1 = this.delegate) != null) { - ref1.menuWillShow(); - } - ref2 = this.items; - results = []; - for (j = 0, len = ref2.length; j < len; j++) { - item = ref2[j]; - if (item.submenu != null) { - results.push(item.submenu._callMenuWillShow()); - } - } - return results; -}; - -var applicationMenu = null; - -Menu.setApplicationMenu = function(menu) { - var j, len, results, w, windows; - if (!(menu === null || menu.constructor === Menu)) { - throw new TypeError('Invalid menu'); - } - - // Keep a reference. - applicationMenu = menu; - if (process.platform === 'darwin') { - if (menu === null) { - return; - } - menu._callMenuWillShow(); - return bindings.setApplicationMenu(menu); - } else { - windows = BrowserWindow.getAllWindows(); - results = []; - for (j = 0, len = windows.length; j < len; j++) { - w = windows[j]; - results.push(w.setMenu(menu)); - } - return results; - } -}; - -Menu.getApplicationMenu = function() { - return applicationMenu; -}; - -Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder; - -Menu.buildFromTemplate = function(template) { - var insertIndex, item, j, k, key, len, len1, menu, menuItem, positionedTemplate, value; - if (!Array.isArray(template)) { - throw new TypeError('Invalid template for Menu'); - } - positionedTemplate = []; - insertIndex = 0; - for (j = 0, len = template.length; j < len; j++) { - item = template[j]; - if (item.position) { - insertIndex = indexToInsertByPosition(positionedTemplate, item.position); - } else { - // If no |position| is specified, insert after last item. - insertIndex++; - } - positionedTemplate.splice(insertIndex, 0, item); - } - menu = new Menu; - for (k = 0, len1 = positionedTemplate.length; k < len1; k++) { - item = positionedTemplate[k]; - if (typeof item !== 'object') { - throw new TypeError('Invalid template for MenuItem'); - } - menuItem = new MenuItem(item); - for (key in item) { - value = item[key]; - if (menuItem[key] == null) { - menuItem[key] = value; - } - } - menu.append(menuItem); - } - return menu; -}; - -module.exports = Menu; diff --git a/atom/browser/api/lib/navigation-controller.js b/atom/browser/api/lib/navigation-controller.js deleted file mode 100644 index 80756eb13e4..00000000000 --- a/atom/browser/api/lib/navigation-controller.js +++ /dev/null @@ -1,188 +0,0 @@ -const ipcMain = require('electron').ipcMain; - -var slice = [].slice; - -// The history operation in renderer is redirected to browser. -ipcMain.on('ATOM_SHELL_NAVIGATION_CONTROLLER', function() { - var args, event, method, ref; - event = arguments[0], method = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - return (ref = event.sender)[method].apply(ref, args); -}); - -ipcMain.on('ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', function() { - var args, event, method, ref; - event = arguments[0], method = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - return event.returnValue = (ref = event.sender)[method].apply(ref, args); -}); - -// JavaScript implementation of Chromium's NavigationController. -// Instead of relying on Chromium for history control, we compeletely do history -// control on user land, and only rely on WebContents.loadURL for navigation. -// This helps us avoid Chromium's various optimizations so we can ensure renderer -// process is restarted everytime. -var NavigationController = (function() { - function NavigationController(webContents) { - this.webContents = webContents; - this.clearHistory(); - - // webContents may have already navigated to a page. - if (this.webContents._getURL()) { - this.currentIndex++; - this.history.push(this.webContents._getURL()); - } - this.webContents.on('navigation-entry-commited', (function(_this) { - return function(event, url, inPage, replaceEntry) { - var currentEntry; - if (_this.inPageIndex > -1 && !inPage) { - - // Navigated to a new page, clear in-page mark. - _this.inPageIndex = -1; - } else if (_this.inPageIndex === -1 && inPage) { - - // Started in-page navigations. - _this.inPageIndex = _this.currentIndex; - } - if (_this.pendingIndex >= 0) { - - // Go to index. - _this.currentIndex = _this.pendingIndex; - _this.pendingIndex = -1; - return _this.history[_this.currentIndex] = url; - } else if (replaceEntry) { - - // Non-user initialized navigation. - return _this.history[_this.currentIndex] = url; - } else { - - // Normal navigation. Clear history. - _this.history = _this.history.slice(0, _this.currentIndex + 1); - currentEntry = _this.history[_this.currentIndex]; - if ((currentEntry != null ? currentEntry.url : void 0) !== url) { - _this.currentIndex++; - return _this.history.push(url); - } - } - }; - })(this)); - } - - NavigationController.prototype.loadURL = function(url, options) { - if (options == null) { - options = {}; - } - this.pendingIndex = -1; - this.webContents._loadURL(url, options); - return this.webContents.emit('load-url', url, options); - }; - - NavigationController.prototype.getURL = function() { - if (this.currentIndex === -1) { - return ''; - } else { - return this.history[this.currentIndex]; - } - }; - - NavigationController.prototype.stop = function() { - this.pendingIndex = -1; - return this.webContents._stop(); - }; - - NavigationController.prototype.reload = function() { - this.pendingIndex = this.currentIndex; - return this.webContents._loadURL(this.getURL(), {}); - }; - - NavigationController.prototype.reloadIgnoringCache = function() { - this.pendingIndex = this.currentIndex; - return this.webContents._loadURL(this.getURL(), { - extraHeaders: "pragma: no-cache\n" - }); - }; - - NavigationController.prototype.canGoBack = function() { - return this.getActiveIndex() > 0; - }; - - NavigationController.prototype.canGoForward = function() { - return this.getActiveIndex() < this.history.length - 1; - }; - - NavigationController.prototype.canGoToIndex = function(index) { - return index >= 0 && index < this.history.length; - }; - - NavigationController.prototype.canGoToOffset = function(offset) { - return this.canGoToIndex(this.currentIndex + offset); - }; - - NavigationController.prototype.clearHistory = function() { - this.history = []; - this.currentIndex = -1; - this.pendingIndex = -1; - return this.inPageIndex = -1; - }; - - NavigationController.prototype.goBack = function() { - if (!this.canGoBack()) { - return; - } - this.pendingIndex = this.getActiveIndex() - 1; - if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { - return this.webContents._goBack(); - } else { - return this.webContents._loadURL(this.history[this.pendingIndex], {}); - } - }; - - NavigationController.prototype.goForward = function() { - if (!this.canGoForward()) { - return; - } - this.pendingIndex = this.getActiveIndex() + 1; - if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { - return this.webContents._goForward(); - } else { - return this.webContents._loadURL(this.history[this.pendingIndex], {}); - } - }; - - NavigationController.prototype.goToIndex = function(index) { - if (!this.canGoToIndex(index)) { - return; - } - this.pendingIndex = index; - return this.webContents._loadURL(this.history[this.pendingIndex], {}); - }; - - NavigationController.prototype.goToOffset = function(offset) { - var pendingIndex; - if (!this.canGoToOffset(offset)) { - return; - } - pendingIndex = this.currentIndex + offset; - if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) { - this.pendingIndex = pendingIndex; - return this.webContents._goToOffset(offset); - } else { - return this.goToIndex(pendingIndex); - } - }; - - NavigationController.prototype.getActiveIndex = function() { - if (this.pendingIndex === -1) { - return this.currentIndex; - } else { - return this.pendingIndex; - } - }; - - NavigationController.prototype.length = function() { - return this.history.length; - }; - - return NavigationController; - -})(); - -module.exports = NavigationController; diff --git a/atom/browser/api/lib/power-monitor.js b/atom/browser/api/lib/power-monitor.js deleted file mode 100644 index 239eb3b4d47..00000000000 --- a/atom/browser/api/lib/power-monitor.js +++ /dev/null @@ -1,6 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const powerMonitor = process.atomBinding('power_monitor').powerMonitor; - -powerMonitor.__proto__ = EventEmitter.prototype; - -module.exports = powerMonitor; diff --git a/atom/browser/api/lib/power-save-blocker.js b/atom/browser/api/lib/power-save-blocker.js deleted file mode 100644 index c44e3e2b63a..00000000000 --- a/atom/browser/api/lib/power-save-blocker.js +++ /dev/null @@ -1,5 +0,0 @@ -var powerSaveBlocker; - -powerSaveBlocker = process.atomBinding('power_save_blocker').powerSaveBlocker; - -module.exports = powerSaveBlocker; diff --git a/atom/browser/api/lib/protocol.js b/atom/browser/api/lib/protocol.js deleted file mode 100644 index 41cb48db09b..00000000000 --- a/atom/browser/api/lib/protocol.js +++ /dev/null @@ -1,31 +0,0 @@ -const app = require('electron').app; - -if (!app.isReady()) { - throw new Error('Can not initialize protocol module before app is ready'); -} - -const protocol = process.atomBinding('protocol').protocol; - -// Warn about removed APIs. -var logAndThrow = function(callback, message) { - console.error(message); - if (callback) { - return callback(new Error(message)); - } else { - throw new Error(message); - } -}; - -protocol.registerProtocol = function(scheme, handler, callback) { - return logAndThrow(callback, 'registerProtocol API has been replaced by the register[File/Http/Buffer/String]Protocol API family, please switch to the new APIs.'); -}; - -protocol.isHandledProtocol = function(scheme, callback) { - return logAndThrow(callback, 'isHandledProtocol API has been replaced by isProtocolHandled.'); -}; - -protocol.interceptProtocol = function(scheme, handler, callback) { - return logAndThrow(callback, 'interceptProtocol API has been replaced by the intercept[File/Http/Buffer/String]Protocol API family, please switch to the new APIs.'); -}; - -module.exports = protocol; diff --git a/atom/browser/api/lib/screen.js b/atom/browser/api/lib/screen.js deleted file mode 100644 index 04965278a3c..00000000000 --- a/atom/browser/api/lib/screen.js +++ /dev/null @@ -1,6 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const screen = process.atomBinding('screen').screen; - -screen.__proto__ = EventEmitter.prototype; - -module.exports = screen; diff --git a/atom/browser/api/lib/session.js b/atom/browser/api/lib/session.js deleted file mode 100644 index dc65264349f..00000000000 --- a/atom/browser/api/lib/session.js +++ /dev/null @@ -1,33 +0,0 @@ -const EventEmitter = require('events').EventEmitter; -const bindings = process.atomBinding('session'); -const PERSIST_PERFIX = 'persist:'; - -// Returns the Session from |partition| string. -exports.fromPartition = function(partition) { - if (partition == null) { - partition = ''; - } - if (partition === '') { - return exports.defaultSession; - } - if (partition.startsWith(PERSIST_PERFIX)) { - return bindings.fromPartition(partition.substr(PERSIST_PERFIX.length), false); - } else { - return bindings.fromPartition(partition, true); - } -}; - -// Returns the default session. -Object.defineProperty(exports, 'defaultSession', { - enumerable: true, - get: function() { - return bindings.fromPartition('', false); - } -}); - -var wrapSession = function(session) { - // session is an EventEmitter. - return session.__proto__ = EventEmitter.prototype; -}; - -bindings._setWrapSession(wrapSession); diff --git a/atom/browser/api/lib/tray.js b/atom/browser/api/lib/tray.js deleted file mode 100644 index 342683552f6..00000000000 --- a/atom/browser/api/lib/tray.js +++ /dev/null @@ -1,23 +0,0 @@ -const deprecate = require('electron').deprecate; -const EventEmitter = require('events').EventEmitter; -const Tray = process.atomBinding('tray').Tray; - -Tray.prototype.__proto__ = EventEmitter.prototype; - -Tray.prototype._init = function() { - // Deprecated. - deprecate.rename(this, 'popContextMenu', 'popUpContextMenu'); - deprecate.event(this, 'clicked', 'click'); - deprecate.event(this, 'double-clicked', 'double-click'); - deprecate.event(this, 'right-clicked', 'right-click'); - return deprecate.event(this, 'balloon-clicked', 'balloon-click'); -}; - -Tray.prototype.setContextMenu = function(menu) { - this._setContextMenu(menu); - - // Keep a strong reference of menu. - return this.menu = menu; -}; - -module.exports = Tray; diff --git a/atom/browser/api/lib/web-contents.js b/atom/browser/api/lib/web-contents.js deleted file mode 100644 index 13e6e97900d..00000000000 --- a/atom/browser/api/lib/web-contents.js +++ /dev/null @@ -1,225 +0,0 @@ -'use strict'; - -const EventEmitter = require('events').EventEmitter; -const deprecate = require('electron').deprecate; -const ipcMain = require('electron').ipcMain; -const NavigationController = require('electron').NavigationController; -const Menu = require('electron').Menu; - -const binding = process.atomBinding('web_contents'); - -let slice = [].slice; -let nextId = 0; - -let getNextId = function() { - return ++nextId; -}; - -let PDFPageSize = { - A5: { - custom_display_name: "A5", - height_microns: 210000, - name: "ISO_A5", - width_microns: 148000 - }, - A4: { - custom_display_name: "A4", - height_microns: 297000, - name: "ISO_A4", - is_default: "true", - width_microns: 210000 - }, - A3: { - custom_display_name: "A3", - height_microns: 420000, - name: "ISO_A3", - width_microns: 297000 - }, - Legal: { - custom_display_name: "Legal", - height_microns: 355600, - name: "NA_LEGAL", - width_microns: 215900 - }, - Letter: { - custom_display_name: "Letter", - height_microns: 279400, - name: "NA_LETTER", - width_microns: 215900 - }, - Tabloid: { - height_microns: 431800, - name: "NA_LEDGER", - width_microns: 279400, - custom_display_name: "Tabloid" - } -}; - -// Following methods are mapped to webFrame. -const webFrameMethods = [ - 'executeJavaScript', - 'insertText', - 'setZoomFactor', - 'setZoomLevel', - 'setZoomLevelLimits' -]; - -let wrapWebContents = function(webContents) { - // webContents is an EventEmitter. - var controller, method, name, ref1; - webContents.__proto__ = EventEmitter.prototype; - - // Every remote callback from renderer process would add a listenter to the - // render-view-deleted event, so ignore the listenters warning. - webContents.setMaxListeners(0); - - // WebContents::send(channel, args..) - webContents.send = function() { - var args, channel; - channel = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - return this._send(channel, slice.call(args)); - }; - - // The navigation controller. - controller = new NavigationController(webContents); - ref1 = NavigationController.prototype; - for (name in ref1) { - method = ref1[name]; - if (method instanceof Function) { - (function(name, method) { - return webContents[name] = function() { - return method.apply(controller, arguments); - }; - })(name, method); - } - } - - // Mapping webFrame methods. - for (let method of webFrameMethods) { - webContents[method] = function() { - let args = Array.prototype.slice.call(arguments); - this.send('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, args); - }; - } - - // Make sure webContents.executeJavaScript would run the code only when the - // webContents has been loaded. - const executeJavaScript = webContents.executeJavaScript; - webContents.executeJavaScript = function(code, hasUserGesture) { - if (this.getURL() && !this.isLoading()) - return executeJavaScript.call(this, code, hasUserGesture); - else - return this.once('did-finish-load', executeJavaScript.bind(this, code, hasUserGesture)); - }; - - // Dispatch IPC messages to the ipc module. - webContents.on('ipc-message', function(event, packed) { - var args, channel; - channel = packed[0], args = 2 <= packed.length ? slice.call(packed, 1) : []; - return ipcMain.emit.apply(ipcMain, [channel, event].concat(slice.call(args))); - }); - webContents.on('ipc-message-sync', function(event, packed) { - var args, channel; - channel = packed[0], args = 2 <= packed.length ? slice.call(packed, 1) : []; - Object.defineProperty(event, 'returnValue', { - set: function(value) { - return event.sendReply(JSON.stringify(value)); - } - }); - return ipcMain.emit.apply(ipcMain, [channel, event].concat(slice.call(args))); - }); - - // Handle context menu action request from pepper plugin. - webContents.on('pepper-context-menu', function(event, params) { - var menu; - menu = Menu.buildFromTemplate(params.menu); - return menu.popup(params.x, params.y); - }); - - // This error occurs when host could not be found. - webContents.on('did-fail-provisional-load', function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - - // Calling loadURL during this event might cause crash, so delay the event - // until next tick. - return setImmediate((function(_this) { - return function() { - return _this.emit.apply(_this, ['did-fail-load'].concat(slice.call(args))); - }; - })(this)); - }); - - // Delays the page-title-updated event to next tick. - webContents.on('-page-title-updated', function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return setImmediate((function(_this) { - return function() { - return _this.emit.apply(_this, ['page-title-updated'].concat(slice.call(args))); - }; - })(this)); - }); - - // Deprecated. - deprecate.rename(webContents, 'loadUrl', 'loadURL'); - deprecate.rename(webContents, 'getUrl', 'getURL'); - deprecate.event(webContents, 'page-title-set', 'page-title-updated', function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return this.emit.apply(this, ['page-title-set'].concat(slice.call(args))); - }); - return webContents.printToPDF = function(options, callback) { - var printingSetting; - printingSetting = { - pageRage: [], - mediaSize: {}, - landscape: false, - color: 2, - headerFooterEnabled: false, - marginsType: 0, - isFirstRequest: false, - requestID: getNextId(), - previewModifiable: true, - printToPDF: true, - printWithCloudPrint: false, - printWithPrivet: false, - printWithExtension: false, - deviceName: "Save as PDF", - generateDraftData: true, - fitToPageEnabled: false, - duplex: 0, - copies: 1, - collate: true, - shouldPrintBackgrounds: false, - shouldPrintSelectionOnly: false - }; - if (options.landscape) { - printingSetting.landscape = options.landscape; - } - if (options.marginsType) { - printingSetting.marginsType = options.marginsType; - } - if (options.printSelectionOnly) { - printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly; - } - if (options.printBackground) { - printingSetting.shouldPrintBackgrounds = options.printBackground; - } - if (options.pageSize && PDFPageSize[options.pageSize]) { - printingSetting.mediaSize = PDFPageSize[options.pageSize]; - } else { - printingSetting.mediaSize = PDFPageSize['A4']; - } - return this._printToPDF(printingSetting, callback); - }; -}; - -binding._setWrapWebContents(wrapWebContents); - -module.exports.create = function(options) { - if (options == null) { - options = {}; - } - return binding.create(options); -}; diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index 5ad8e69ffc7..9a59c3f9d41 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -8,6 +8,7 @@ #include #endif +#include "atom/browser/api/atom_api_app.h" #include "atom/browser/atom_access_token_store.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" @@ -15,6 +16,7 @@ #include "atom/browser/atom_resource_dispatcher_host_delegate.h" #include "atom/browser/atom_speech_recognition_manager_delegate.h" #include "atom/browser/native_window.h" +#include "atom/browser/web_contents_permission_helper.h" #include "atom/browser/web_contents_preferences.h" #include "atom/browser/window_list.h" #include "atom/common/options_switches.h" @@ -45,11 +47,6 @@ namespace atom { namespace { -// The default routing id of WebContents. -// In Electron each RenderProcessHost only has one WebContents, so this ID is -// same for every WebContents. -int kDefaultRoutingID = 2; - // Next navigation should not restart renderer process. bool g_suppress_renderer_process_restart = false; @@ -200,16 +197,10 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches( if (ContainsKey(pending_processes_, process_id)) process_id = pending_processes_[process_id]; - // Certain render process will be created with no associated render view, // for example: ServiceWorker. - auto rvh = content::RenderViewHost::FromID(process_id, kDefaultRoutingID); - if (!rvh) - return; - - // Get the WebContents of the render process. content::WebContents* web_contents = - content::WebContents::FromRenderViewHost(rvh); + WebContentsPreferences::GetWebContentsFromProcessID(process_id); if (!web_contents) return; @@ -229,8 +220,7 @@ content::QuotaPermissionContext* } void AtomBrowserClient::AllowCertificateError( - int render_process_id, - int render_frame_id, + content::WebContents* web_contents, int cert_error, const net::SSLInfo& ssl_info, const GURL& request_url, @@ -242,7 +232,7 @@ void AtomBrowserClient::AllowCertificateError( content::CertificateRequestResultType* request) { if (delegate_) { delegate_->AllowCertificateError( - render_process_id, render_frame_id, cert_error, ssl_info, request_url, + web_contents, cert_error, ssl_info, request_url, resource_type, overridable, strict_enforcement, expired_previous_decision, callback, request); } @@ -264,7 +254,7 @@ void AtomBrowserClient::SelectClientCertificate( if (!cert_request_info->client_certs.empty() && delegate_) { delegate_->SelectClientCertificate( - web_contents, cert_request_info, delegate.Pass()); + web_contents, cert_request_info, std::move(delegate)); } } @@ -275,12 +265,63 @@ void AtomBrowserClient::ResourceDispatcherHostCreated() { resource_dispatcher_host_delegate_.get()); } +bool AtomBrowserClient::CanCreateWindow( + const GURL& opener_url, + const GURL& opener_top_level_frame_url, + const GURL& source_origin, + WindowContainerType container_type, + const std::string& frame_name, + const GURL& target_url, + const content::Referrer& referrer, + WindowOpenDisposition disposition, + const blink::WebWindowFeatures& features, + bool user_gesture, + bool opener_suppressed, + content::ResourceContext* context, + int render_process_id, + int opener_render_view_id, + int opener_render_frame_id, + bool* no_javascript_access) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + if (delegate_) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(&api::App::OnCreateWindow, + base::Unretained(static_cast(delegate_)), + target_url, + frame_name, + disposition, + render_process_id, + opener_render_frame_id)); + } + + return false; +} + brightray::BrowserMainParts* AtomBrowserClient::OverrideCreateBrowserMainParts( const content::MainFunctionParams&) { v8::V8::Initialize(); // Init V8 before creating main parts. return new AtomBrowserMainParts; } +void AtomBrowserClient::WebNotificationAllowed( + int render_process_id, + const base::Callback& callback) { + content::WebContents* web_contents = + WebContentsPreferences::GetWebContentsFromProcessID(render_process_id); + if (!web_contents) { + callback.Run(false); + return; + } + auto permission_helper = + WebContentsPermissionHelper::FromWebContents(web_contents); + if (!permission_helper) { + callback.Run(false); + return; + } + permission_helper->RequestWebNotificationPermission(callback); +} + void AtomBrowserClient::RenderProcessHostDestroyed( content::RenderProcessHost* host) { int process_id = host->GetID(); diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index 3c54fab40bc..5354e14cc61 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -61,8 +61,7 @@ class AtomBrowserClient : public brightray::BrowserClient, void DidCreatePpapiPlugin(content::BrowserPpapiHost* browser_host) override; content::QuotaPermissionContext* CreateQuotaPermissionContext() override; void AllowCertificateError( - int render_process_id, - int render_frame_id, + content::WebContents* web_contents, int cert_error, const net::SSLInfo& ssl_info, const GURL& request_url, @@ -77,10 +76,29 @@ class AtomBrowserClient : public brightray::BrowserClient, net::SSLCertRequestInfo* cert_request_info, scoped_ptr delegate) override; void ResourceDispatcherHostCreated() override; + bool CanCreateWindow(const GURL& opener_url, + const GURL& opener_top_level_frame_url, + const GURL& source_origin, + WindowContainerType container_type, + const std::string& frame_name, + const GURL& target_url, + const content::Referrer& referrer, + WindowOpenDisposition disposition, + const blink::WebWindowFeatures& features, + bool user_gesture, + bool opener_suppressed, + content::ResourceContext* context, + int render_process_id, + int opener_render_view_id, + int opener_render_frame_id, + bool* no_javascript_access) override; // brightray::BrowserClient: brightray::BrowserMainParts* OverrideCreateBrowserMainParts( const content::MainFunctionParams&) override; + void WebNotificationAllowed( + int render_process_id, + const base::Callback& callback) override; // content::RenderProcessHostObserver: void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index ac59f8c3133..d6724ff5338 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -13,6 +13,7 @@ #include "atom/browser/net/atom_url_request_job_factory.h" #include "atom/browser/net/asar/asar_protocol_handler.h" #include "atom/browser/net/http_protocol_handler.h" +#include "atom/browser/atom_permission_manager.h" #include "atom/browser/web_view_manager.h" #include "atom/common/atom_version.h" #include "atom/common/chrome_version.h" @@ -133,14 +134,15 @@ AtomBrowserContext::CreateURLRequestJobFactory( new net::FtpNetworkLayer(host_resolver)))); // Set up interceptors in the reverse order. - scoped_ptr top_job_factory = job_factory.Pass(); + scoped_ptr top_job_factory = + std::move(job_factory); content::URLRequestInterceptorScopedVector::reverse_iterator it; for (it = interceptors->rbegin(); it != interceptors->rend(); ++it) top_job_factory.reset(new net::URLRequestInterceptingJobFactory( - top_job_factory.Pass(), make_scoped_ptr(*it))); + std::move(top_job_factory), make_scoped_ptr(*it))); interceptors->weak_clear(); - return top_job_factory.Pass(); + return top_job_factory; } net::HttpCache::BackendFactory* @@ -169,6 +171,12 @@ content::BrowserPluginGuestManager* AtomBrowserContext::GetGuestManager() { return guest_manager_.get(); } +content::PermissionManager* AtomBrowserContext::GetPermissionManager() { + if (!permission_manager_.get()) + permission_manager_.reset(new AtomPermissionManager); + return permission_manager_.get(); +} + scoped_ptr AtomBrowserContext::CreateCertVerifier() { DCHECK(!cert_verifier_); cert_verifier_ = new AtomCertVerifier; @@ -186,6 +194,7 @@ void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &download_dir); pref_registry->RegisterFilePathPref(prefs::kDownloadDefaultDirectory, download_dir); + pref_registry->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths); } bool AtomBrowserContext::AllowNTLMCredentialsForDomain(const GURL& origin) { diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index 9c94a60c305..d959adbc753 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -14,6 +14,7 @@ namespace atom { class AtomDownloadManagerDelegate; class AtomCertVerifier; class AtomNetworkDelegate; +class AtomPermissionManager; class AtomURLRequestJobFactory; class WebViewManager; @@ -37,6 +38,7 @@ class AtomBrowserContext : public brightray::BrowserContext { // content::BrowserContext: content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; content::BrowserPluginGuestManager* GetGuestManager() override; + content::PermissionManager* GetPermissionManager() override; // brightray::BrowserContext: void RegisterPrefs(PrefRegistrySimple* pref_registry) override; @@ -52,6 +54,7 @@ class AtomBrowserContext : public brightray::BrowserContext { private: scoped_ptr download_manager_delegate_; scoped_ptr guest_manager_; + scoped_ptr permission_manager_; // Managed by brightray::BrowserContext. AtomCertVerifier* cert_verifier_; diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index a046f34287e..f45f6492a84 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -46,6 +46,14 @@ AtomBrowserMainParts::AtomBrowserMainParts() } AtomBrowserMainParts::~AtomBrowserMainParts() { + // 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 + // about this can be found at https://github.com/atom/electron/issues/4767. + // On the other handle there is actually no need to gracefully shutdown V8 + // on exit in the main process, we already ensured all necessary resources get + // cleaned up, and it would make quitting faster. + ignore_result(js_env_.release()); } // static @@ -98,17 +106,21 @@ void AtomBrowserMainParts::PostEarlyInitialization() { node_debugger_.reset(new NodeDebugger(js_env_->isolate())); // Create the global environment. - global_env = node_bindings_->CreateEnvironment(js_env_->context()); + node::Environment* env = + node_bindings_->CreateEnvironment(js_env_->context()); // Make sure node can get correct environment when debugging. if (node_debugger_->IsRunning()) - global_env->AssignToContext(v8::Debug::GetDebugContext()); + env->AssignToContext(v8::Debug::GetDebugContext()); // Add atom-shell extended APIs. - atom_bindings_->BindTo(js_env_->isolate(), global_env->process_object()); + atom_bindings_->BindTo(js_env_->isolate(), env->process_object()); // Load everything. - node_bindings_->LoadEnvironment(global_env); + node_bindings_->LoadEnvironment(env); + + // Wrap the uv loop with global env. + node_bindings_->set_uv_env(env); } void AtomBrowserMainParts::PreMainMessageLoopRun() { @@ -172,14 +184,6 @@ void AtomBrowserMainParts::PostMainMessageLoopRun() { ++iter; callback.Run(); } - - // Destroy JavaScript environment immediately after running destruction - // callbacks. - gc_timer_.Stop(); - node_debugger_.reset(); - atom_bindings_.reset(); - node_bindings_.reset(); - js_env_.reset(); } } // namespace atom diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index c1c0c89c678..e1053a257b9 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -31,7 +31,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { static AtomBrowserMainParts* Get(); - // Sets the exit code, will fail if the the message loop is not ready. + // Sets the exit code, will fail if the message loop is not ready. bool SetExitCode(int code); // Gets the exit code diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc index a5f5cc6d8e4..16c0cf708b8 100644 --- a/atom/browser/atom_download_manager_delegate.cc +++ b/atom/browser/atom_download_manager_delegate.cc @@ -60,7 +60,7 @@ void AtomDownloadManagerDelegate::CreateDownloadPath( } void AtomDownloadManagerDelegate::OnDownloadPathGenerated( - uint32 download_id, + uint32_t download_id, const content::DownloadTargetCallback& callback, const base::FilePath& default_path) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -109,16 +109,24 @@ bool AtomDownloadManagerDelegate::DetermineDownloadTarget( download->GetForcedFilePath()); return true; } - base::SupportsUserData::Data* save_path = download->GetUserData( - atom::api::DownloadItem::UserDataKey()); - if (save_path) { - const base::FilePath& default_download_path = - static_cast(save_path)->path(); - callback.Run(default_download_path, - content::DownloadItem::TARGET_DISPOSITION_OVERWRITE, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - default_download_path); - return true; + + // Try to get the save path from JS wrapper. + { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + api::DownloadItem* download_item = api::DownloadItem::FromWrappedClass( + isolate, download); + if (download_item) { + base::FilePath save_path = download_item->GetSavePath(); + if (!save_path.empty()) { + callback.Run(save_path, + content::DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + save_path); + return true; + } + } } AtomBrowserContext* browser_context = static_cast( @@ -152,7 +160,7 @@ bool AtomDownloadManagerDelegate::ShouldOpenDownload( void AtomDownloadManagerDelegate::GetNextId( const content::DownloadIdCallback& callback) { - static uint32 next_id = content::DownloadItem::kInvalidId + 1; + static uint32_t next_id = content::DownloadItem::kInvalidId + 1; callback.Run(next_id++); } diff --git a/atom/browser/atom_download_manager_delegate.h b/atom/browser/atom_download_manager_delegate.h index 2df3a7d45a6..5ea3d50d5ae 100644 --- a/atom/browser/atom_download_manager_delegate.h +++ b/atom/browser/atom_download_manager_delegate.h @@ -31,7 +31,7 @@ class AtomDownloadManagerDelegate : public content::DownloadManagerDelegate { const std::string& mime_type, const base::FilePath& path, const CreateDownloadPathCallback& callback); - void OnDownloadPathGenerated(uint32 download_id, + void OnDownloadPathGenerated(uint32_t download_id, const content::DownloadTargetCallback& callback, const base::FilePath& default_path); diff --git a/atom/browser/atom_permission_manager.cc b/atom/browser/atom_permission_manager.cc new file mode 100644 index 00000000000..f7523c07ff8 --- /dev/null +++ b/atom/browser/atom_permission_manager.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2016 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_permission_manager.h" + +#include + +#include "atom/browser/web_contents_preferences.h" +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/permission_type.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" + +namespace atom { + +namespace { + +bool WebContentsDestroyed(int process_id) { + auto contents = + WebContentsPreferences::GetWebContentsFromProcessID(process_id); + if (!contents) + return true; + return contents->IsBeingDestroyed(); +} + +} // namespace + +AtomPermissionManager::AtomPermissionManager() + : request_id_(0) { +} + +AtomPermissionManager::~AtomPermissionManager() { +} + +void AtomPermissionManager::SetPermissionRequestHandler( + const RequestHandler& handler) { + if (handler.is_null() && !pending_requests_.empty()) { + for (const auto& request : pending_requests_) { + if (!WebContentsDestroyed(request.second.render_process_id)) + request.second.callback.Run(content::PERMISSION_STATUS_DENIED); + } + pending_requests_.clear(); + } + request_handler_ = handler; +} + +int AtomPermissionManager::RequestPermission( + content::PermissionType permission, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const ResponseCallback& response_callback) { + int process_id = render_frame_host->GetProcess()->GetID(); + + if (permission == content::PermissionType::MIDI_SYSEX) { + content::ChildProcessSecurityPolicy::GetInstance()-> + GrantSendMidiSysExMessage(process_id); + } + + if (!request_handler_.is_null()) { + auto web_contents = + content::WebContents::FromRenderFrameHost(render_frame_host); + ++request_id_; + auto callback = base::Bind(&AtomPermissionManager::OnPermissionResponse, + base::Unretained(this), + request_id_, + requesting_origin, + response_callback); + pending_requests_[request_id_] = { process_id, callback }; + request_handler_.Run(web_contents, permission, callback); + return request_id_; + } + + response_callback.Run(content::PERMISSION_STATUS_GRANTED); + return kNoPendingOperation; +} + +int AtomPermissionManager::RequestPermissions( + const std::vector& permissions, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const base::Callback&)>& callback) { + // FIXME(zcbenz): Just ignore multiple permissions request for now. + std::vector permissionStatuses; + for (auto permission : permissions) { + if (permission == content::PermissionType::MIDI_SYSEX) { + content::ChildProcessSecurityPolicy::GetInstance()-> + GrantSendMidiSysExMessage(render_frame_host->GetProcess()->GetID()); + } + permissionStatuses.push_back(content::PERMISSION_STATUS_GRANTED); + } + callback.Run(permissionStatuses); + return kNoPendingOperation; +} + +void AtomPermissionManager::OnPermissionResponse( + int request_id, + const GURL& origin, + const ResponseCallback& callback, + content::PermissionStatus status) { + auto request = pending_requests_.find(request_id); + if (request != pending_requests_.end()) { + if (!WebContentsDestroyed(request->second.render_process_id)) + callback.Run(status); + pending_requests_.erase(request); + } +} + +void AtomPermissionManager::CancelPermissionRequest(int request_id) { + auto request = pending_requests_.find(request_id); + if (request != pending_requests_.end()) { + if (!WebContentsDestroyed(request->second.render_process_id)) + request->second.callback.Run(content::PERMISSION_STATUS_DENIED); + pending_requests_.erase(request); + } +} + +void AtomPermissionManager::ResetPermission( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) { +} + +content::PermissionStatus AtomPermissionManager::GetPermissionStatus( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) { + return content::PERMISSION_STATUS_GRANTED; +} + +void AtomPermissionManager::RegisterPermissionUsage( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) { +} + +int AtomPermissionManager::SubscribePermissionStatusChange( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin, + const ResponseCallback& callback) { + return -1; +} + +void AtomPermissionManager::UnsubscribePermissionStatusChange( + int subscription_id) { +} + +} // namespace atom diff --git a/atom/browser/atom_permission_manager.h b/atom/browser/atom_permission_manager.h new file mode 100644 index 00000000000..e16893fd8bb --- /dev/null +++ b/atom/browser/atom_permission_manager.h @@ -0,0 +1,92 @@ +// Copyright (c) 2016 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_PERMISSION_MANAGER_H_ +#define ATOM_BROWSER_ATOM_PERMISSION_MANAGER_H_ + +#include +#include + +#include "base/callback.h" +#include "content/public/browser/permission_manager.h" + +namespace content { +class WebContents; +} + +namespace atom { + +class AtomPermissionManager : public content::PermissionManager { + public: + AtomPermissionManager(); + ~AtomPermissionManager() override; + + using ResponseCallback = + base::Callback; + using RequestHandler = + base::Callback; + + // Handler to dispatch permission requests in JS. + void SetPermissionRequestHandler(const RequestHandler& handler); + + // content::PermissionManager: + int RequestPermission( + content::PermissionType permission, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const ResponseCallback& callback) override; + int RequestPermissions( + const std::vector& permissions, + content::RenderFrameHost* render_frame_host, + const GURL& requesting_origin, + bool user_gesture, + const base::Callback&)>& callback) override; + + protected: + void OnPermissionResponse(int request_id, + const GURL& url, + const ResponseCallback& callback, + content::PermissionStatus status); + + // content::PermissionManager: + void CancelPermissionRequest(int request_id) override; + void ResetPermission(content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) override; + content::PermissionStatus GetPermissionStatus( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) override; + void RegisterPermissionUsage(content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin) override; + int SubscribePermissionStatusChange( + content::PermissionType permission, + const GURL& requesting_origin, + const GURL& embedding_origin, + const base::Callback& callback) override; + void UnsubscribePermissionStatusChange(int subscription_id) override; + + private: + struct RequestInfo { + int render_process_id; + ResponseCallback callback; + }; + + RequestHandler request_handler_; + + std::map pending_requests_; + + int request_id_; + + DISALLOW_COPY_AND_ASSIGN(AtomPermissionManager); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_PERMISSION_MANAGER_H_ diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.cc b/atom/browser/atom_resource_dispatcher_host_delegate.cc index aaba1f31045..68576a52f24 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.cc +++ b/atom/browser/atom_resource_dispatcher_host_delegate.cc @@ -19,14 +19,15 @@ AtomResourceDispatcherHostDelegate::AtomResourceDispatcherHostDelegate() { bool AtomResourceDispatcherHostDelegate::HandleExternalProtocol( const GURL& url, - int render_process_id, - int render_view_id, + int child_id, + const content::ResourceRequestInfo::WebContentsGetter&, bool is_main_frame, ui::PageTransition transition, bool has_user_gesture) { GURL escaped_url(net::EscapeExternalHandlerValue(url.spec())); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::IgnoreResult(platform_util::OpenExternal), escaped_url)); + base::Bind( + base::IgnoreResult(platform_util::OpenExternal), escaped_url, true)); return true; } diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.h b/atom/browser/atom_resource_dispatcher_host_delegate.h index a90b366bc75..408b83c92d9 100644 --- a/atom/browser/atom_resource_dispatcher_host_delegate.h +++ b/atom/browser/atom_resource_dispatcher_host_delegate.h @@ -15,12 +15,13 @@ class AtomResourceDispatcherHostDelegate AtomResourceDispatcherHostDelegate(); // content::ResourceDispatcherHostDelegate: - bool HandleExternalProtocol(const GURL& url, - int render_process_id, - int render_view_id, - bool is_main_frame, - ui::PageTransition transition, - bool has_user_gesture) override; + bool HandleExternalProtocol( + const GURL& url, + int child_id, + const content::ResourceRequestInfo::WebContentsGetter&, + bool is_main_frame, + ui::PageTransition transition, + bool has_user_gesture) override; content::ResourceDispatcherHostLoginDelegate* CreateLoginDelegate( net::AuthChallengeInfo* auth_info, net::URLRequest* request) override; diff --git a/atom/browser/atom_speech_recognition_manager_delegate.h b/atom/browser/atom_speech_recognition_manager_delegate.h index ec31e227baf..4c78e0eead1 100644 --- a/atom/browser/atom_speech_recognition_manager_delegate.h +++ b/atom/browser/atom_speech_recognition_manager_delegate.h @@ -7,6 +7,7 @@ #include +#include "base/macros.h" #include "content/public/browser/speech_recognition_event_listener.h" #include "content/public/browser/speech_recognition_manager_delegate.h" diff --git a/atom/browser/auto_updater.h b/atom/browser/auto_updater.h index 9e479d4220d..d13c6f0c330 100644 --- a/atom/browser/auto_updater.h +++ b/atom/browser/auto_updater.h @@ -7,7 +7,8 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" +#include "build/build_config.h" namespace base { class Time; diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 7a2c22ea9d2..e89f52283b3 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -181,4 +181,8 @@ void Browser::OnWindowAllClosed() { FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWindowAllClosed()); } +void Browser::PlatformThemeChanged() { + FOR_EACH_OBSERVER(BrowserObserver, observers_, OnPlatformThemeChanged()); +} + } // namespace atom diff --git a/atom/browser/browser.h b/atom/browser/browser.h index e46624b158d..634e14e6026 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -8,7 +8,7 @@ #include #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "base/observer_list.h" #include "base/strings/string16.h" @@ -27,6 +27,10 @@ namespace ui { class MenuModel; } +namespace gfx { +class Image; +} + namespace atom { class LoginHandler; @@ -72,7 +76,22 @@ class Browser : public WindowListObserver { // Set the application user model ID. void SetAppUserModelID(const base::string16& name); + // Remove the default protocol handler registry key + bool RemoveAsDefaultProtocolClient(const std::string& protocol); + + // Set as default handler for a protocol. + bool SetAsDefaultProtocolClient(const std::string& protocol); + #if defined(OS_MACOSX) + // Hide the application. + void Hide(); + + // Show the application. + void Show(); + + // Check if the system is in Dark Mode. + bool IsDarkMode(); + // Bounce the dock icon. enum BounceType { BOUNCE_CRITICAL = 0, @@ -91,6 +110,9 @@ class Browser : public WindowListObserver { // Set docks' menu. void DockSetMenu(ui::MenuModel* model); + + // Set docks' icon. + void DockSetIcon(const gfx::Image& image); #endif // defined(OS_MACOSX) #if defined(OS_WIN) @@ -129,6 +151,9 @@ class Browser : public WindowListObserver { // Request basic auth login. void RequestLogin(LoginHandler* login_handler); + // Tell the application that plaform's theme changed. + void PlatformThemeChanged(); + void AddObserver(BrowserObserver* obs) { observers_.AddObserver(obs); } diff --git a/atom/browser/browser_linux.cc b/atom/browser/browser_linux.cc index 25cb9a0a238..6c7d4abaf64 100644 --- a/atom/browser/browser_linux.cc +++ b/atom/browser/browser_linux.cc @@ -34,6 +34,14 @@ void Browser::ClearRecentDocuments() { void Browser::SetAppUserModelID(const base::string16& name) { } +bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol) { + return false; +} + +bool Browser::SetAsDefaultProtocolClient(const std::string& protocol) { + return false; +} + std::string Browser::GetExecutableFileVersion() const { return brightray::GetApplicationVersion(); } diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 8b1dd31f355..0294894fcd6 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -8,6 +8,7 @@ #include "atom/browser/mac/atom_application_delegate.h" #include "atom/browser/native_window.h" #include "atom/browser/window_list.h" +#include "base/mac/bundle_locations.h" #include "base/mac/foundation_util.h" #include "base/strings/sys_string_conversions.h" #include "brightray/common/application_info.h" @@ -18,6 +19,19 @@ void Browser::Focus() { [[AtomApplication sharedApplication] activateIgnoringOtherApps:YES]; } +void Browser::Hide() { + [[AtomApplication sharedApplication] hide:nil]; +} + +void Browser::Show() { + [[AtomApplication sharedApplication] unhide:nil]; +} + +bool Browser::IsDarkMode() { + NSString *mode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; + return [mode isEqualToString: @"Dark"]; +} + void Browser::AddRecentDocument(const base::FilePath& path) { NSString* path_string = base::mac::FilePathToNSString(path); if (!path_string) @@ -32,6 +46,25 @@ void Browser::ClearRecentDocuments() { [[NSDocumentController sharedDocumentController] clearRecentDocuments:nil]; } +bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol) { + return false; +} + +bool Browser::SetAsDefaultProtocolClient(const std::string& protocol) { + if (protocol.empty()) + return false; + + NSString* identifier = [base::mac::MainBundle() bundleIdentifier]; + if (!identifier) + return false; + + NSString* protocol_ns = [NSString stringWithUTF8String:protocol.c_str()]; + OSStatus return_code = + LSSetDefaultHandlerForURLScheme(base::mac::NSToCFCast(protocol_ns), + base::mac::NSToCFCast(identifier)); + return return_code == noErr; +} + void Browser::SetAppUserModelID(const base::string16& name) { } @@ -48,8 +81,8 @@ int Browser::DockBounce(BounceType type) { requestUserAttention:(NSRequestUserAttentionType)type]; } -void Browser::DockCancelBounce(int rid) { - [[AtomApplication sharedApplication] cancelUserAttentionRequest:rid]; +void Browser::DockCancelBounce(int request_id) { + [[AtomApplication sharedApplication] cancelUserAttentionRequest:request_id]; } void Browser::DockSetBadgeText(const std::string& label) { @@ -102,4 +135,9 @@ void Browser::DockSetMenu(ui::MenuModel* model) { [delegate setApplicationDockMenu:model]; } +void Browser::DockSetIcon(const gfx::Image& image) { + [[AtomApplication sharedApplication] + setApplicationIconImage:image.AsNSImage()]; +} + } // namespace atom diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index f6d76bc13fb..da327eb90a0 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -45,6 +45,8 @@ class BrowserObserver { // The browser requests HTTP login. virtual void OnLogin(LoginHandler* login_handler) {} + virtual void OnPlatformThemeChanged() {} + protected: virtual ~BrowserObserver() {} }; diff --git a/atom/browser/browser_win.cc b/atom/browser/browser_win.cc index fdf4bd8c3bb..9531406f8cd 100644 --- a/atom/browser/browser_win.cc +++ b/atom/browser/browser_win.cc @@ -19,6 +19,7 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/win/win_util.h" +#include "base/win/registry.h" #include "base/win/windows_version.h" #include "atom/common/atom_version.h" @@ -125,6 +126,105 @@ void Browser::SetUserTasks(const std::vector& tasks) { destinations->CommitList(); } +bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol) { + if (protocol.empty()) + return false; + + base::FilePath path; + if (!PathService::Get(base::FILE_EXE, &path)) { + LOG(ERROR) << "Error getting app exe path"; + return false; + } + + // Main Registry Key + HKEY root = HKEY_CURRENT_USER; + std::string keyPathStr = "Software\\Classes\\" + protocol; + std::wstring keyPath = std::wstring(keyPathStr.begin(), keyPathStr.end()); + + // Command Key + std::string cmdPathStr = keyPathStr + "\\shell\\open\\command"; + std::wstring cmdPath = std::wstring(cmdPathStr.begin(), cmdPathStr.end()); + + base::win::RegKey key; + base::win::RegKey commandKey; + if (FAILED(key.Open(root, keyPath.c_str(), KEY_ALL_ACCESS))) + // Key doesn't even exist, we can confirm that it is not set + return true; + + if (FAILED(commandKey.Open(root, cmdPath.c_str(), KEY_ALL_ACCESS))) + // Key doesn't even exist, we can confirm that it is not set + return true; + + std::wstring keyVal; + if (FAILED(commandKey.ReadValue(L"", &keyVal))) + // Default value not set, we can confirm that it is not set + return true; + + std::wstring exePath(path.value()); + std::wstring exe = L"\"" + exePath + L"\" \"%1\""; + if (keyVal == exe) { + // Let's kill the key + if (FAILED(key.DeleteKey(L"shell"))) + return false; + + return true; + } else { + return true; + } +} + +bool Browser::SetAsDefaultProtocolClient(const std::string& protocol) { + // HKEY_CLASSES_ROOT + // $PROTOCOL + // (Default) = "URL:$NAME" + // URL Protocol = "" + // shell + // open + // command + // (Default) = "$COMMAND" "%1" + // + // However, the "HKEY_CLASSES_ROOT" key can only be written by the + // Administrator user. So, we instead write to "HKEY_CURRENT_USER\ + // Software\Classes", which is inherited by "HKEY_CLASSES_ROOT" + // anyway, and can be written by unprivileged users. + + if (protocol.empty()) + return false; + + base::FilePath path; + if (!PathService::Get(base::FILE_EXE, &path)) { + LOG(ERROR) << "Error getting app exe path"; + return false; + } + + // Main Registry Key + HKEY root = HKEY_CURRENT_USER; + std::string keyPathStr = "Software\\Classes\\" + protocol; + std::wstring keyPath = std::wstring(keyPathStr.begin(), keyPathStr.end()); + std::string urlDeclStr = "URL:" + protocol; + std::wstring urlDecl = std::wstring(urlDeclStr.begin(), urlDeclStr.end()); + + // Command Key + std::string cmdPathStr = keyPathStr + "\\shell\\open\\command"; + std::wstring cmdPath = std::wstring(cmdPathStr.begin(), cmdPathStr.end()); + + // Executable Path + std::wstring exePath(path.value()); + std::wstring exe = L"\"" + exePath + L"\" \"%1\""; + + // Write information to registry + base::win::RegKey key(root, keyPath.c_str(), KEY_ALL_ACCESS); + if (FAILED(key.WriteValue(L"URL Protocol", L"")) || + FAILED(key.WriteValue(L"", urlDecl.c_str()))) + return false; + + base::win::RegKey commandKey(root, cmdPath.c_str(), KEY_ALL_ACCESS); + if (FAILED(commandKey.WriteValue(L"", exe.c_str()))) + return false; + + return true; +} + PCWSTR Browser::GetAppUserModelID() { if (app_user_model_id_.empty()) { SetAppUserModelID(base::ReplaceStringPlaceholders( diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index 72a664f8cd0..62e854ac565 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -4,21 +4,27 @@ #include "atom/browser/common_web_contents_delegate.h" +#include #include #include +#include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_javascript_dialog_manager.h" #include "atom/browser/native_window.h" #include "atom/browser/ui/file_dialog.h" #include "atom/browser/web_dialog_helper.h" #include "base/files/file_util.h" +#include "base/prefs/pref_service.h" +#include "base/prefs/scoped_user_pref_update.h" #include "chrome/browser/printing/print_preview_message_handler.h" #include "chrome/browser/printing/print_view_manager_basic.h" #include "chrome/browser/ui/browser_dialogs.h" +#include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" #include "storage/browser/fileapi/isolated_context.h" #if defined(TOOLKIT_VIEWS) @@ -35,6 +41,8 @@ namespace atom { namespace { +const char kRootName[] = ""; + struct FileSystem { FileSystem() { } @@ -52,14 +60,14 @@ struct FileSystem { }; std::string RegisterFileSystem(content::WebContents* web_contents, - const base::FilePath& path, - std::string* registered_name) { + const base::FilePath& path) { auto isolated_context = storage::IsolatedContext::GetInstance(); + std::string root_name(kRootName); std::string file_system_id = isolated_context->RegisterFileSystemForPath( storage::kFileSystemTypeNativeLocal, std::string(), path, - registered_name); + &root_name); content::ChildProcessSecurityPolicy* policy = content::ChildProcessSecurityPolicy::GetInstance(); @@ -79,13 +87,12 @@ std::string RegisterFileSystem(content::WebContents* web_contents, FileSystem CreateFileSystemStruct( content::WebContents* web_contents, const std::string& file_system_id, - const std::string& registered_name, const std::string& file_system_path) { const GURL origin = web_contents->GetURL().GetOrigin(); std::string file_system_name = storage::GetIsolatedFileSystemName(origin, file_system_id); std::string root_url = storage::GetIsolatedFileSystemRootURIString( - origin, file_system_id, registered_name); + origin, file_system_id, kRootName); return FileSystem(file_system_name, root_url, file_system_path); } @@ -113,6 +120,26 @@ void AppendToFile(const base::FilePath& path, base::AppendToFile(path, content.data(), content.size()); } +PrefService* GetPrefService(content::WebContents* web_contents) { + auto context = web_contents->GetBrowserContext(); + return static_cast(context)->prefs(); +} + +std::set GetAddedFileSystemPaths( + content::WebContents* web_contents) { + auto pref_service = GetPrefService(web_contents); + const base::DictionaryValue* file_system_paths_value = + pref_service->GetDictionary(prefs::kDevToolsFileSystemPaths); + std::set result; + if (file_system_paths_value) { + base::DictionaryValue::Iterator it(*file_system_paths_value); + for (; !it.IsAtEnd(); it.Advance()) { + result.insert(it.key()); + } + } + return result; +} + } // namespace CommonWebContentsDelegate::CommonWebContentsDelegate() @@ -173,21 +200,12 @@ content::WebContents* CommonWebContentsDelegate::OpenURLFromTab( load_url_params.should_replace_current_entry = params.should_replace_current_entry; load_url_params.is_renderer_initiated = params.is_renderer_initiated; - load_url_params.transferred_global_request_id = - params.transferred_global_request_id; load_url_params.should_clear_history_list = true; source->GetController().LoadURLWithParams(load_url_params); return source; } -void CommonWebContentsDelegate::RequestToLockMouse( - content::WebContents* web_contents, - bool user_gesture, - bool last_unlocked_by_target) { - GetWebContents()->GotResponseToLockMouseRequest(true); -} - bool CommonWebContentsDelegate::CanOverscrollContent() const { return false; } @@ -230,7 +248,7 @@ void CommonWebContentsDelegate::EnterFullscreenModeForTab( return; SetHtmlApiFullscreen(true); owner_window_->NotifyWindowEnterHtmlFullScreen(); - source->GetRenderViewHost()->WasResized(); + source->GetRenderViewHost()->GetWidget()->WasResized(); } void CommonWebContentsDelegate::ExitFullscreenModeForTab( @@ -239,7 +257,7 @@ void CommonWebContentsDelegate::ExitFullscreenModeForTab( return; SetHtmlApiFullscreen(false); owner_window_->NotifyWindowLeaveHtmlFullScreen(); - source->GetRenderViewHost()->WasResized(); + source->GetRenderViewHost()->GetWidget()->WasResized(); } bool CommonWebContentsDelegate::IsFullscreenForTabOrPending( @@ -286,6 +304,34 @@ void CommonWebContentsDelegate::DevToolsAppendToFile( base::Unretained(this), url)); } +void CommonWebContentsDelegate::DevToolsRequestFileSystems() { + auto file_system_paths = GetAddedFileSystemPaths(GetDevToolsWebContents()); + if (file_system_paths.empty()) { + base::ListValue empty_file_system_value; + web_contents_->CallClientFunction("DevToolsAPI.fileSystemsLoaded", + &empty_file_system_value, + nullptr, nullptr); + return; + } + + std::vector file_systems; + for (auto file_system_path : file_system_paths) { + base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path); + std::string file_system_id = RegisterFileSystem(GetDevToolsWebContents(), + path); + FileSystem file_system = CreateFileSystemStruct(GetDevToolsWebContents(), + file_system_id, + file_system_path); + file_systems.push_back(file_system); + } + + base::ListValue file_system_value; + for (size_t i = 0; i < file_systems.size(); ++i) + file_system_value.Append(CreateFileSystemValue(file_systems[i])); + web_contents_->CallClientFunction("DevToolsAPI.fileSystemsLoaded", + &file_system_value, nullptr, nullptr); +} + void CommonWebContentsDelegate::DevToolsAddFileSystem( const base::FilePath& file_system_path) { base::FilePath path = file_system_path; @@ -301,32 +347,26 @@ void CommonWebContentsDelegate::DevToolsAddFileSystem( path = paths[0]; } - std::string registered_name; std::string file_system_id = RegisterFileSystem(GetDevToolsWebContents(), - path, - ®istered_name); - - WorkspaceMap::iterator it = saved_paths_.find(file_system_id); - if (it != saved_paths_.end()) + path); + auto file_system_paths = GetAddedFileSystemPaths(GetDevToolsWebContents()); + if (file_system_paths.find(path.AsUTF8Unsafe()) != file_system_paths.end()) return; - saved_paths_[file_system_id] = path; - FileSystem file_system = CreateFileSystemStruct(GetDevToolsWebContents(), - file_system_id, - registered_name, - path.AsUTF8Unsafe()); + file_system_id, + path.AsUTF8Unsafe()); + scoped_ptr file_system_value( + CreateFileSystemValue(file_system)); - scoped_ptr error_string_value( - new base::StringValue(std::string())); - scoped_ptr file_system_value; - if (!file_system.file_system_path.empty()) - file_system_value.reset(CreateFileSystemValue(file_system)); - web_contents_->CallClientFunction( - "DevToolsAPI.fileSystemAdded", - error_string_value.get(), - file_system_value.get(), - nullptr); + auto pref_service = GetPrefService(GetDevToolsWebContents()); + DictionaryPrefUpdate update(pref_service, prefs::kDevToolsFileSystemPaths); + update.Get()->SetWithoutPathExpansion( + path.AsUTF8Unsafe(), base::Value::CreateNullValue()); + + web_contents_->CallClientFunction("DevToolsAPI.fileSystemAdded", + file_system_value.get(), + nullptr, nullptr); } void CommonWebContentsDelegate::DevToolsRemoveFileSystem( @@ -334,21 +374,18 @@ void CommonWebContentsDelegate::DevToolsRemoveFileSystem( if (!web_contents_) return; + std::string path = file_system_path.AsUTF8Unsafe(); storage::IsolatedContext::GetInstance()-> RevokeFileSystemByPath(file_system_path); - for (auto it = saved_paths_.begin(); it != saved_paths_.end(); ++it) - if (it->second == file_system_path) { - saved_paths_.erase(it); - break; - } + auto pref_service = GetPrefService(GetDevToolsWebContents()); + DictionaryPrefUpdate update(pref_service, prefs::kDevToolsFileSystemPaths); + update.Get()->RemoveWithoutPathExpansion(path, nullptr); - base::StringValue file_system_path_value(file_system_path.AsUTF8Unsafe()); - web_contents_->CallClientFunction( - "DevToolsAPI.fileSystemRemoved", - &file_system_path_value, - nullptr, - nullptr); + base::StringValue file_system_path_value(path); + web_contents_->CallClientFunction("DevToolsAPI.fileSystemRemoved", + &file_system_path_value, + nullptr, nullptr); } void CommonWebContentsDelegate::OnDevToolsSaveToFile( diff --git a/atom/browser/common_web_contents_delegate.h b/atom/browser/common_web_contents_delegate.h index ee18f36660e..61ff63793df 100644 --- a/atom/browser/common_web_contents_delegate.h +++ b/atom/browser/common_web_contents_delegate.h @@ -9,10 +9,10 @@ #include #include -#include "brightray/browser/default_web_contents_delegate.h" #include "brightray/browser/inspectable_web_contents_impl.h" #include "brightray/browser/inspectable_web_contents_delegate.h" #include "brightray/browser/inspectable_web_contents_view_delegate.h" +#include "content/public/browser/web_contents_delegate.h" namespace atom { @@ -21,7 +21,7 @@ class NativeWindow; class WebDialogHelper; class CommonWebContentsDelegate - : public brightray::DefaultWebContentsDelegate, + : public content::WebContentsDelegate, public brightray::InspectableWebContentsDelegate, public brightray::InspectableWebContentsViewDelegate { public: @@ -59,9 +59,6 @@ class CommonWebContentsDelegate content::WebContents* OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) override; - void RequestToLockMouse(content::WebContents* web_contents, - bool user_gesture, - bool last_unlocked_by_target) override; bool CanOverscrollContent() const override; content::JavaScriptDialogManager* GetJavaScriptDialogManager( content::WebContents* source) override; @@ -86,6 +83,7 @@ class CommonWebContentsDelegate bool save_as) override; void DevToolsAppendToFile(const std::string& url, const std::string& content) override; + void DevToolsRequestFileSystems() override; void DevToolsAddFileSystem(const base::FilePath& path) override; void DevToolsRemoveFileSystem( const base::FilePath& file_system_path) override; @@ -131,11 +129,6 @@ class CommonWebContentsDelegate typedef std::map PathsMap; PathsMap saved_files_; - // Maps file system id to file path, used by the file system requests - // sent from devtools. - typedef std::map WorkspaceMap; - WorkspaceMap saved_paths_; - DISALLOW_COPY_AND_ASSIGN(CommonWebContentsDelegate); }; diff --git a/atom/browser/default_app/default_app.js b/atom/browser/default_app/default_app.js deleted file mode 100644 index 2ec765d0d69..00000000000 --- a/atom/browser/default_app/default_app.js +++ /dev/null @@ -1,21 +0,0 @@ -const electron = require('electron'); -const app = electron.app; -const BrowserWindow = electron.BrowserWindow; - -var mainWindow = null; - -// Quit when all windows are closed. -app.on('window-all-closed', function() { - app.quit(); -}); - -app.on('ready', function() { - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - autoHideMenuBar: true, - useContentSize: true, - }); - mainWindow.loadURL('file://' + __dirname + '/index.html'); - mainWindow.focus(); -}); diff --git a/atom/browser/default_app/main.js b/atom/browser/default_app/main.js deleted file mode 100644 index 2c4fb1d2eea..00000000000 --- a/atom/browser/default_app/main.js +++ /dev/null @@ -1,289 +0,0 @@ -const electron = require('electron'); -const app = electron.app; -const dialog = electron.dialog; -const shell = electron.shell; -const Menu = electron.Menu; - -var fs = require('fs'); -var path = require('path'); - -// Quit when all windows are closed and no other one is listening to this. -app.on('window-all-closed', function() { - if (app.listeners('window-all-closed').length == 1) - app.quit(); -}); - -// Parse command line options. -var argv = process.argv.slice(1); -var option = { file: null, help: null, version: null, webdriver: null, modules: [] }; -for (var i = 0; i < argv.length; i++) { - if (argv[i] == '--version' || argv[i] == '-v') { - option.version = true; - break; - } else if (argv[i].match(/^--app=/)) { - option.file = argv[i].split('=')[1]; - break; - } else if (argv[i] == '--help' || argv[i] == '-h') { - option.help = true; - break; - } else if (argv[i] == '--test-type=webdriver') { - option.webdriver = true; - } else if (argv[i] == '--require' || argv[i] == '-r') { - option.modules.push(argv[++i]); - continue; - } else if (argv[i][0] == '-') { - continue; - } else { - option.file = argv[i]; - break; - } -} - -// Create default menu. -app.once('ready', function() { - if (Menu.getApplicationMenu()) - return; - - var 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' - }, - ] - }, - { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'CmdOrCtrl+R', - click: function(item, focusedWindow) { - if (focusedWindow) - focusedWindow.reload(); - } - }, - { - label: 'Toggle Full Screen', - accelerator: (function() { - if (process.platform == 'darwin') - return 'Ctrl+Command+F'; - else - return '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(); - } - }, - ] - }, - { - label: 'Window', - role: 'window', - submenu: [ - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - }, - ] - }, - { - label: 'Help', - role: 'help', - submenu: [ - { - label: 'Learn More', - click: function() { - shell.openExternal('http://electron.atom.io'); - } - }, - { - label: 'Documentation', - click: function() { - shell.openExternal( - `https://github.com/atom/electron/tree/v${process.versions.electron}/docs#readme` - ); - } - }, - { - label: 'Community Discussions', - click: function() { - shell.openExternal('https://discuss.atom.io/c/electron'); - } - }, - { - label: 'Search Issues', - click: function() { - shell.openExternal('https://github.com/atom/electron/issues'); - } - } - ] - }, - ]; - - if (process.platform == 'darwin') { - template.unshift({ - label: 'Electron', - submenu: [ - { - label: 'About Electron', - role: 'about' - }, - { - type: 'separator' - }, - { - label: 'Services', - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - label: 'Hide Electron', - accelerator: 'Command+H', - role: 'hide' - }, - { - label: 'Hide Others', - accelerator: 'Command+Shift+H', - role: 'hideothers' - }, - { - label: 'Show All', - role: 'unhide' - }, - { - type: 'separator' - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: function() { app.quit(); } - }, - ] - }); - template[3].submenu.push( - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' - } - ); - } - - var menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); -}); - -if (option.modules.length > 0) { - require('module')._preloadModules(option.modules); -} - -// Start the specified app if there is one specified in command line, otherwise -// start the default app. -if (option.file && !option.webdriver) { - try { - // Override app name and version. - var packagePath = path.resolve(option.file); - var packageJsonPath = path.join(packagePath, 'package.json'); - if (fs.existsSync(packageJsonPath)) { - var packageJson = JSON.parse(fs.readFileSync(packageJsonPath)); - if (packageJson.version) - app.setVersion(packageJson.version); - if (packageJson.productName) - app.setName(packageJson.productName); - else if (packageJson.name) - app.setName(packageJson.name); - app.setPath('userData', path.join(app.getPath('appData'), app.getName())); - app.setPath('userCache', path.join(app.getPath('cache'), app.getName())); - app.setAppPath(packagePath); - } - - // Run the app. - require('module')._load(packagePath, module, true); - } catch(e) { - if (e.code == 'MODULE_NOT_FOUND') { - app.focus(); - dialog.showErrorBox( - 'Error opening app', - 'The app provided is not a valid Electron app, please read the docs on how to write one:\n' + - `https://github.com/atom/electron/tree/v${process.versions.electron}/docs\n\n${e.toString()}` - ); - process.exit(1); - } else { - console.error('App threw an error when running', e); - throw e; - } - } -} else if (option.version) { - console.log('v' + process.versions.electron); - process.exit(0); -} else if (option.help) { - var helpMessage = "Electron v" + process.versions.electron + " - Cross Platform Desktop Application Shell\n\n"; - helpMessage += "Usage: electron [options] [path]\n\n"; - helpMessage += "A path to an Electron application may be specified. The path must be to \n"; - helpMessage += "an index.js file or to a folder containing a package.json or index.js file.\n\n"; - helpMessage += "Options:\n"; - helpMessage += " -r, --require Module to preload (option can be repeated)\n"; - helpMessage += " -h, --help Print this usage message.\n"; - helpMessage += " -v, --version Print the version."; - console.log(helpMessage); - process.exit(0); -} else { - require('./default_app'); -} diff --git a/atom/browser/javascript_environment.cc b/atom/browser/javascript_environment.cc index dc27cedee56..970132b47c1 100644 --- a/atom/browser/javascript_environment.cc +++ b/atom/browser/javascript_environment.cc @@ -37,6 +37,7 @@ bool JavascriptEnvironment::Initialize() { v8::V8::SetFlagsFromString(js_flags.c_str(), js_flags.size()); gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode, + gin::IsolateHolder::kStableV8Extras, gin::ArrayBufferAllocator::SharedInstance()); return true; } diff --git a/atom/browser/javascript_environment.h b/atom/browser/javascript_environment.h index 20f1667c3b8..07cd602cf00 100644 --- a/atom/browser/javascript_environment.h +++ b/atom/browser/javascript_environment.h @@ -5,7 +5,7 @@ #ifndef ATOM_BROWSER_JAVASCRIPT_ENVIRONMENT_H_ #define ATOM_BROWSER_JAVASCRIPT_ENVIRONMENT_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "gin/public/isolate_holder.h" namespace atom { diff --git a/atom/browser/lib/chrome-extension.js b/atom/browser/lib/chrome-extension.js deleted file mode 100644 index fbb537d052c..00000000000 --- a/atom/browser/lib/chrome-extension.js +++ /dev/null @@ -1,141 +0,0 @@ -const electron = require('electron'); -const app = electron.app; -const fs = require('fs'); -const path = require('path'); -const url = require('url'); - -// Mapping between hostname and file path. -var hostPathMap = {}; -var hostPathMapNextKey = 0; - -var getHostForPath = function(path) { - var key; - key = "extension-" + (++hostPathMapNextKey); - hostPathMap[key] = path; - return key; -}; - -var getPathForHost = function(host) { - return hostPathMap[host]; -}; - -// Cache extensionInfo. -var extensionInfoMap = {}; - -var getExtensionInfoFromPath = function(srcDirectory) { - var manifest, page; - manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json'))); - if (extensionInfoMap[manifest.name] == null) { - - // We can not use 'file://' directly because all resources in the extension - // will be treated as relative to the root in Chrome. - page = url.format({ - protocol: 'chrome-extension', - slashes: true, - hostname: getHostForPath(srcDirectory), - pathname: manifest.devtools_page - }); - extensionInfoMap[manifest.name] = { - startPage: page, - name: manifest.name, - srcDirectory: srcDirectory, - exposeExperimentalAPIs: true - }; - return extensionInfoMap[manifest.name]; - } -}; - -// The loaded extensions cache and its persistent path. -var loadedExtensions = null; -var loadedExtensionsPath = null; - -app.on('will-quit', function() { - try { - loadedExtensions = Object.keys(extensionInfoMap).map(function(key) { - return extensionInfoMap[key].srcDirectory; - }); - try { - fs.mkdirSync(path.dirname(loadedExtensionsPath)); - } catch (error) { - // Ignore error - } - return fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions)); - } catch (error) { - // Ignore error - } -}); - -// We can not use protocol or BrowserWindow until app is ready. -app.once('ready', function() { - var BrowserWindow, chromeExtensionHandler, i, init, len, protocol, srcDirectory; - protocol = electron.protocol, BrowserWindow = electron.BrowserWindow; - - // Load persistented extensions. - loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions'); - try { - loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath)); - if (!Array.isArray(loadedExtensions)) { - loadedExtensions = []; - } - - // Preheat the extensionInfo cache. - for (i = 0, len = loadedExtensions.length; i < len; i++) { - srcDirectory = loadedExtensions[i]; - getExtensionInfoFromPath(srcDirectory); - } - } catch (error) { - // Ignore error - } - - // The chrome-extension: can map a extension URL request to real file path. - chromeExtensionHandler = function(request, callback) { - var directory, parsed; - parsed = url.parse(request.url); - if (!(parsed.hostname && (parsed.path != null))) { - return callback(); - } - if (!/extension-\d+/.test(parsed.hostname)) { - return callback(); - } - directory = getPathForHost(parsed.hostname); - if (directory == null) { - return callback(); - } - return callback(path.join(directory, parsed.path)); - }; - protocol.registerFileProtocol('chrome-extension', chromeExtensionHandler, function(error) { - if (error) { - return console.error('Unable to register chrome-extension protocol'); - } - }); - BrowserWindow.prototype._loadDevToolsExtensions = function(extensionInfoArray) { - var ref; - return (ref = this.devToolsWebContents) != null ? ref.executeJavaScript("DevToolsAPI.addExtensions(" + (JSON.stringify(extensionInfoArray)) + ");") : void 0; - }; - BrowserWindow.addDevToolsExtension = function(srcDirectory) { - var extensionInfo, j, len1, ref, window; - extensionInfo = getExtensionInfoFromPath(srcDirectory); - if (extensionInfo) { - ref = BrowserWindow.getAllWindows(); - for (j = 0, len1 = ref.length; j < len1; j++) { - window = ref[j]; - window._loadDevToolsExtensions([extensionInfo]); - } - return extensionInfo.name; - } - }; - BrowserWindow.removeDevToolsExtension = function(name) { - return delete extensionInfoMap[name]; - }; - - // Load persistented extensions when devtools is opened. - init = BrowserWindow.prototype._init; - return BrowserWindow.prototype._init = function() { - init.call(this); - return this.on('devtools-opened', function() { - return this._loadDevToolsExtensions(Object.keys(extensionInfoMap).map(function(key) { - return extensionInfoMap[key]; - })); - }); - }; -}); diff --git a/atom/browser/lib/guest-view-manager.js b/atom/browser/lib/guest-view-manager.js deleted file mode 100644 index 40f59691e7a..00000000000 --- a/atom/browser/lib/guest-view-manager.js +++ /dev/null @@ -1,216 +0,0 @@ -const ipcMain = require('electron').ipcMain; -const webContents = require('electron').webContents; - -var slice = [].slice; - -// Doesn't exist in early initialization. -var webViewManager = null; - -var supportedWebViewEvents = ['load-commit', 'did-finish-load', 'did-fail-load', 'did-frame-finish-load', 'did-start-loading', 'did-stop-loading', 'did-get-response-details', 'did-get-redirect-request', 'dom-ready', 'console-message', 'devtools-opened', 'devtools-closed', 'devtools-focused', 'new-window', 'will-navigate', 'did-navigate', 'did-navigate-in-page', 'close', 'crashed', 'gpu-crashed', 'plugin-crashed', 'destroyed', 'page-title-updated', 'page-favicon-updated', 'enter-html-full-screen', 'leave-html-full-screen', 'media-started-playing', 'media-paused', 'found-in-page', 'did-change-theme-color']; - -var nextInstanceId = 0; -var guestInstances = {}; -var embedderElementsMap = {}; -var reverseEmbedderElementsMap = {}; - -// Moves the last element of array to the first one. -var moveLastToFirst = function(list) { - return list.unshift(list.pop()); -}; - -// Generate guestInstanceId. -var getNextInstanceId = function() { - return ++nextInstanceId; -}; - -// Create a new guest instance. -var createGuest = function(embedder, params) { - var destroy, destroyEvents, event, fn, guest, i, id, j, len, len1, listeners; - if (webViewManager == null) { - webViewManager = process.atomBinding('web_view_manager'); - } - id = getNextInstanceId(embedder); - guest = webContents.create({ - isGuest: true, - partition: params.partition, - embedder: embedder - }); - guestInstances[id] = { - guest: guest, - embedder: embedder - }; - - // Destroy guest when the embedder is gone or navigated. - destroyEvents = ['will-destroy', 'crashed', 'did-navigate']; - destroy = function() { - if (guestInstances[id] != null) { - return destroyGuest(embedder, id); - } - }; - for (i = 0, len = destroyEvents.length; i < len; i++) { - event = destroyEvents[i]; - embedder.once(event, destroy); - - // Users might also listen to the crashed event, so We must ensure the guest - // is destroyed before users' listener gets called. It is done by moving our - // listener to the first one in queue. - listeners = embedder._events[event]; - if (Array.isArray(listeners)) { - moveLastToFirst(listeners); - } - } - guest.once('destroyed', function() { - var j, len1, results; - results = []; - for (j = 0, len1 = destroyEvents.length; j < len1; j++) { - event = destroyEvents[j]; - results.push(embedder.removeListener(event, destroy)); - } - return results; - }); - - // Init guest web view after attached. - guest.once('did-attach', function() { - var opts; - params = this.attachParams; - delete this.attachParams; - this.viewInstanceId = params.instanceId; - this.setSize({ - normal: { - width: params.elementWidth, - height: params.elementHeight - }, - enableAutoSize: params.autosize, - min: { - width: params.minwidth, - height: params.minheight - }, - max: { - width: params.maxwidth, - height: params.maxheight - } - }); - if (params.src) { - opts = {}; - if (params.httpreferrer) { - opts.httpReferrer = params.httpreferrer; - } - if (params.useragent) { - opts.userAgent = params.useragent; - } - this.loadURL(params.src, opts); - } - if (params.allowtransparency != null) { - this.setAllowTransparency(params.allowtransparency); - } - return guest.allowPopups = params.allowpopups; - }); - - // Dispatch events to embedder. - fn = function(event) { - return guest.on(event, function() { - var args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + guest.viewInstanceId, event].concat(slice.call(args))); - }); - }; - for (j = 0, len1 = supportedWebViewEvents.length; j < len1; j++) { - event = supportedWebViewEvents[j]; - fn(event); - } - - // Dispatch guest's IPC messages to embedder. - guest.on('ipc-message-host', function(_, packed) { - var args, channel; - channel = packed[0], args = 2 <= packed.length ? slice.call(packed, 1) : []; - return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + guest.viewInstanceId, channel].concat(slice.call(args))); - }); - - // Autosize. - guest.on('size-changed', function() { - var args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - return embedder.send.apply(embedder, ["ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + guest.viewInstanceId].concat(slice.call(args))); - }); - return id; -}; - -// Attach the guest to an element of embedder. -var attachGuest = function(embedder, elementInstanceId, guestInstanceId, params) { - var guest, key, oldGuestInstanceId, ref1, webPreferences; - guest = guestInstances[guestInstanceId].guest; - - // Destroy the old guest when attaching. - key = (embedder.getId()) + "-" + elementInstanceId; - oldGuestInstanceId = embedderElementsMap[key]; - if (oldGuestInstanceId != null) { - - // Reattachment to the same guest is not currently supported. - if (oldGuestInstanceId === guestInstanceId) { - return; - } - if (guestInstances[oldGuestInstanceId] == null) { - return; - } - destroyGuest(embedder, oldGuestInstanceId); - } - webPreferences = { - guestInstanceId: guestInstanceId, - nodeIntegration: (ref1 = params.nodeintegration) != null ? ref1 : false, - plugins: params.plugins, - webSecurity: !params.disablewebsecurity, - blinkFeatures: params.blinkfeatures - }; - if (params.preload) { - webPreferences.preloadURL = params.preload; - } - webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences); - guest.attachParams = params; - embedderElementsMap[key] = guestInstanceId; - return reverseEmbedderElementsMap[guestInstanceId] = key; -}; - -// Destroy an existing guest instance. -var destroyGuest = function(embedder, id) { - var key; - webViewManager.removeGuest(embedder, id); - guestInstances[id].guest.destroy(); - delete guestInstances[id]; - key = reverseEmbedderElementsMap[id]; - if (key != null) { - delete reverseEmbedderElementsMap[id]; - return delete embedderElementsMap[key]; - } -}; - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', function(event, params, requestId) { - return event.sender.send("ATOM_SHELL_RESPONSE_" + requestId, createGuest(event.sender, params)); -}); - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', function(event, elementInstanceId, guestInstanceId, params) { - return attachGuest(event.sender, elementInstanceId, guestInstanceId, params); -}); - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', function(event, id) { - return destroyGuest(event.sender, id); -}); - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', function(event, id, params) { - var ref1; - return (ref1 = guestInstances[id]) != null ? ref1.guest.setSize(params) : void 0; -}); - -ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', function(event, id, allowtransparency) { - var ref1; - return (ref1 = guestInstances[id]) != null ? ref1.guest.setAllowTransparency(allowtransparency) : void 0; -}); - -// Returns WebContents from its guest id. -exports.getGuest = function(id) { - var ref1; - return (ref1 = guestInstances[id]) != null ? ref1.guest : void 0; -}; - -// Returns the embedder of the guest. -exports.getEmbedder = function(id) { - var ref1; - return (ref1 = guestInstances[id]) != null ? ref1.embedder : void 0; -}; diff --git a/atom/browser/lib/guest-window-manager.js b/atom/browser/lib/guest-window-manager.js deleted file mode 100644 index 50c6a62eb0c..00000000000 --- a/atom/browser/lib/guest-window-manager.js +++ /dev/null @@ -1,124 +0,0 @@ -const ipcMain = require('electron').ipcMain; -const BrowserWindow = require('electron').BrowserWindow; - -var hasProp = {}.hasOwnProperty; -var slice = [].slice; -var frameToGuest = {}; - -// Copy attribute of |parent| to |child| if it is not defined in |child|. -var mergeOptions = function(child, parent) { - var key, value; - for (key in parent) { - if (!hasProp.call(parent, key)) continue; - value = parent[key]; - if (!(key in child)) { - if (typeof value === 'object') { - child[key] = mergeOptions({}, value); - } else { - child[key] = value; - } - } - } - return child; -}; - -// Merge |options| with the |embedder|'s window's options. -var mergeBrowserWindowOptions = function(embedder, options) { - if (embedder.browserWindowOptions != null) { - - // Inherit the original options if it is a BrowserWindow. - mergeOptions(options, embedder.browserWindowOptions); - } else { - - // Or only inherit web-preferences if it is a webview. - if (options.webPreferences == null) { - options.webPreferences = {}; - } - mergeOptions(options.webPreferences, embedder.getWebPreferences()); - } - return options; -}; - -// Create a new guest created by |embedder| with |options|. -var createGuest = function(embedder, url, frameName, options) { - var closedByEmbedder, closedByUser, guest, guestId, ref1; - guest = frameToGuest[frameName]; - if (frameName && (guest != null)) { - guest.loadURL(url); - return guest.id; - } - - // Remember the embedder window's id. - if (options.webPreferences == null) { - options.webPreferences = {}; - } - options.webPreferences.openerId = (ref1 = BrowserWindow.fromWebContents(embedder)) != null ? ref1.id : void 0; - guest = new BrowserWindow(options); - guest.loadURL(url); - - // When |embedder| is destroyed we should also destroy attached guest, and if - // guest is closed by user then we should prevent |embedder| from double - // closing guest. - guestId = guest.id; - closedByEmbedder = function() { - guest.removeListener('closed', closedByUser); - return guest.destroy(); - }; - closedByUser = function() { - embedder.send("ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_" + guestId); - return embedder.removeListener('render-view-deleted', closedByEmbedder); - }; - embedder.once('render-view-deleted', closedByEmbedder); - guest.once('closed', closedByUser); - if (frameName) { - frameToGuest[frameName] = guest; - guest.frameName = frameName; - guest.once('closed', function() { - return delete frameToGuest[frameName]; - }); - } - return guest.id; -}; - -// Routed window.open messages. -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function() { - var args, event, frameName, options, url; - event = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - url = args[0], frameName = args[1], options = args[2]; - options = mergeBrowserWindowOptions(event.sender, options); - event.sender.emit('new-window', event, url, frameName, 'new-window', options); - if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) { - return event.returnValue = null; - } else { - return event.returnValue = createGuest(event.sender, url, frameName, options); - } -}); - -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function(event, guestId) { - var ref1; - return (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1.destroy() : void 0; -}); - -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function() { - var args, guestId, method, ref1; - guestId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; - return (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1[method].apply(ref1, args) : void 0; -}); - -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function(event, guestId, message, targetOrigin, sourceOrigin) { - var guestContents, ref1, ref2, sourceId; - sourceId = (ref1 = BrowserWindow.fromWebContents(event.sender)) != null ? ref1.id : void 0; - if (sourceId == null) { - return; - } - guestContents = (ref2 = BrowserWindow.fromId(guestId)) != null ? ref2.webContents : void 0; - if ((guestContents != null ? guestContents.getURL().indexOf(targetOrigin) : void 0) === 0 || targetOrigin === '*') { - return guestContents != null ? guestContents.send('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) : void 0; - } -}); - -ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function() { - var args, guestId, method, ref1, ref2; - guestId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; - return (ref1 = BrowserWindow.fromId(guestId)) != null ? (ref2 = ref1.webContents) != null ? ref2[method].apply(ref2, args) : void 0 : void 0; -}); diff --git a/atom/browser/lib/init.js b/atom/browser/lib/init.js deleted file mode 100644 index 272cebbf875..00000000000 --- a/atom/browser/lib/init.js +++ /dev/null @@ -1,152 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const Module = require('module'); - -var slice = [].slice; - -// We modified the original process.argv to let node.js load the atom.js, -// we need to restore it here. -process.argv.splice(1, 1); - -// Clear search paths. -require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths')); - -// Import common settings. -require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')); - -var globalPaths = Module.globalPaths; - -if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { - globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib')); -} - -// Expose public APIs. -globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib', 'exports')); - -if (process.platform === 'win32') { - // Redirect node's console to use our own implementations, since node can not - // handle console output when running as GUI program. - var consoleLog = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return process.log(util.format.apply(util, args) + "\n"); - }; - var streamWrite = function(chunk, encoding, callback) { - if (Buffer.isBuffer(chunk)) { - chunk = chunk.toString(encoding); - } - process.log(chunk); - if (callback) { - callback(); - } - return true; - }; - console.log = console.error = console.warn = consoleLog; - process.stdout.write = process.stderr.write = streamWrite; - - // Always returns EOF for stdin stream. - var Readable = require('stream').Readable; - var stdin = new Readable; - stdin.push(null); - process.__defineGetter__('stdin', function() { - return stdin; - }); -} - -// Don't quit on fatal error. -process.on('uncaughtException', function(error) { - - // Do nothing if the user has a custom uncaught exception handler. - var dialog, message, ref, stack; - if (process.listeners('uncaughtException').length > 1) { - return; - } - - // Show error in GUI. - dialog = require('electron').dialog; - stack = (ref = error.stack) != null ? ref : error.name + ": " + error.message; - message = "Uncaught Exception:\n" + stack; - return dialog.showErrorBox('A JavaScript error occurred in the main process', message); -}); - -// Emit 'exit' event on quit. -var app = require('electron').app; - -app.on('quit', function(event, exitCode) { - return process.emit('exit', exitCode); -}); - -// Map process.exit to app.exit, which quits gracefully. -process.exit = app.exit; - -// Load the RPC server. -require('./rpc-server'); - -// Load the guest view manager. -require('./guest-view-manager'); - -require('./guest-window-manager'); - -// Now we try to load app's package.json. -var packageJson = null; -var searchPaths = ['app', 'app.asar', 'default_app']; -var i, len, packagePath; -for (i = 0, len = searchPaths.length; i < len; i++) { - packagePath = searchPaths[i]; - try { - packagePath = path.join(process.resourcesPath, packagePath); - packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'))); - break; - } catch (error) { - continue; - } -} - -if (packageJson == null) { - process.nextTick(function() { - return process.exit(1); - }); - throw new Error("Unable to find a valid app"); -} - -// Set application's version. -if (packageJson.version != null) { - app.setVersion(packageJson.version); -} - -// Set application's name. -if (packageJson.productName != null) { - app.setName(packageJson.productName); -} else if (packageJson.name != null) { - app.setName(packageJson.name); -} - -// Set application's desktop name. -if (packageJson.desktopName != null) { - app.setDesktopName(packageJson.desktopName); -} else { - app.setDesktopName((app.getName()) + ".desktop"); -} - -// Chrome 42 disables NPAPI plugins by default, reenable them here -app.commandLine.appendSwitch('enable-npapi'); - -// Set the user path according to application's name. -app.setPath('userData', path.join(app.getPath('appData'), app.getName())); - -app.setPath('userCache', path.join(app.getPath('cache'), app.getName())); - -app.setAppPath(packagePath); - -// Load the chrome extension support. -require('./chrome-extension'); - -// Load internal desktop-capturer module. -require('./desktop-capturer'); - -// Set main startup script of the app. -var mainStartupScript = packageJson.main || 'index.js'; - -// Finally load app's main.js and transfer control to C++. -Module._load(path.join(packagePath, mainStartupScript), Module, true); diff --git a/atom/browser/lib/objects-registry.js b/atom/browser/lib/objects-registry.js deleted file mode 100644 index e9d6d365fa5..00000000000 --- a/atom/browser/lib/objects-registry.js +++ /dev/null @@ -1,109 +0,0 @@ -'use strict'; - -const EventEmitter = require('events').EventEmitter; -const v8Util = process.atomBinding('v8_util'); - -class ObjectsRegistry extends EventEmitter { - constructor() { - super(); - - this.setMaxListeners(Number.MAX_VALUE); - this.nextId = 0; - - // Stores all objects by ref-counting. - // (id) => {object, count} - this.storage = {}; - - // Stores the IDs of objects referenced by WebContents. - // (webContentsId) => {(id) => (count)} - this.owners = {}; - } - - // Register a new object, the object would be kept referenced until you release - // it explicitly. - add(webContentsId, obj) { - var base, base1, id; - id = this.saveToStorage(obj); - - // Remember the owner. - if ((base = this.owners)[webContentsId] == null) { - base[webContentsId] = {}; - } - if ((base1 = this.owners[webContentsId])[id] == null) { - base1[id] = 0; - } - this.owners[webContentsId][id]++; - - // Returns object's id - return id; - } - - // Get an object according to its ID. - get(id) { - var ref; - return (ref = this.storage[id]) != null ? ref.object : void 0; - } - - // Dereference an object according to its ID. - remove(webContentsId, id) { - var pointer; - this.dereference(id, 1); - - // Also reduce the count in owner. - pointer = this.owners[webContentsId]; - if (pointer == null) { - return; - } - --pointer[id]; - if (pointer[id] === 0) { - return delete pointer[id]; - } - } - - // Clear all references to objects refrenced by the WebContents. - clear(webContentsId) { - var count, id, ref; - this.emit("clear-" + webContentsId); - if (this.owners[webContentsId] == null) { - return; - } - ref = this.owners[webContentsId]; - for (id in ref) { - count = ref[id]; - this.dereference(id, count); - } - return delete this.owners[webContentsId]; - } - - // Private: Saves the object into storage and assigns an ID for it. - saveToStorage(object) { - var id; - id = v8Util.getHiddenValue(object, 'atomId'); - if (!id) { - id = ++this.nextId; - this.storage[id] = { - count: 0, - object: object - }; - v8Util.setHiddenValue(object, 'atomId', id); - } - ++this.storage[id].count; - return id; - } - - // Private: Dereference the object from store. - dereference(id, count) { - var pointer; - pointer = this.storage[id]; - if (pointer == null) { - return; - } - pointer.count -= count; - if (pointer.count === 0) { - v8Util.deleteHiddenValue(pointer.object, 'atomId'); - return delete this.storage[id]; - } - } -} - -module.exports = new ObjectsRegistry; diff --git a/atom/browser/lib/rpc-server.js b/atom/browser/lib/rpc-server.js deleted file mode 100644 index b3e6ddc7832..00000000000 --- a/atom/browser/lib/rpc-server.js +++ /dev/null @@ -1,344 +0,0 @@ -'use strict'; - -const electron = require('electron'); -const ipcMain = electron.ipcMain; -const objectsRegistry = require('./objects-registry'); -const v8Util = process.atomBinding('v8_util'); -const IDWeakMap = process.atomBinding('id_weak_map').IDWeakMap; - -var slice = [].slice; - -// Convert a real value into meta data. -var valueToMeta = function(sender, value, optimizeSimpleObject) { - var el, field, i, len, meta, name; - if (optimizeSimpleObject == null) { - optimizeSimpleObject = false; - } - meta = { - type: typeof value - }; - if (Buffer.isBuffer(value)) { - meta.type = 'buffer'; - } - if (value === null) { - meta.type = 'value'; - } - if (Array.isArray(value)) { - meta.type = 'array'; - } - if (value instanceof Error) { - meta.type = 'error'; - } - if (value instanceof Date) { - meta.type = 'date'; - } - if ((value != null ? value.constructor.name : void 0) === 'Promise') { - meta.type = 'promise'; - } - - // Treat simple objects as value. - if (optimizeSimpleObject && meta.type === 'object' && v8Util.getHiddenValue(value, 'simple')) { - meta.type = 'value'; - } - - // Treat the arguments object as array. - if (meta.type === 'object' && (value.hasOwnProperty('callee')) && (value.length != null)) { - meta.type = 'array'; - } - if (meta.type === 'array') { - meta.members = []; - for (i = 0, len = value.length; i < len; i++) { - el = value[i]; - meta.members.push(valueToMeta(sender, el)); - } - } else if (meta.type === 'object' || meta.type === 'function') { - meta.name = value.constructor.name; - - // Reference the original value if it's an object, because when it's - // passed to renderer we would assume the renderer keeps a reference of - // it. - meta.id = objectsRegistry.add(sender.getId(), value); - meta.members = (function() { - var results; - results = []; - for (name in value) { - field = value[name]; - results.push({ - name: name, - type: typeof field - }); - } - return results; - })(); - } else if (meta.type === 'buffer') { - meta.value = Array.prototype.slice.call(value, 0); - } else if (meta.type === 'promise') { - meta.then = valueToMeta(sender, value.then.bind(value)); - } else if (meta.type === 'error') { - meta.members = plainObjectToMeta(value); - - // Error.name is not part of own properties. - meta.members.push({ - name: 'name', - value: value.name - }); - } else if (meta.type === 'date') { - meta.value = value.getTime(); - } else { - meta.type = 'value'; - meta.value = value; - } - return meta; -}; - -// Convert object to meta by value. -var plainObjectToMeta = function(obj) { - return Object.getOwnPropertyNames(obj).map(function(name) { - return { - name: name, - value: obj[name] - }; - }); -}; - -// Convert Error into meta data. -var exceptionToMeta = function(error) { - return { - type: 'exception', - message: error.message, - stack: error.stack || error - }; -}; - -// Convert array of meta data from renderer into array of real values. -var unwrapArgs = function(sender, args) { - var metaToValue; - metaToValue = function(meta) { - var i, len, member, ref, rendererReleased, returnValue; - switch (meta.type) { - case 'value': - return meta.value; - case 'remote-object': - return objectsRegistry.get(meta.id); - case 'array': - return unwrapArgs(sender, meta.value); - case 'buffer': - return new Buffer(meta.value); - case 'date': - return new Date(meta.value); - case 'promise': - return Promise.resolve({ - then: metaToValue(meta.then) - }); - case 'object': - let ret = v8Util.createObjectWithName(meta.name); - ref = meta.members; - for (i = 0, len = ref.length; i < len; i++) { - member = ref[i]; - ret[member.name] = metaToValue(member.value); - } - return ret; - case 'function-with-return-value': - returnValue = metaToValue(meta.value); - return function() { - return returnValue; - }; - case 'function': - // Cache the callbacks in renderer. - if (!sender.callbacks) { - sender.callbacks = new IDWeakMap; - sender.on('render-view-deleted', function() { - return this.callbacks.clear(); - }); - } - - if (sender.callbacks.has(meta.id)) - return sender.callbacks.get(meta.id); - - // Prevent the callback from being called when its page is gone. - rendererReleased = false; - sender.once('render-view-deleted', function() { - rendererReleased = true; - }); - - let callIntoRenderer = function(...args) { - if (rendererReleased) - throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`); - sender.send('ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args)); - }; - v8Util.setDestructor(callIntoRenderer, function() { - if (!rendererReleased) - sender.send('ATOM_RENDERER_RELEASE_CALLBACK', meta.id); - }); - sender.callbacks.set(meta.id, callIntoRenderer); - return callIntoRenderer; - default: - throw new TypeError("Unknown type: " + meta.type); - } - }; - return args.map(metaToValue); -}; - -// Call a function and send reply asynchronously if it's a an asynchronous -// style function and the caller didn't pass a callback. -var callFunction = function(event, func, caller, args) { - var funcMarkedAsync, funcName, funcPassedCallback, ref, ret; - funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous'); - funcPassedCallback = typeof args[args.length - 1] === 'function'; - try { - if (funcMarkedAsync && !funcPassedCallback) { - args.push(function(ret) { - return event.returnValue = valueToMeta(event.sender, ret, true); - }); - return func.apply(caller, args); - } else { - ret = func.apply(caller, args); - return event.returnValue = valueToMeta(event.sender, ret, true); - } - } catch (error) { - // Catch functions thrown further down in function invocation and wrap - // them with the function name so it's easier to trace things like - // `Error processing argument -1.` - funcName = (ref = func.name) != null ? ref : "anonymous"; - throw new Error("Could not call remote function `" + funcName + "`. Check that the function signature is correct. Underlying error: " + error.message); - } -}; - -// Send by BrowserWindow when its render view is deleted. -process.on('ATOM_BROWSER_RELEASE_RENDER_VIEW', function(id) { - return objectsRegistry.clear(id); -}); - -ipcMain.on('ATOM_BROWSER_REQUIRE', function(event, module) { - try { - return event.returnValue = valueToMeta(event.sender, process.mainModule.require(module)); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_GET_BUILTIN', function(event, module) { - try { - return event.returnValue = valueToMeta(event.sender, electron[module]); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_GLOBAL', function(event, name) { - try { - return event.returnValue = valueToMeta(event.sender, global[name]); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_CURRENT_WINDOW', function(event) { - try { - return event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow()); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_CURRENT_WEB_CONTENTS', function(event) { - return event.returnValue = valueToMeta(event.sender, event.sender); -}); - -ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function(event, id, args) { - var constructor, obj; - try { - args = unwrapArgs(event.sender, args); - constructor = objectsRegistry.get(id); - - // Call new with array of arguments. - // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible - obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))); - return event.returnValue = valueToMeta(event.sender, obj); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function(event, id, args) { - var func; - try { - args = unwrapArgs(event.sender, args); - func = objectsRegistry.get(id); - return callFunction(event, func, global, args); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function(event, id, method, args) { - var constructor, obj; - try { - args = unwrapArgs(event.sender, args); - constructor = objectsRegistry.get(id)[method]; - - // Call new with array of arguments. - obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))); - return event.returnValue = valueToMeta(event.sender, obj); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function(event, id, method, args) { - var obj; - try { - args = unwrapArgs(event.sender, args); - obj = objectsRegistry.get(id); - return callFunction(event, obj[method], obj, args); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_MEMBER_SET', function(event, id, name, value) { - var obj; - try { - obj = objectsRegistry.get(id); - obj[name] = value; - return event.returnValue = null; - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_MEMBER_GET', function(event, id, name) { - var obj; - try { - obj = objectsRegistry.get(id); - return event.returnValue = valueToMeta(event.sender, obj[name]); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_DEREFERENCE', function(event, id) { - return objectsRegistry.remove(event.sender.getId(), id); -}); - -ipcMain.on('ATOM_BROWSER_GUEST_WEB_CONTENTS', function(event, guestInstanceId) { - var guestViewManager; - try { - guestViewManager = require('./guest-view-manager'); - return event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId)); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); - -ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function() { - var args, event, guest, guestInstanceId, guestViewManager, method; - event = arguments[0], guestInstanceId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; - try { - guestViewManager = require('./guest-view-manager'); - guest = guestViewManager.getGuest(guestInstanceId); - return guest[method].apply(guest, args); - } catch (error) { - return event.returnValue = exceptionToMeta(error); - } -}); diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index 7662162ab61..f4db929bf57 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -24,6 +24,9 @@ // Don't add the "Enter Full Screen" menu item automatically. [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"]; + // Add observer to monitor the system's Dark Mode theme. + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(platformThemeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil]; + atom::Browser::Get()->WillFinishLaunching(); } @@ -59,4 +62,8 @@ return flag; } +- (void)platformThemeChanged:(NSNotification *)notify { + atom::Browser::Get()->PlatformThemeChanged(); +} + @end diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 1f1a98b0658..2627c704d21 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -27,6 +27,7 @@ #include "content/public/browser/plugin_service.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/common/content_switches.h" #include "ipc/ipc_message_macros.h" @@ -115,22 +116,41 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { } else { SetSizeConstraints(size_constraints); } -#if defined(OS_WIN) || defined(USE_X11) +#if defined(USE_X11) bool resizable; if (options.Get(options::kResizable, &resizable)) { SetResizable(resizable); } #endif +#if defined(OS_WIN) || defined(USE_X11) + bool closable; + if (options.Get(options::kClosable, &closable)) { + SetClosable(closable); + } +#endif + bool movable; + if (options.Get(options::kMovable, &movable)) { + SetMovable(movable); + } + bool has_shadow; + if (options.Get(options::kHasShadow, &has_shadow)) { + SetHasShadow(has_shadow); + } bool top; if (options.Get(options::kAlwaysOnTop, &top) && top) { SetAlwaysOnTop(true); } -#if defined(OS_MACOSX) || defined(OS_WIN) - bool fullscreen; - if (options.Get(options::kFullscreen, &fullscreen) && fullscreen) { + // Disable fullscreen button if 'fullscreen' is specified to false. + bool fullscreenable = true; + bool fullscreen = false; + if (options.Get(options::kFullscreen, &fullscreen) && !fullscreen) + fullscreenable = false; + // Overriden by 'fullscreenable'. + options.Get(options::kFullScreenable, &fullscreenable); + SetFullScreenable(fullscreenable); + if (fullscreen) { SetFullScreen(true); } -#endif bool skip; if (options.Get(options::kSkipTaskbar, &skip) && skip) { SetSkipTaskbar(skip); @@ -256,15 +276,15 @@ bool NativeWindow::HasModalDialog() { } void NativeWindow::FocusOnWebView() { - web_contents()->GetRenderViewHost()->Focus(); + web_contents()->GetRenderViewHost()->GetWidget()->Focus(); } void NativeWindow::BlurWebView() { - web_contents()->GetRenderViewHost()->Blur(); + web_contents()->GetRenderViewHost()->GetWidget()->Blur(); } bool NativeWindow::IsWebViewFocused() { - auto host_view = web_contents()->GetRenderViewHost()->GetView(); + auto host_view = web_contents()->GetRenderViewHost()->GetWidget()->GetView(); return host_view && host_view->HasFocus(); } @@ -409,6 +429,14 @@ void NativeWindow::NotifyWindowFocus() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowFocus()); } +void NativeWindow::NotifyWindowShow() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowShow()); +} + +void NativeWindow::NotifyWindowHide() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowHide()); +} + void NativeWindow::NotifyWindowMaximize() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowMaximize()); } @@ -442,6 +470,21 @@ void NativeWindow::NotifyWindowEnterFullScreen() { OnWindowEnterFullScreen()); } +void NativeWindow::NotifyWindowScrollTouchBegin() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowScrollTouchBegin()); +} + +void NativeWindow::NotifyWindowScrollTouchEnd() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowScrollTouchEnd()); +} + +void NativeWindow::NotifyWindowSwipe(const std::string& direction) { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowSwipe(direction)); +} + void NativeWindow::NotifyWindowLeaveFullScreen() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowLeaveFullScreen()); @@ -482,7 +525,7 @@ scoped_ptr NativeWindow::DraggableRegionsToSkRegion( region.bounds.bottom(), region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); } - return sk_region.Pass(); + return sk_region; } void NativeWindow::RenderViewCreated( @@ -552,11 +595,11 @@ void NativeWindow::OnCapturePageDone(const CapturePageCallback& callback, } SkColor NativeWindow::ParseHexColor(const std::string& name) { - SkColor result = 0xFF000000; - unsigned value = 0; auto color = name.substr(1); unsigned length = color.size(); - if (length != 3 && length != 6) + SkColor result = (length != 8 ? 0xFF000000 : 0x00000000); + unsigned value = 0; + if (length != 3 && length != 6 && length != 8) return result; for (unsigned i = 0; i < length; ++i) { if (!base::IsHexDigit(color[i])) @@ -564,7 +607,7 @@ SkColor NativeWindow::ParseHexColor(const std::string& name) { value <<= 4; value |= (color[i] < 'A' ? color[i] - '0' : (color[i] - 'A' + 10) & 0xF); } - if (length == 6) { + if (length == 6 || length == 8) { result |= value; return result; } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 0a3ce124bd1..49e1e71d5df 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -125,6 +125,16 @@ class NativeWindow : public base::SupportsUserData, virtual gfx::Size GetMaximumSize(); virtual void SetResizable(bool resizable) = 0; virtual bool IsResizable() = 0; + virtual void SetMovable(bool movable) = 0; + virtual bool IsMovable() = 0; + virtual void SetMinimizable(bool minimizable) = 0; + virtual bool IsMinimizable() = 0; + virtual void SetMaximizable(bool maximizable) = 0; + virtual bool IsMaximizable() = 0; + virtual void SetFullScreenable(bool fullscreenable) = 0; + virtual bool IsFullScreenable() = 0; + virtual void SetClosable(bool closable) = 0; + virtual bool IsClosable() = 0; virtual void SetAlwaysOnTop(bool top) = 0; virtual bool IsAlwaysOnTop() = 0; virtual void Center() = 0; @@ -135,6 +145,8 @@ class NativeWindow : public base::SupportsUserData, virtual void SetKiosk(bool kiosk) = 0; virtual bool IsKiosk() = 0; virtual void SetBackgroundColor(const std::string& color_name) = 0; + virtual void SetHasShadow(bool has_shadow) = 0; + virtual bool HasShadow() = 0; virtual void SetRepresentedFilename(const std::string& filename); virtual std::string GetRepresentedFilename(); virtual void SetDocumentEdited(bool edited); @@ -198,6 +210,8 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowClosed(); void NotifyWindowBlur(); void NotifyWindowFocus(); + void NotifyWindowShow(); + void NotifyWindowHide(); void NotifyWindowMaximize(); void NotifyWindowUnmaximize(); void NotifyWindowMinimize(); @@ -205,6 +219,9 @@ class NativeWindow : public base::SupportsUserData, void NotifyWindowMove(); void NotifyWindowResize(); void NotifyWindowMoved(); + void NotifyWindowScrollTouchBegin(); + void NotifyWindowScrollTouchEnd(); + void NotifyWindowSwipe(const std::string& direction); void NotifyWindowEnterFullScreen(); void NotifyWindowLeaveFullScreen(); void NotifyWindowEnterHtmlFullScreen(); diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index ef3ccb15021..93d03e4c157 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -48,6 +48,16 @@ class NativeWindowMac : public NativeWindow { const extensions::SizeConstraints& size_constraints) override; void SetResizable(bool resizable) override; bool IsResizable() override; + void SetMovable(bool movable) override; + bool IsMovable() override; + void SetMinimizable(bool minimizable) override; + bool IsMinimizable() override; + void SetMaximizable(bool maximizable) override; + bool IsMaximizable() override; + void SetFullScreenable(bool fullscreenable) override; + bool IsFullScreenable() override; + void SetClosable(bool closable) override; + bool IsClosable() override; void SetAlwaysOnTop(bool top) override; bool IsAlwaysOnTop() override; void Center() override; @@ -58,6 +68,8 @@ class NativeWindowMac : public NativeWindow { void SetKiosk(bool kiosk) override; bool IsKiosk() override; void SetBackgroundColor(const std::string& color_name) override; + void SetHasShadow(bool has_shadow) override; + bool HasShadow() override; void SetRepresentedFilename(const std::string& filename) override; std::string GetRepresentedFilename() override; void SetDocumentEdited(bool edited) override; @@ -108,9 +120,16 @@ class NativeWindowMac : public NativeWindow { // whehter we can drag. void UpdateDraggableRegionViews(const std::vector& regions); + // Set the attribute of NSWindow while work around a bug of zo0m button. + void SetStyleMask(bool on, NSUInteger flag); + void SetCollectionBehavior(bool on, NSUInteger flag); + base::scoped_nsobject window_; base::scoped_nsobject window_delegate_; + // Event monitor for scroll wheel event. + id wheel_event_monitor_; + // The view that will fill the whole frameless window. base::scoped_nsobject content_view_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 156017194ec..2044ee7d718 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -79,6 +79,21 @@ bool ScopedDisableResize::disable_resize_ = false; return self; } +- (void)windowDidChangeOcclusionState:(NSNotification *)notification { + // notification.object is the window that changed its state. + // It's safe to use self.window instead if you don't assign one delegate to many windows + NSWindow *window = notification.object; + + // check occlusion binary flag + if (window.occlusionState & NSWindowOcclusionStateVisible) { + // The app is visible + shell_->NotifyWindowShow(); + } else { + // The app is not visible + shell_->NotifyWindowHide(); + } +} + - (void)windowDidBecomeMain:(NSNotification*)notification { content::WebContents* web_contents = shell_->web_contents(); if (!web_contents) @@ -252,6 +267,18 @@ bool ScopedDisableResize::disable_resize_ = false; // NSWindow overrides. +- (void)swipeWithEvent:(NSEvent *)event { + if (event.deltaY == 1.0) { + shell_->NotifyWindowSwipe("up"); + } else if (event.deltaX == -1.0) { + shell_->NotifyWindowSwipe("right"); + } else if (event.deltaY == -1.0) { + shell_->NotifyWindowSwipe("down"); + } else if (event.deltaX == 1.0) { + shell_->NotifyWindowSwipe("left"); + } +} + - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen { // Resizing is disabled. if (ScopedDisableResize::IsResizeDisabled()) @@ -369,6 +396,15 @@ NativeWindowMac::NativeWindowMac( bool resizable = true; options.Get(options::kResizable, &resizable); + bool minimizable = true; + options.Get(options::kMinimizable, &minimizable); + + bool maximizable = true; + options.Get(options::kMaximizable, &maximizable); + + bool closable = true; + options.Get(options::kClosable, &closable); + // New title bar styles are available in Yosemite or newer std::string titleBarStyle; if (base::mac::IsOSYosemiteOrLater()) @@ -385,8 +421,13 @@ NativeWindowMac::NativeWindowMac( useStandardWindow = false; } - NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask | - NSMiniaturizableWindowMask; + NSUInteger styleMask = NSTitledWindowMask; + if (minimizable) { + styleMask |= NSMiniaturizableWindowMask; + } + if (closable) { + styleMask |= NSClosableWindowMask; + } if (!useStandardWindow || transparent() || !has_frame()) { styleMask |= NSTexturedBackgroundWindowMask; } @@ -452,11 +493,6 @@ NativeWindowMac::NativeWindowMac( set_force_using_draggable_region(true); } - bool movable; - if (options.Get(options::kMovable, &movable)) { - [window_ setMovable:movable]; - } - // On OS X the initial window size doesn't include window frame. bool use_content_size = false; options.Get(options::kUseContentSize, &use_content_size); @@ -473,27 +509,41 @@ NativeWindowMac::NativeWindowMac( options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor); [window_ setDisableAutoHideCursor:disableAutoHideCursor]; - // Disable fullscreen button when 'fullscreen' is specified to false. - bool fullscreen = false; - if (!(options.Get(options::kFullscreen, &fullscreen) && - !fullscreen)) { - NSUInteger collectionBehavior = [window_ collectionBehavior]; - collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; - [window_ setCollectionBehavior:collectionBehavior]; - } else if (base::mac::IsOSElCapitanOrLater()) { - // On EL Capitan this flag is required to hide fullscreen button. - NSUInteger collectionBehavior = [window_ collectionBehavior]; - collectionBehavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; - [window_ setCollectionBehavior:collectionBehavior]; - } + // Disable zoom button if window is not resizable. + if (!maximizable) + SetMaximizable(false); NSView* view = inspectable_web_contents()->GetView()->GetNativeView(); [view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + // Use an NSEvent monitor to listen for the wheel event. + BOOL __block began = NO; + wheel_event_monitor_ = [NSEvent + addLocalMonitorForEventsMatchingMask:NSScrollWheelMask + handler:^(NSEvent* event) { + if ([[event window] windowNumber] != [window_ windowNumber]) + return event; + + if (!web_contents) + return event; + + if (!began && (([event phase] == NSEventPhaseMayBegin) || + ([event phase] == NSEventPhaseBegan))) { + this->NotifyWindowScrollTouchBegin(); + began = YES; + } else if (began && (([event phase] == NSEventPhaseEnded) || + ([event phase] == NSEventPhaseCancelled))) { + this->NotifyWindowScrollTouchEnd(); + began = NO; + } + return event; + }]; + InstallView(); } NativeWindowMac::~NativeWindowMac() { + [NSEvent removeMonitor:wheel_event_monitor_]; Observe(nullptr); } @@ -550,7 +600,16 @@ void NativeWindowMac::Unmaximize() { } bool NativeWindowMac::IsMaximized() { - return [window_ isZoomed]; + if (([window_ styleMask] & NSResizableWindowMask) != 0) { + return [window_ isZoomed]; + } else { + NSRect rectScreen = [[NSScreen mainScreen] visibleFrame]; + NSRect rectWindow = [window_ frame]; + return (rectScreen.origin.x == rectWindow.origin.x && + rectScreen.origin.y == rectWindow.origin.y && + rectScreen.size.width == rectWindow.size.width && + rectScreen.size.height == rectWindow.size.height); + } } void NativeWindowMac::Minimize() { @@ -631,19 +690,58 @@ void NativeWindowMac::SetResizable(bool resizable) { // Change styleMask for frameless causes the window to change size, so we have // to explicitly disables that. ScopedDisableResize disable_resize; - if (resizable) { - [[window_ standardWindowButton:NSWindowZoomButton] setEnabled:YES]; - [window_ setStyleMask:[window_ styleMask] | NSResizableWindowMask]; - } else { - [[window_ standardWindowButton:NSWindowZoomButton] setEnabled:NO]; - [window_ setStyleMask:[window_ styleMask] & (~NSResizableWindowMask)]; - } + SetStyleMask(resizable, NSResizableWindowMask); } bool NativeWindowMac::IsResizable() { return [window_ styleMask] & NSResizableWindowMask; } +void NativeWindowMac::SetMovable(bool movable) { + [window_ setMovable:movable]; +} + +bool NativeWindowMac::IsMovable() { + return [window_ isMovable]; +} + +void NativeWindowMac::SetMinimizable(bool minimizable) { + SetStyleMask(minimizable, NSMiniaturizableWindowMask); +} + +bool NativeWindowMac::IsMinimizable() { + return [window_ styleMask] & NSMiniaturizableWindowMask; +} + +void NativeWindowMac::SetMaximizable(bool maximizable) { + [[window_ standardWindowButton:NSWindowZoomButton] setEnabled:maximizable]; +} + +bool NativeWindowMac::IsMaximizable() { + return [[window_ standardWindowButton:NSWindowZoomButton] isEnabled]; +} + +void NativeWindowMac::SetFullScreenable(bool fullscreenable) { + SetCollectionBehavior( + fullscreenable, NSWindowCollectionBehaviorFullScreenPrimary); + // On EL Capitan this flag is required to hide fullscreen button. + SetCollectionBehavior( + !fullscreenable, NSWindowCollectionBehaviorFullScreenAuxiliary); +} + +bool NativeWindowMac::IsFullScreenable() { + NSUInteger collectionBehavior = [window_ collectionBehavior]; + return collectionBehavior & NSWindowCollectionBehaviorFullScreenPrimary; +} + +void NativeWindowMac::SetClosable(bool closable) { + SetStyleMask(closable, NSClosableWindowMask); +} + +bool NativeWindowMac::IsClosable() { + return [window_ styleMask] & NSClosableWindowMask; +} + void NativeWindowMac::SetAlwaysOnTop(bool top) { [window_ setLevel:(top ? NSFloatingWindowLevel : NSNormalWindowLevel)]; } @@ -710,10 +808,18 @@ void NativeWindowMac::SetBackgroundColor(const std::string& color_name) { NSColor *color = [NSColor colorWithCalibratedRed:SkColorGetR(background_color) green:SkColorGetG(background_color) blue:SkColorGetB(background_color) - alpha:1.0]; + alpha:SkColorGetA(background_color)/255.0f]; [window_ setBackgroundColor:color]; } +void NativeWindowMac::SetHasShadow(bool has_shadow) { + [window_ setHasShadow:has_shadow]; +} + +bool NativeWindowMac::HasShadow() { + return [window_ hasShadow]; +} + void NativeWindowMac::SetRepresentedFilename(const std::string& filename) { [window_ setRepresentedFilename:base::SysUTF8ToNSString(filename)]; } @@ -796,13 +902,7 @@ void NativeWindowMac::ShowDefinitionForSelection() { } void NativeWindowMac::SetVisibleOnAllWorkspaces(bool visible) { - NSUInteger collectionBehavior = [window_ collectionBehavior]; - if (visible) { - collectionBehavior |= NSWindowCollectionBehaviorCanJoinAllSpaces; - } else { - collectionBehavior &= ~NSWindowCollectionBehaviorCanJoinAllSpaces; - } - [window_ setCollectionBehavior:collectionBehavior]; + SetCollectionBehavior(visible, NSWindowCollectionBehaviorCanJoinAllSpaces); } bool NativeWindowMac::IsVisibleOnAllWorkspaces() { @@ -818,12 +918,15 @@ void NativeWindowMac::HandleKeyboardEvent( return; BOOL handled = [[NSApp mainMenu] performKeyEquivalent:event.os_event]; - if (!handled && event.os_event.window != window_.get()) { - // The event comes from detached devtools view, and it has already been - if (!handled && (event.os_event.modifierFlags & NSCommandKeyMask) && - (event.os_event.keyCode == 50 /* ~ key */)) { - // Handle the cmd+~ shortcut. - Focus(true); + if (!handled && event.os_event.window) { + // Handle the cmd+~ shortcut. + if ((event.os_event.modifierFlags & NSCommandKeyMask) /* cmd */ && + (event.os_event.keyCode == 50 /* ~ */)) { + if (event.os_event.modifierFlags & NSShiftKeyMask) { + [NSApp sendAction:@selector(_cycleWindowsReversed:) to:nil from:nil]; + } else { + [NSApp sendAction:@selector(_cycleWindows:) to:nil from:nil]; + } } } } @@ -959,6 +1062,30 @@ void NativeWindowMac::UpdateDraggableRegionViews( [window_ setMovableByWindowBackground:YES]; } +void NativeWindowMac::SetStyleMask(bool on, NSUInteger flag) { + bool zoom_button_enabled = IsMaximizable(); + if (on) + [window_ setStyleMask:[window_ styleMask] | flag]; + else + [window_ setStyleMask:[window_ styleMask] & (~flag)]; + // Change style mask will make the zoom button revert to default, probably + // a bug of Cocoa or OS X. + if (!zoom_button_enabled) + SetMaximizable(false); +} + +void NativeWindowMac::SetCollectionBehavior(bool on, NSUInteger flag) { + bool zoom_button_enabled = IsMaximizable(); + if (on) + [window_ setCollectionBehavior:[window_ collectionBehavior] | flag]; + else + [window_ setCollectionBehavior:[window_ collectionBehavior] & (~flag)]; + // Change collectionBehavior will make the zoom button revert to default, + // probably a bug of Cocoa or OS X. + if (!zoom_button_enabled) + SetMaximizable(false); +} + // static NativeWindow* NativeWindow::Create( brightray::InspectableWebContents* inspectable_web_contents, diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index ce2d41c6fad..cfbae95bda1 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -42,6 +42,12 @@ class NativeWindowObserver { // Called when window gains focus. virtual void OnWindowFocus() {} + // Called when window is shown. + virtual void OnWindowShow() {} + + // Called when window is hidden. + virtual void OnWindowHide() {} + // Called when window state changed. virtual void OnWindowMaximize() {} virtual void OnWindowUnmaximize() {} @@ -50,6 +56,9 @@ class NativeWindowObserver { virtual void OnWindowResize() {} virtual void OnWindowMove() {} virtual void OnWindowMoved() {} + virtual void OnWindowScrollTouchBegin() {} + virtual void OnWindowScrollTouchEnd() {} + virtual void OnWindowSwipe(const std::string& direction) {} virtual void OnWindowEnterFullScreen() {} virtual void OnWindowLeaveFullScreen() {} virtual void OnWindowEnterHtmlFullScreen() {} diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index da5c2f5e0b9..15f2046364b 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -59,6 +59,17 @@ const int kMenuBarHeight = 20; const int kMenuBarHeight = 25; #endif +#if defined(OS_WIN) +void FlipWindowStyle(HWND handle, bool on, DWORD flag) { + DWORD style = ::GetWindowLong(handle, GWL_STYLE); + if (on) + style |= flag; + else + style &= ~flag; + ::SetWindowLong(handle, GWL_STYLE, style); +} +#endif + bool IsAltKey(const content::NativeWebKeyboardEvent& event) { return event.windowsKeyCode == ui::VKEY_MENU; } @@ -103,7 +114,11 @@ NativeWindowViews::NativeWindowViews( menu_bar_alt_pressed_(false), keyboard_event_handler_(new views::UnhandledKeyboardEventHandler), use_content_size_(false), - resizable_(true) { + movable_(true), + resizable_(true), + maximizable_(true), + minimizable_(true), + fullscreenable_(true) { options.Get(options::kTitle, &title_); options.Get(options::kAutoHideMenuBar, &menu_bar_autohide_); @@ -111,6 +126,8 @@ NativeWindowViews::NativeWindowViews( // On Windows we rely on the CanResize() to indicate whether window can be // resized, and it should be set before window is created. options.Get(options::kResizable, &resizable_); + options.Get(options::kMinimizable, &minimizable_); + options.Get(options::kMaximizable, &maximizable_); #endif if (enable_larger_than_screen()) @@ -139,6 +156,11 @@ NativeWindowViews::NativeWindowViews( if (transparent()) params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; + // The given window is most likely not rectangular since it uses + // transparency and has no standard frame, don't show a shadow for it. + if (transparent() && !has_frame()) + params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE; + #if defined(OS_WIN) params.native_widget = new views::DesktopNativeWidgetAura(window_.get()); @@ -210,14 +232,18 @@ NativeWindowViews::NativeWindowViews( last_window_state_ = ui::SHOW_STATE_FULLSCREEN; else last_window_state_ = ui::SHOW_STATE_NORMAL; - last_normal_size_ = gfx::Size(widget_size_); if (!has_frame()) { // Set Window style so that we get a minimize and maximize animation when // frameless. - DWORD frame_style = WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | - WS_CAPTION; + DWORD frame_style = WS_CAPTION; + if (resizable_) + frame_style |= WS_THICKFRAME; + if (minimizable_) + frame_style |= WS_MINIMIZEBOX; + if (maximizable_) + frame_style |= WS_MAXIMIZEBOX; // We should not show a frame for transparent window. if (transparent()) frame_style &= ~(WS_THICKFRAME | WS_CAPTION); @@ -239,11 +265,6 @@ NativeWindowViews::NativeWindowViews( window_->FrameTypeChanged(); } - // The given window is most likely not rectangular since it uses - // transparency and has no standard frame, don't show a shadow for it. - if (transparent() && !has_frame()) - wm::SetShadowType(GetNativeWindow(), wm::SHADOW_TYPE_NONE); - gfx::Size size = bounds.size(); if (has_frame() && options.Get(options::kUseContentSize, &use_content_size_) && @@ -280,14 +301,35 @@ bool NativeWindowViews::IsFocused() { void NativeWindowViews::Show() { window_->native_widget_private()->ShowWithWindowState(GetRestoredState()); + + NotifyWindowShow(); + +#if defined(USE_X11) + if (global_menu_bar_) + global_menu_bar_->OnWindowMapped(); +#endif } void NativeWindowViews::ShowInactive() { window_->ShowInactive(); + + NotifyWindowShow(); + +#if defined(USE_X11) + if (global_menu_bar_) + global_menu_bar_->OnWindowMapped(); +#endif } void NativeWindowViews::Hide() { window_->Hide(); + + NotifyWindowHide(); + +#if defined(USE_X11) + if (global_menu_bar_) + global_menu_bar_->OnWindowUnmapped(); +#endif } bool NativeWindowViews::IsVisible() { @@ -327,6 +369,9 @@ bool NativeWindowViews::IsMinimized() { } void NativeWindowViews::SetFullScreen(bool fullscreen) { + if (!IsFullScreenable()) + return; + #if defined(OS_WIN) // There is no native fullscreen state on Windows. if (fullscreen) { @@ -399,17 +444,8 @@ void NativeWindowViews::SetContentSizeConstraints( void NativeWindowViews::SetResizable(bool resizable) { #if defined(OS_WIN) - // WS_MAXIMIZEBOX => Maximize button - // WS_MINIMIZEBOX => Minimize button - // WS_THICKFRAME => Resize handle - if (!transparent()) { - DWORD style = ::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE); - if (resizable) - style |= WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME; - else - style = (style & ~(WS_MAXIMIZEBOX | WS_THICKFRAME)) | WS_MINIMIZEBOX; - ::SetWindowLong(GetAcceleratedWidget(), GWL_STYLE, style); - } + if (!transparent()) + FlipWindowStyle(GetAcceleratedWidget(), resizable, WS_THICKFRAME); #elif defined(USE_X11) if (resizable != resizable_) { // On Linux there is no "resizable" property of a window, we have to set @@ -430,7 +466,88 @@ void NativeWindowViews::SetResizable(bool resizable) { } bool NativeWindowViews::IsResizable() { - return resizable_; +#if defined(OS_WIN) + return ::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME; +#else + return CanResize(); +#endif +} + +void NativeWindowViews::SetMovable(bool movable) { + movable_ = movable; +} + +bool NativeWindowViews::IsMovable() { +#if defined(OS_WIN) + return movable_; +#else + return true; // Not implemented on Linux. +#endif +} + +void NativeWindowViews::SetMinimizable(bool minimizable) { +#if defined(OS_WIN) + FlipWindowStyle(GetAcceleratedWidget(), minimizable, WS_MINIMIZEBOX); +#endif + minimizable_ = minimizable; +} + +bool NativeWindowViews::IsMinimizable() { +#if defined(OS_WIN) + return ::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_MINIMIZEBOX; +#else + return true; // Not implemented on Linux. +#endif +} + +void NativeWindowViews::SetMaximizable(bool maximizable) { +#if defined(OS_WIN) + FlipWindowStyle(GetAcceleratedWidget(), maximizable, WS_MAXIMIZEBOX); +#endif + maximizable_ = maximizable; +} + +bool NativeWindowViews::IsMaximizable() { +#if defined(OS_WIN) + return ::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_MAXIMIZEBOX; +#else + return true; // Not implemented on Linux. +#endif +} + +void NativeWindowViews::SetFullScreenable(bool fullscreenable) { + fullscreenable_ = fullscreenable; +} + +bool NativeWindowViews::IsFullScreenable() { + return fullscreenable_; +} + +void NativeWindowViews::SetClosable(bool closable) { +#if defined(OS_WIN) + HMENU menu = GetSystemMenu(GetAcceleratedWidget(), false); + if (closable) { + EnableMenuItem(menu, SC_CLOSE, MF_BYCOMMAND | MF_ENABLED); + } else { + EnableMenuItem(menu, SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); + } +#endif +} + +bool NativeWindowViews::IsClosable() { +#if defined(OS_WIN) + HMENU menu = GetSystemMenu(GetAcceleratedWidget(), false); + MENUITEMINFO info; + memset(&info, 0, sizeof(info)); + info.cbSize = sizeof(info); + info.fMask = MIIM_STATE; + if (!GetMenuItemInfo(menu, SC_CLOSE, false, &info)) { + return false; + } + return !(info.fState & MFS_DISABLED); +#elif defined(USE_X11) + return true; +#endif } void NativeWindowViews::SetAlwaysOnTop(bool top) { @@ -510,6 +627,16 @@ void NativeWindowViews::SetBackgroundColor(const std::string& color_name) { #endif } +void NativeWindowViews::SetHasShadow(bool has_shadow) { + wm::SetShadowType( + GetNativeWindow(), + has_shadow ? wm::SHADOW_TYPE_RECTANGULAR : wm::SHADOW_TYPE_NONE); +} + +bool NativeWindowViews::HasShadow() { + return wm::GetShadowType(GetNativeWindow()) != wm::SHADOW_TYPE_NONE; +} + void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) { if (menu_model == nullptr) { // Remove accelerators @@ -690,11 +817,15 @@ bool NativeWindowViews::CanResize() const { } bool NativeWindowViews::CanMaximize() const { - return resizable_; + return resizable_ && maximizable_; } bool NativeWindowViews::CanMinimize() const { +#if defined(OS_WIN) + return minimizable_; +#elif defined(USE_X11) return true; +#endif } base::string16 NativeWindowViews::GetWindowTitle() const { diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 6592242210f..862cd5458bb 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -68,6 +68,16 @@ class NativeWindowViews : public NativeWindow, const extensions::SizeConstraints& size_constraints) override; void SetResizable(bool resizable) override; bool IsResizable() override; + void SetMovable(bool movable) override; + bool IsMovable() override; + void SetMinimizable(bool minimizable) override; + bool IsMinimizable() override; + void SetMaximizable(bool maximizable) override; + bool IsMaximizable() override; + void SetFullScreenable(bool fullscreenable) override; + bool IsFullScreenable() override; + void SetClosable(bool closable) override; + bool IsClosable() override; void SetAlwaysOnTop(bool top) override; bool IsAlwaysOnTop() override; void Center() override; @@ -78,6 +88,8 @@ class NativeWindowViews : public NativeWindow, void SetKiosk(bool kiosk) override; bool IsKiosk() override; void SetBackgroundColor(const std::string& color_name) override; + void SetHasShadow(bool has_shadow) override; + bool HasShadow() override; void SetMenu(ui::MenuModel* menu_model) override; gfx::NativeWindow GetNativeWindow() override; void SetOverlayIcon(const gfx::Image& overlay, @@ -196,7 +208,11 @@ class NativeWindowViews : public NativeWindow, accelerator_util::AcceleratorTable accelerator_table_; bool use_content_size_; + bool movable_; bool resizable_; + bool maximizable_; + bool minimizable_; + bool fullscreenable_; std::string title_; gfx::Size widget_size_; diff --git a/atom/browser/native_window_views_win.cc b/atom/browser/native_window_views_win.cc index 513b33bcb85..e5ed1975f80 100644 --- a/atom/browser/native_window_views_win.cc +++ b/atom/browser/native_window_views_win.cc @@ -89,10 +89,18 @@ bool NativeWindowViews::PreHandleMSG( if (HIWORD(w_param) == THBN_CLICKED) return taskbar_host_.HandleThumbarButtonEvent(LOWORD(w_param)); return false; + case WM_SIZE: // Handle window state change. HandleSizeEvent(w_param, l_param); return false; + + case WM_MOVING: { + if (!movable_) + ::GetWindowRect(GetAcceleratedWidget(), (LPRECT)l_param); + return false; + } + default: return false; } diff --git a/atom/browser/net/asar/url_request_asar_job.cc b/atom/browser/net/asar/url_request_asar_job.cc index d926d111172..39e55a35cbc 100644 --- a/atom/browser/net/asar/url_request_asar_job.cc +++ b/atom/browser/net/asar/url_request_asar_job.cc @@ -44,6 +44,7 @@ URLRequestAsarJob::URLRequestAsarJob( : net::URLRequestJob(request, network_delegate), type_(TYPE_ERROR), remaining_bytes_(0), + range_parse_result_(net::OK), weak_ptr_factory_(this) {} URLRequestAsarJob::~URLRequestAsarJob() {} @@ -99,7 +100,7 @@ void URLRequestAsarJob::InitializeFileJob( void URLRequestAsarJob::Start() { if (type_ == TYPE_ASAR) { - remaining_bytes_ = static_cast(file_info_.size); + remaining_bytes_ = static_cast(file_info_.size); int flags = base::File::FLAG_OPEN | base::File::FLAG_READ | @@ -131,18 +132,14 @@ void URLRequestAsarJob::Kill() { URLRequestJob::Kill(); } -bool URLRequestAsarJob::ReadRawData(net::IOBuffer* dest, - int dest_size, - int* bytes_read) { +int URLRequestAsarJob::ReadRawData(net::IOBuffer* dest, int dest_size) { if (remaining_bytes_ < dest_size) dest_size = static_cast(remaining_bytes_); // If we should copy zero bytes because |remaining_bytes_| is zero, short // circuit here. - if (!dest_size) { - *bytes_read = 0; - return true; - } + if (!dest_size) + return 0; int rv = stream_->Read(dest, dest_size, @@ -150,20 +147,11 @@ bool URLRequestAsarJob::ReadRawData(net::IOBuffer* dest, weak_ptr_factory_.GetWeakPtr(), make_scoped_refptr(dest))); if (rv >= 0) { - // Data is immediately available. - *bytes_read = rv; remaining_bytes_ -= rv; DCHECK_GE(remaining_bytes_, 0); - return true; } - // Otherwise, a read error occured. We may just need to wait... - if (rv == net::ERR_IO_PENDING) { - SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); - } else { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, rv)); - } - return false; + return rv; } bool URLRequestAsarJob::IsRedirectResponse(GURL* location, @@ -214,15 +202,16 @@ void URLRequestAsarJob::SetExtraRequestHeaders( const net::HttpRequestHeaders& headers) { std::string range_header; if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { - // We only care about "Range" header here. + // This job only cares about the Range header. This method stashes the value + // for later use in DidOpen(), which is responsible for some of the range + // validation as well. NotifyStartError is not legal to call here since + // the job has not started. std::vector ranges; if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { if (ranges.size() == 1) { byte_range_ = ranges[0]; } else { - NotifyDone(net::URLRequestStatus( - net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + range_parse_result_ = net::ERR_REQUEST_RANGE_NOT_SATISFIABLE; } } } @@ -274,7 +263,14 @@ void URLRequestAsarJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) { void URLRequestAsarJob::DidOpen(int result) { if (result != net::OK) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); + NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, + result)); + return; + } + + if (range_parse_result_ != net::OK) { + NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, + range_parse_result_)); return; } @@ -289,8 +285,9 @@ void URLRequestAsarJob::DidOpen(int result) { } } else { if (!byte_range_.ComputeBounds(meta_info_.file_size)) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + NotifyStartError( + net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); return; } @@ -315,17 +312,19 @@ void URLRequestAsarJob::DidOpen(int result) { } } -void URLRequestAsarJob::DidSeek(int64 result) { +void URLRequestAsarJob::DidSeek(int64_t result) { if (type_ == TYPE_ASAR) { - if (result != static_cast(file_info_.offset)) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + if (result != static_cast(file_info_.offset)) { + NotifyStartError( + net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); return; } } else { if (result != byte_range_.first_byte_position()) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + NotifyStartError( + net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); return; } } @@ -334,21 +333,14 @@ void URLRequestAsarJob::DidSeek(int64 result) { } void URLRequestAsarJob::DidRead(scoped_refptr buf, int result) { - if (result > 0) { - SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status + if (result >= 0) { remaining_bytes_ -= result; DCHECK_GE(remaining_bytes_, 0); } buf = NULL; - if (result == 0) { - NotifyDone(net::URLRequestStatus()); - } else if (result < 0) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); - } - - NotifyReadComplete(result); + ReadRawDataComplete(result); } } // namespace asar diff --git a/atom/browser/net/asar/url_request_asar_job.h b/atom/browser/net/asar/url_request_asar_job.h index 29d1afc521d..7103abc4139 100644 --- a/atom/browser/net/asar/url_request_asar_job.h +++ b/atom/browser/net/asar/url_request_asar_job.h @@ -54,9 +54,7 @@ class URLRequestAsarJob : public net::URLRequestJob { // net::URLRequestJob: void Start() override; void Kill() override; - bool ReadRawData(net::IOBuffer* buf, - int buf_size, - int* bytes_read) override; + int ReadRawData(net::IOBuffer* buf, int buf_size) override; bool IsRedirectResponse(GURL* location, int* http_status_code) override; net::Filter* SetupFilter() const override; bool GetMimeType(std::string* mime_type) const override; @@ -72,7 +70,7 @@ class URLRequestAsarJob : public net::URLRequestJob { FileMetaInfo(); // Size of the file. - int64 file_size; + int64_t file_size; // Mime type associated with the file. std::string mime_type; // Result returned from GetMimeTypeFromFile(), i.e. flag showing whether @@ -97,7 +95,7 @@ class URLRequestAsarJob : public net::URLRequestJob { // Callback after seeking to the beginning of |byte_range_| in the file // on a background thread. - void DidSeek(int64 result); + void DidSeek(int64_t result); // Callback after data is asynchronously read from the file into |buf|. void DidRead(scoped_refptr buf, int result); @@ -119,7 +117,9 @@ class URLRequestAsarJob : public net::URLRequestJob { scoped_refptr file_task_runner_; net::HttpByteRange byte_range_; - int64 remaining_bytes_; + int64_t remaining_bytes_; + + net::Error range_parse_result_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/atom/browser/net/atom_network_delegate.cc b/atom/browser/net/atom_network_delegate.cc index 4f9bc835ed2..91b63fada35 100644 --- a/atom/browser/net/atom_network_delegate.cc +++ b/atom/browser/net/atom_network_delegate.cc @@ -75,6 +75,10 @@ void ToDictionary(base::DictionaryValue* details, net::URLRequest* request) { details->SetString("resourceType", info ? ResourceTypeToString(info->GetResourceType()) : "other"); + scoped_ptr list(new base::ListValue); + GetUploadData(list.get(), request); + if (!list->empty()) + details->Set("uploadData", std::move(list)); } void ToDictionary(base::DictionaryValue* details, @@ -83,7 +87,7 @@ void ToDictionary(base::DictionaryValue* details, net::HttpRequestHeaders::Iterator it(headers); while (it.GetNext()) dict->SetString(it.name(), it.value()); - details->Set("requestHeaders", dict.Pass()); + details->Set("requestHeaders", std::move(dict)); } void ToDictionary(base::DictionaryValue* details, @@ -103,10 +107,10 @@ void ToDictionary(base::DictionaryValue* details, } else { scoped_ptr values(new base::ListValue); values->AppendString(value); - dict->Set(key, values.Pass()); + dict->Set(key, std::move(values)); } } - details->Set("responseHeaders", dict.Pass()); + details->Set("responseHeaders", std::move(dict)); details->SetString("statusLine", headers->GetStatusLine()); details->SetInteger("statusCode", headers->response_code()); } diff --git a/atom/browser/net/atom_ssl_config_service.cc b/atom/browser/net/atom_ssl_config_service.cc index 2b3143e93cb..c306a8a3917 100644 --- a/atom/browser/net/atom_ssl_config_service.cc +++ b/atom/browser/net/atom_ssl_config_service.cc @@ -18,8 +18,8 @@ namespace atom { namespace { -uint16 GetSSLProtocolVersion(const std::string& version_string) { - uint16 version = 0; // Invalid +uint16_t GetSSLProtocolVersion(const std::string& version_string) { + uint16_t version = 0; // Invalid if (version_string == "tls1") version = net::SSL_PROTOCOL_VERSION_TLS1; else if (version_string == "tls1.1") @@ -29,13 +29,13 @@ uint16 GetSSLProtocolVersion(const std::string& version_string) { return version; } -std::vector ParseCipherSuites( +std::vector ParseCipherSuites( const std::vector& cipher_strings) { - std::vector cipher_suites; + std::vector cipher_suites; cipher_suites.reserve(cipher_strings.size()); for (auto& cipher_string : cipher_strings) { - uint16 cipher_suite = 0; + uint16_t cipher_suite = 0; if (!net::ParseSSLCipherString(cipher_string, &cipher_suite)) { LOG(ERROR) << "Ignoring unrecognised cipher suite : " << cipher_string; diff --git a/atom/browser/net/js_asker.cc b/atom/browser/net/js_asker.cc index 8f0d1d2b957..b11a69c9c13 100644 --- a/atom/browser/net/js_asker.cc +++ b/atom/browser/net/js_asker.cc @@ -16,7 +16,9 @@ namespace internal { namespace { // The callback which is passed to |handler|. -void HandlerCallback(const ResponseCallback& callback, mate::Arguments* args) { +void HandlerCallback(const BeforeStartCallback& before_start, + const ResponseCallback& callback, + mate::Arguments* args) { // If there is no argument passed then we failed. v8::Local value; if (!args->GetNext(&value)) { @@ -26,6 +28,9 @@ void HandlerCallback(const ResponseCallback& callback, mate::Arguments* args) { return; } + // Give the job a chance to parse V8 value. + before_start.Run(args->isolate(), value); + // Pass whatever user passed to the actaul request job. V8ValueConverter converter; v8::Local context = args->isolate()->GetCurrentContext(); @@ -40,15 +45,17 @@ void HandlerCallback(const ResponseCallback& callback, mate::Arguments* args) { void AskForOptions(v8::Isolate* isolate, const JavaScriptHandler& handler, net::URLRequest* request, + const BeforeStartCallback& before_start, const ResponseCallback& callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); v8::Locker locker(isolate); v8::HandleScope handle_scope(isolate); v8::Local context = isolate->GetCurrentContext(); v8::Context::Scope context_scope(context); - handler.Run(request, - mate::ConvertToV8(isolate, - base::Bind(&HandlerCallback, callback))); + handler.Run( + request, + mate::ConvertToV8(isolate, + base::Bind(&HandlerCallback, before_start, callback))); } bool IsErrorOptions(base::Value* value, int* error) { diff --git a/atom/browser/net/js_asker.h b/atom/browser/net/js_asker.h index 8ec245ee8c4..8a70794fa94 100644 --- a/atom/browser/net/js_asker.h +++ b/atom/browser/net/js_asker.h @@ -23,6 +23,8 @@ using JavaScriptHandler = namespace internal { +using BeforeStartCallback = + base::Callback)>; using ResponseCallback = base::Callback options)>; @@ -30,6 +32,7 @@ using ResponseCallback = void AskForOptions(v8::Isolate* isolate, const JavaScriptHandler& handler, net::URLRequest* request, + const BeforeStartCallback& before_start, const ResponseCallback& callback); // Test whether the |options| means an error. @@ -54,6 +57,7 @@ class JsAsker : public RequestJob { } // Subclass should do initailze work here. + virtual void BeforeStartInUI(v8::Isolate*, v8::Local) {} virtual void StartAsync(scoped_ptr options) = 0; net::URLRequestContextGetter* request_context_getter() const { @@ -69,6 +73,8 @@ class JsAsker : public RequestJob { isolate_, handler_, RequestJob::request(), + base::Bind(&JsAsker::BeforeStartInUI, + weak_factory_.GetWeakPtr()), base::Bind(&JsAsker::OnResponse, weak_factory_.GetWeakPtr()))); } @@ -81,7 +87,7 @@ class JsAsker : public RequestJob { void OnResponse(bool success, scoped_ptr value) { int error = net::ERR_NOT_IMPLEMENTED; if (success && value && !internal::IsErrorOptions(value.get(), &error)) { - StartAsync(value.Pass()); + StartAsync(std::move(value)); } else { RequestJob::NotifyStartError( net::URLRequestStatus(net::URLRequestStatus::FAILED, error)); diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc index 6f418290175..2f907314cad 100644 --- a/atom/browser/net/url_request_fetch_job.cc +++ b/atom/browser/net/url_request_fetch_job.cc @@ -8,15 +8,14 @@ #include #include "base/strings/string_util.h" -#include "base/thread_task_runner_handle.h" +#include "native_mate/dictionary.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher_response_writer.h" -#include "net/url_request/url_request_context.h" -#include "net/url_request/url_request_context_builder.h" -#include "net/url_request/url_request_status.h" + +using content::BrowserThread; namespace atom { @@ -81,6 +80,25 @@ URLRequestFetchJob::URLRequestFetchJob( pending_buffer_size_(0) { } +void URLRequestFetchJob::BeforeStartInUI( + v8::Isolate* isolate, v8::Local value) { + mate::Dictionary options; + if (!mate::ConvertFromV8(isolate, value, &options)) + return; + + // When |session| is set to |null| we use a new request context for fetch job. + // TODO(zcbenz): Handle the case when it is not null. + v8::Local session; + if (options.Get("session", &session) && session->IsNull()) { + // We have to create the URLRequestContextGetter on UI thread. + url_request_context_getter_ = new brightray::URLRequestContextGetter( + this, nullptr, nullptr, base::FilePath(), true, + BrowserThread::UnsafeGetMessageLoopForThread(BrowserThread::IO), + BrowserThread::UnsafeGetMessageLoopForThread(BrowserThread::FILE), + nullptr, content::URLRequestInterceptorScopedVector()); + } +} + void URLRequestFetchJob::StartAsync(scoped_ptr options) { if (!options->IsType(base::Value::TYPE_DICTIONARY)) { NotifyStartError(net::URLRequestStatus( @@ -89,14 +107,12 @@ void URLRequestFetchJob::StartAsync(scoped_ptr options) { } std::string url, method, referrer; - base::Value* session = nullptr; base::DictionaryValue* upload_data = nullptr; base::DictionaryValue* dict = static_cast(options.get()); dict->GetString("url", &url); dict->GetString("method", &method); dict->GetString("referrer", &referrer); - dict->Get("session", &session); dict->GetDictionary("uploadData", &upload_data); // Check if URL is valid. @@ -117,9 +133,9 @@ void URLRequestFetchJob::StartAsync(scoped_ptr options) { fetcher_ = net::URLFetcher::Create(formated_url, request_type, this); fetcher_->SaveResponseWithWriter(make_scoped_ptr(new ResponsePiper(this))); - // When |session| is set to |null| we use a new request context for fetch job. - if (session && session->IsType(base::Value::TYPE_NULL)) - fetcher_->SetRequestContext(CreateRequestContext()); + // A request context getter is passed by the user. + if (url_request_context_getter_) + fetcher_->SetRequestContext(url_request_context_getter_.get()); else fetcher_->SetRequestContext(request_context_getter()); @@ -144,18 +160,6 @@ void URLRequestFetchJob::StartAsync(scoped_ptr options) { fetcher_->Start(); } -net::URLRequestContextGetter* URLRequestFetchJob::CreateRequestContext() { - if (!url_request_context_getter_.get()) { - auto task_runner = base::ThreadTaskRunnerHandle::Get(); - net::URLRequestContextBuilder builder; - builder.set_proxy_service(net::ProxyService::CreateDirect()); - request_context_ = builder.Build(); - url_request_context_getter_ = new net::TrivialURLRequestContextGetter( - request_context_.get(), task_runner); - } - return url_request_context_getter_.get(); -} - void URLRequestFetchJob::HeadersCompleted() { response_info_.reset(new net::HttpResponseInfo); response_info_->headers = fetcher_->GetResponseHeaders(); @@ -163,8 +167,6 @@ void URLRequestFetchJob::HeadersCompleted() { } int URLRequestFetchJob::DataAvailable(net::IOBuffer* buffer, int num_bytes) { - // Clear the IO_PENDING status. - SetStatus(net::URLRequestStatus()); // Do nothing if pending_buffer_ is empty, i.e. there's no ReadRawData() // operation waiting for IO completion. if (!pending_buffer_.get()) @@ -172,7 +174,6 @@ int URLRequestFetchJob::DataAvailable(net::IOBuffer* buffer, int num_bytes) { // pending_buffer_ is set to the IOBuffer instance provided to ReadRawData() // by URLRequestJob. - int bytes_read = std::min(num_bytes, pending_buffer_size_); memcpy(pending_buffer_->data(), buffer->data(), bytes_read); @@ -181,7 +182,7 @@ int URLRequestFetchJob::DataAvailable(net::IOBuffer* buffer, int num_bytes) { pending_buffer_ = nullptr; pending_buffer_size_ = 0; - NotifyReadComplete(bytes_read); + ReadRawDataComplete(bytes_read); return bytes_read; } @@ -190,18 +191,14 @@ void URLRequestFetchJob::Kill() { fetcher_.reset(); } -bool URLRequestFetchJob::ReadRawData(net::IOBuffer* dest, - int dest_size, - int* bytes_read) { +int URLRequestFetchJob::ReadRawData(net::IOBuffer* dest, int dest_size) { if (GetResponseCode() == 204) { - *bytes_read = 0; request()->set_received_response_content_length(prefilter_bytes_read()); - return true; + return net::OK; } pending_buffer_ = dest; pending_buffer_size_ = dest_size; - SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); - return false; + return net::ERR_IO_PENDING; } bool URLRequestFetchJob::GetMimeType(std::string* mime_type) const { @@ -234,9 +231,10 @@ void URLRequestFetchJob::OnURLFetchComplete(const net::URLFetcher* source) { pending_buffer_ = nullptr; pending_buffer_size_ = 0; - NotifyDone(fetcher_->GetStatus()); if (fetcher_->GetStatus().is_success()) - NotifyReadComplete(0); + ReadRawDataComplete(0); + else + NotifyStartError(fetcher_->GetStatus()); } } // namespace atom diff --git a/atom/browser/net/url_request_fetch_job.h b/atom/browser/net/url_request_fetch_job.h index 399f78ae396..69067fdc7fd 100644 --- a/atom/browser/net/url_request_fetch_job.h +++ b/atom/browser/net/url_request_fetch_job.h @@ -8,6 +8,7 @@ #include #include "atom/browser/net/js_asker.h" +#include "browser/url_request_context_getter.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_fetcher_delegate.h" #include "net/url_request/url_request_job.h" @@ -17,7 +18,8 @@ namespace atom { class AtomBrowserContext; class URLRequestFetchJob : public JsAsker, - public net::URLFetcherDelegate { + public net::URLFetcherDelegate, + public brightray::URLRequestContextGetter::Delegate { public: URLRequestFetchJob(net::URLRequest*, net::NetworkDelegate*); @@ -27,13 +29,12 @@ class URLRequestFetchJob : public JsAsker, protected: // JsAsker: + void BeforeStartInUI(v8::Isolate*, v8::Local) override; void StartAsync(scoped_ptr options) override; // net::URLRequestJob: void Kill() override; - bool ReadRawData(net::IOBuffer* buf, - int buf_size, - int* bytes_read) override; + int ReadRawData(net::IOBuffer* buf, int buf_size) override; bool GetMimeType(std::string* mime_type) const override; void GetResponseInfo(net::HttpResponseInfo* info) override; int GetResponseCode() const override; @@ -42,10 +43,6 @@ class URLRequestFetchJob : public JsAsker, void OnURLFetchComplete(const net::URLFetcher* source) override; private: - // Create a independent request context. - net::URLRequestContextGetter* CreateRequestContext(); - - scoped_ptr request_context_; scoped_refptr url_request_context_getter_; scoped_ptr fetcher_; scoped_refptr pending_buffer_; diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc index 2cfcdb222ae..50aa454fe75 100644 --- a/atom/browser/node_debugger.cc +++ b/atom/browser/node_debugger.cc @@ -152,7 +152,7 @@ void NodeDebugger::DidAccept( return; } - accepted_socket_ = socket.Pass(); + accepted_socket_ = std::move(socket); SendConnectMessage(); } diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index f1de499afed..e2afa33f7df 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -15,15 +15,15 @@ CFBundlePackageType APPL CFBundleIconFile - atom.icns + electron.icns CFBundleVersion - 0.36.4 + 0.37.3 CFBundleShortVersionString - 0.36.4 + 0.37.3 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion - 10.8.0 + 10.9.0 NSMainNibFile MainMenu NSPrincipalClass diff --git a/atom/browser/resources/mac/atom.icns b/atom/browser/resources/mac/electron.icns similarity index 100% rename from atom/browser/resources/mac/atom.icns rename to atom/browser/resources/mac/electron.icns diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 5763cb7ba3c..15252346669 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 0,36,4,0 - PRODUCTVERSION 0,36,4,0 + FILEVERSION 0,37,3,0 + PRODUCTVERSION 0,37,3,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -74,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.36.4" + VALUE "FileVersion", "0.37.3" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.36.4" + VALUE "ProductVersion", "0.37.3" VALUE "SquirrelAwareVersion", "1" END END diff --git a/atom/browser/ui/accelerator_util.cc b/atom/browser/ui/accelerator_util.cc index a0b90e0c7e5..f683c99c637 100644 --- a/atom/browser/ui/accelerator_util.cc +++ b/atom/browser/ui/accelerator_util.cc @@ -9,7 +9,7 @@ #include #include -#include "atom/common/keyboad_util.h" +#include "atom/common/keyboard_util.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" @@ -18,13 +18,12 @@ namespace accelerator_util { -bool StringToAccelerator(const std::string& description, +bool StringToAccelerator(const std::string& shortcut, ui::Accelerator* accelerator) { - if (!base::IsStringASCII(description)) { + if (!base::IsStringASCII(shortcut)) { LOG(ERROR) << "The accelerator string can only contain ASCII characters"; return false; } - std::string shortcut(base::ToLowerASCII(description)); std::vector tokens = base::SplitString( shortcut, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); @@ -33,95 +32,35 @@ bool StringToAccelerator(const std::string& description, int modifiers = ui::EF_NONE; ui::KeyboardCode key = ui::VKEY_UNKNOWN; for (size_t i = 0; i < tokens.size(); i++) { - // We use straight comparing instead of map because the accelerator tends - // to be correct and usually only uses few special tokens. - if (tokens[i].size() == 1) { - bool shifted = false; - key = atom::KeyboardCodeFromCharCode(tokens[i][0], &shifted); - if (shifted) + bool shifted = false; + ui::KeyboardCode code = atom::KeyboardCodeFromStr(tokens[i], &shifted); + if (shifted) + modifiers |= ui::EF_SHIFT_DOWN; + switch (code) { + // The token can be a modifier. + case ui::VKEY_SHIFT: modifiers |= ui::EF_SHIFT_DOWN; - } else if (tokens[i] == "ctrl" || tokens[i] == "control") { - modifiers |= ui::EF_CONTROL_DOWN; - } else if (tokens[i] == "super") { - modifiers |= ui::EF_COMMAND_DOWN; -#if defined(OS_MACOSX) - } else if (tokens[i] == "cmd" || tokens[i] == "command") { - modifiers |= ui::EF_COMMAND_DOWN; -#endif - } else if (tokens[i] == "commandorcontrol" || tokens[i] == "cmdorctrl") { -#if defined(OS_MACOSX) - modifiers |= ui::EF_COMMAND_DOWN; -#else - modifiers |= ui::EF_CONTROL_DOWN; -#endif - } else if (tokens[i] == "alt") { - modifiers |= ui::EF_ALT_DOWN; - } else if (tokens[i] == "shift") { - modifiers |= ui::EF_SHIFT_DOWN; - } else if (tokens[i] == "plus") { - modifiers |= ui::EF_SHIFT_DOWN; - key = ui::VKEY_OEM_PLUS; - } else if (tokens[i] == "tab") { - key = ui::VKEY_TAB; - } else if (tokens[i] == "space") { - key = ui::VKEY_SPACE; - } else if (tokens[i] == "backspace") { - key = ui::VKEY_BACK; - } else if (tokens[i] == "delete") { - key = ui::VKEY_DELETE; - } else if (tokens[i] == "insert") { - key = ui::VKEY_INSERT; - } else if (tokens[i] == "enter" || tokens[i] == "return") { - key = ui::VKEY_RETURN; - } else if (tokens[i] == "up") { - key = ui::VKEY_UP; - } else if (tokens[i] == "down") { - key = ui::VKEY_DOWN; - } else if (tokens[i] == "left") { - key = ui::VKEY_LEFT; - } else if (tokens[i] == "right") { - key = ui::VKEY_RIGHT; - } else if (tokens[i] == "home") { - key = ui::VKEY_HOME; - } else if (tokens[i] == "end") { - key = ui::VKEY_END; - } else if (tokens[i] == "pageup") { - key = ui::VKEY_PRIOR; - } else if (tokens[i] == "pagedown") { - key = ui::VKEY_NEXT; - } else if (tokens[i] == "esc" || tokens[i] == "escape") { - key = ui::VKEY_ESCAPE; - } else if (tokens[i] == "volumemute") { - key = ui::VKEY_VOLUME_MUTE; - } else if (tokens[i] == "volumeup") { - key = ui::VKEY_VOLUME_UP; - } else if (tokens[i] == "volumedown") { - key = ui::VKEY_VOLUME_DOWN; - } else if (tokens[i] == "medianexttrack") { - key = ui::VKEY_MEDIA_NEXT_TRACK; - } else if (tokens[i] == "mediaprevioustrack") { - key = ui::VKEY_MEDIA_PREV_TRACK; - } else if (tokens[i] == "mediastop") { - key = ui::VKEY_MEDIA_STOP; - } else if (tokens[i] == "mediaplaypause") { - key = ui::VKEY_MEDIA_PLAY_PAUSE; - } else if (tokens[i].size() > 1 && tokens[i][0] == 'f') { - // F1 - F24. - int n; - if (base::StringToInt(tokens[i].c_str() + 1, &n) && n > 0 && n < 25) { - key = static_cast(ui::VKEY_F1 + n - 1); - } else { - LOG(WARNING) << tokens[i] << "is not available on keyboard"; - return false; - } - } else { - LOG(WARNING) << "Invalid accelerator token: " << tokens[i]; - return false; + break; + case ui::VKEY_CONTROL: + modifiers |= ui::EF_CONTROL_DOWN; + break; + case ui::VKEY_MENU: + modifiers |= ui::EF_ALT_DOWN; + break; + case ui::VKEY_COMMAND: + modifiers |= ui::EF_COMMAND_DOWN; + break; + case ui::VKEY_ALTGR: + modifiers |= ui::EF_ALTGR_DOWN; + break; + // Or it is a normal key. + default: + key = code; } } if (key == ui::VKEY_UNKNOWN) { - LOG(WARNING) << "The accelerator doesn't contain a valid key"; + LOG(WARNING) << shortcut << " doesn't contain a valid key"; return false; } diff --git a/atom/browser/ui/accelerator_util_mac.mm b/atom/browser/ui/accelerator_util_mac.mm index 2075b1041f6..be631b02124 100644 --- a/atom/browser/ui/accelerator_util_mac.mm +++ b/atom/browser/ui/accelerator_util_mac.mm @@ -28,7 +28,7 @@ void SetPlatformAccelerator(ui::Accelerator* accelerator) { scoped_ptr platform_accelerator( new ui::PlatformAcceleratorCocoa(characters, modifiers)); - accelerator->set_platform_accelerator(platform_accelerator.Pass()); + accelerator->set_platform_accelerator(std::move(platform_accelerator)); } } // namespace accelerator_util diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index 9c8c99da9aa..24098914b7c 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -32,9 +32,12 @@ Role kRolesMap[] = { { @selector(cut:), "cut" }, { @selector(copy:), "copy" }, { @selector(paste:), "paste" }, + { @selector(delete:), "delete" }, + { @selector(pasteAndMatchStyle:), "paste-and-match-style" }, { @selector(selectAll:), "selectall" }, { @selector(performMiniaturize:), "minimize" }, { @selector(performClose:), "close" }, + { @selector(performZoom:), "zoom" }, }; } // namespace diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index ed79449655f..6d19a2eda46 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -39,7 +39,8 @@ class FileChooserDialog { const std::string& title, const base::FilePath& default_path, const Filters& filters) - : dialog_scope_(parent_window) { + : dialog_scope_(parent_window), + filters_(filters) { const char* confirm_text = GTK_STOCK_OK; if (action == GTK_FILE_CHOOSER_ACTION_SAVE) confirm_text = GTK_STOCK_SAVE; @@ -111,7 +112,7 @@ class FileChooserDialog { base::FilePath GetFileName() const { gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog_)); - base::FilePath path(filename); + base::FilePath path = AddExtensionForFilename(filename); g_free(filename); return path; } @@ -121,7 +122,8 @@ class FileChooserDialog { GSList* filenames = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog_)); for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) { - base::FilePath path(static_cast(iter->data)); + base::FilePath path = AddExtensionForFilename( + static_cast(iter->data)); g_free(iter->data); paths.push_back(path); } @@ -135,11 +137,13 @@ class FileChooserDialog { private: void AddFilters(const Filters& filters); + base::FilePath AddExtensionForFilename(const gchar* filename) const; atom::NativeWindow::DialogScope dialog_scope_; GtkWidget* dialog_; + Filters filters_; SaveDialogCallback save_callback_; OpenDialogCallback open_callback_; @@ -184,6 +188,30 @@ void FileChooserDialog::AddFilters(const Filters& filters) { } } +base::FilePath FileChooserDialog::AddExtensionForFilename( + const gchar* filename) const { + base::FilePath path(filename); + GtkFileFilter* selected_filter = + gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog_)); + if (!selected_filter) + return path; + + GSList* filters = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog_)); + int i = g_slist_index(filters, selected_filter); + g_slist_free(filters); + if (i >= filters_.size()) + return path; + + const auto& extensions = filters_[i].second; + for (const auto& extension : extensions) { + if (extension == "*" || path.MatchesExtension("." + extension)) + return path; + } + + return path.ReplaceExtension(extensions[0]); +} + + } // namespace bool ShowOpenDialog(atom::NativeWindow* parent_window, diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index f9e9718ebf5..d553a6a7dfb 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -8,7 +8,9 @@ #include "atom/browser/native_window.h" #include "base/callback.h" +#include "base/mac/mac_util.h" #include "base/strings/sys_string_conversions.h" +#include "skia/ext/skia_utils_mac.h" @interface ModalDelegate : NSObject { @private @@ -57,7 +59,8 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, int default_id, const std::string& title, const std::string& message, - const std::string& detail) { + const std::string& detail, + const gfx::ImageSkia& icon) { // Ignore the title; it's the window title on other platforms and ignorable. NSAlert* alert = [[NSAlert alloc] init]; [alert setMessageText:base::SysUTF8ToNSString(message)]; @@ -92,6 +95,12 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window, [[ns_buttons objectAtIndex:default_id] setKeyEquivalent:@"\r"]; } + if (!icon.isNull()) { + NSImage* image = skia::SkBitmapToNSImageWithColorSpace( + *icon.bitmap(), base::mac::GetGenericRGBColorSpace()); + [alert setIcon:image]; + } + return alert; } @@ -113,7 +122,7 @@ int ShowMessageBox(NativeWindow* parent_window, const gfx::ImageSkia& icon) { NSAlert* alert = CreateNSAlert( parent_window, type, buttons, default_id, title, message, - detail); + detail, icon); // Use runModal for synchronous alert without parent, since we don't have a // window to wait for. @@ -149,7 +158,7 @@ void ShowMessageBox(NativeWindow* parent_window, const MessageBoxCallback& callback) { NSAlert* alert = CreateNSAlert( parent_window, type, buttons, default_id, title, message, - detail); + detail, icon); ModalDelegate* delegate = [[ModalDelegate alloc] initWithCallback:callback andAlert:alert callEndModal:false]; diff --git a/atom/browser/ui/message_box_win.cc b/atom/browser/ui/message_box_win.cc index 2847ae21a16..5f49151c30f 100644 --- a/atom/browser/ui/message_box_win.cc +++ b/atom/browser/ui/message_box_win.cc @@ -102,9 +102,9 @@ int ShowMessageBoxUTF16(HWND parent, base::win::ScopedHICON hicon; if (!icon.isNull()) { - hicon.Set(IconUtil::CreateHICONFromSkBitmap(*icon.bitmap())); + hicon = IconUtil::CreateHICONFromSkBitmap(*icon.bitmap()); config.dwFlags |= TDF_USE_HICON_MAIN; - config.hMainIcon = hicon.Get(); + config.hMainIcon = hicon.get(); } else { // Show icon according to dialog's type. switch (type) { diff --git a/atom/browser/ui/views/global_menu_bar_x11.cc b/atom/browser/ui/views/global_menu_bar_x11.cc index 26279ecbe8e..b393e3f4657 100644 --- a/atom/browser/ui/views/global_menu_bar_x11.cc +++ b/atom/browser/ui/views/global_menu_bar_x11.cc @@ -210,6 +210,14 @@ void GlobalMenuBarX11::InitServer(gfx::AcceleratedWidget xid) { server_ = server_new(path.c_str()); } +void GlobalMenuBarX11::OnWindowMapped() { + GlobalMenuBarRegistrarX11::GetInstance()->OnWindowMapped(xid_); +} + +void GlobalMenuBarX11::OnWindowUnmapped() { + GlobalMenuBarRegistrarX11::GetInstance()->OnWindowUnmapped(xid_); +} + void GlobalMenuBarX11::BuildMenuFromModel(ui::MenuModel* model, DbusmenuMenuitem* parent) { for (int i = 0; i < model->GetItemCount(); ++i) { diff --git a/atom/browser/ui/views/global_menu_bar_x11.h b/atom/browser/ui/views/global_menu_bar_x11.h index 51147a26f92..89b2680cabe 100644 --- a/atom/browser/ui/views/global_menu_bar_x11.h +++ b/atom/browser/ui/views/global_menu_bar_x11.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "ui/base/glib/glib_signal.h" #include "ui/gfx/native_widget_types.h" @@ -40,12 +40,16 @@ class GlobalMenuBarX11 { explicit GlobalMenuBarX11(NativeWindowViews* window); virtual ~GlobalMenuBarX11(); - // Creates the object path for DbusemenuServer which is attached to |xid|. + // Creates the object path for DbusmenuServer which is attached to |xid|. static std::string GetPathForWindow(gfx::AcceleratedWidget xid); void SetMenu(ui::MenuModel* menu_model); bool IsServerStarted() const; + // Called by NativeWindow when it show/hides. + void OnWindowMapped(); + void OnWindowUnmapped(); + private: // Creates a DbusmenuServer. void InitServer(gfx::AcceleratedWidget xid); diff --git a/atom/browser/ui/views/submenu_button.cc b/atom/browser/ui/views/submenu_button.cc index bedbb98832d..72cab258cbe 100644 --- a/atom/browser/ui/views/submenu_button.cc +++ b/atom/browser/ui/views/submenu_button.cc @@ -15,7 +15,7 @@ namespace atom { namespace { // Filter out the "&" in menu label. -base::string16 FilterAccecelator(const base::string16& label) { +base::string16 FilterAccelerator(const base::string16& label) { base::string16 out; base::RemoveChars(label, base::ASCIIToUTF16("&").c_str(), &out); return out; @@ -26,7 +26,7 @@ base::string16 FilterAccecelator(const base::string16& label) { SubmenuButton::SubmenuButton(views::ButtonListener* listener, const base::string16& title, views::MenuButtonListener* menu_button_listener) - : views::MenuButton(listener, FilterAccecelator(title), + : views::MenuButton(listener, FilterAccelerator(title), menu_button_listener, false), accelerator_(0), show_underline_(false), @@ -37,7 +37,7 @@ SubmenuButton::SubmenuButton(views::ButtonListener* listener, underline_color_(SK_ColorBLACK) { #if defined(OS_LINUX) // Dont' use native style border. - SetBorder(CreateDefaultBorder().Pass()); + SetBorder(std::move(CreateDefaultBorder())); #endif if (GetUnderlinePosition(title, &accelerator_, &underline_start_, diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index 1ac29f1360c..11247301380 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -80,7 +80,7 @@ void NotifyIcon::ResetIcon() { InitIconData(&icon_data); icon_data.uFlags |= NIF_MESSAGE; icon_data.uCallbackMessage = message_id_; - icon_data.hIcon = icon_.Get(); + icon_data.hIcon = icon_.get(); // If we have an image, then set the NIF_ICON flag, which tells // Shell_NotifyIcon() to set the image for the status icon it creates. if (icon_data.hIcon) @@ -96,8 +96,8 @@ void NotifyIcon::SetImage(const gfx::Image& image) { NOTIFYICONDATA icon_data; InitIconData(&icon_data); icon_data.uFlags |= NIF_ICON; - icon_.Set(IconUtil::CreateHICONFromSkBitmap(image.AsBitmap())); - icon_data.hIcon = icon_.Get(); + icon_ = IconUtil::CreateHICONFromSkBitmap(image.AsBitmap()); + icon_data.hIcon = icon_.get(); BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); if (!result) LOG(WARNING) << "Error setting status tray icon image"; @@ -132,8 +132,8 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, base::win::Version win_version = base::win::GetVersion(); if (!icon.IsEmpty() && win_version != base::win::VERSION_PRE_XP) { - balloon_icon_.Set(IconUtil::CreateHICONFromSkBitmap(icon.AsBitmap())); - icon_data.hBalloonIcon = balloon_icon_.Get(); + balloon_icon_ = IconUtil::CreateHICONFromSkBitmap(icon.AsBitmap()); + icon_data.hBalloonIcon = balloon_icon_.get(); icon_data.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON; } diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index 23608c7c7ab..53ed49b937c 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -11,7 +11,7 @@ #include #include "atom/browser/ui/tray_icon.h" -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "base/win/scoped_gdi_object.h" diff --git a/atom/browser/ui/win/notify_icon_host.cc b/atom/browser/ui/win/notify_icon_host.cc index a0d4287ff61..1f0b35b09fd 100644 --- a/atom/browser/ui/win/notify_icon_host.cc +++ b/atom/browser/ui/win/notify_icon_host.cc @@ -15,6 +15,7 @@ #include "base/win/win_util.h" #include "base/win/wrapped_window_proc.h" #include "ui/events/event_constants.h" +#include "ui/events/win/system_event_state_lookup.h" #include "ui/gfx/win/hwnd_util.h" namespace atom { @@ -35,11 +36,11 @@ bool IsWinPressed() { int GetKeyboardModifers() { int modifiers = ui::EF_NONE; - if (base::win::IsShiftPressed()) + if (ui::win::IsShiftPressed()) modifiers |= ui::EF_SHIFT_DOWN; - if (base::win::IsCtrlPressed()) + if (ui::win::IsCtrlPressed()) modifiers |= ui::EF_CONTROL_DOWN; - if (base::win::IsAltPressed()) + if (ui::win::IsAltPressed()) modifiers |= ui::EF_ALT_DOWN; if (IsWinPressed()) modifiers |= ui::EF_COMMAND_DOWN; diff --git a/atom/browser/ui/win/taskbar_host.cc b/atom/browser/ui/win/taskbar_host.cc index 0d250829110..f7841cfa856 100644 --- a/atom/browser/ui/win/taskbar_host.cc +++ b/atom/browser/ui/win/taskbar_host.cc @@ -91,7 +91,7 @@ bool TaskbarHost::SetThumbarButtons( if (!button.icon.IsEmpty()) { thumb_button.dwMask |= THB_ICON; icons[i] = IconUtil::CreateHICONFromSkBitmap(button.icon.AsBitmap()); - thumb_button.hIcon = icons[i].Get(); + thumb_button.hIcon = icons[i].get(); } // Set tooltip. @@ -138,8 +138,8 @@ bool TaskbarHost::SetOverlayIcon( base::win::ScopedHICON icon( IconUtil::CreateHICONFromSkBitmap(overlay.AsBitmap())); - return SUCCEEDED( - taskbar_->SetOverlayIcon(window, icon, base::UTF8ToUTF16(text).c_str())); + return SUCCEEDED(taskbar_->SetOverlayIcon( + window, icon.get(), base::UTF8ToUTF16(text).c_str())); } bool TaskbarHost::HandleThumbarButtonEvent(int button_id) { diff --git a/atom/browser/web_contents_permission_helper.cc b/atom/browser/web_contents_permission_helper.cc new file mode 100644 index 00000000000..d018e1d09db --- /dev/null +++ b/atom/browser/web_contents_permission_helper.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2016 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_permission_helper.h" + +#include + +#include "atom/browser/atom_permission_manager.h" +#include "brightray/browser/media/media_stream_devices_controller.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/render_process_host.h" + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPermissionHelper); + +namespace atom { + +namespace { + +void MediaAccessAllowed( + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback, + bool allowed) { + brightray::MediaStreamDevicesController controller(request, callback); + if (allowed) + controller.TakeAction(); + else + controller.Deny(content::MEDIA_DEVICE_PERMISSION_DENIED); +} + +void OnPointerLockResponse(content::WebContents* web_contents, bool allowed) { + if (web_contents) + web_contents->GotResponseToLockMouseRequest(allowed); +} + +void OnPermissionResponse(const base::Callback& callback, + content::PermissionStatus status) { + if (status == content::PERMISSION_STATUS_GRANTED) + callback.Run(true); + else + callback.Run(false); +} + +} // namespace + +WebContentsPermissionHelper::WebContentsPermissionHelper( + content::WebContents* web_contents) + : web_contents_(web_contents) { +} + +WebContentsPermissionHelper::~WebContentsPermissionHelper() { +} + +void WebContentsPermissionHelper::RequestPermission( + content::PermissionType permission, + const base::Callback& callback, + bool user_gesture) { + auto rfh = web_contents_->GetMainFrame(); + auto permission_manager = static_cast( + web_contents_->GetBrowserContext()->GetPermissionManager()); + auto origin = web_contents_->GetLastCommittedURL(); + permission_manager->RequestPermission( + permission, rfh, origin, user_gesture, + base::Bind(&OnPermissionResponse, callback)); +} + +void WebContentsPermissionHelper::RequestFullscreenPermission( + const base::Callback& callback) { + RequestPermission((content::PermissionType)(PermissionType::FULLSCREEN), + callback); +} + +void WebContentsPermissionHelper::RequestMediaAccessPermission( + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& response_callback) { + auto callback = base::Bind(&MediaAccessAllowed, request, response_callback); + // The permission type doesn't matter here, AUDIO_CAPTURE/VIDEO_CAPTURE + // are presented as same type in content_converter.h. + RequestPermission(content::PermissionType::AUDIO_CAPTURE, callback); +} + +void WebContentsPermissionHelper::RequestWebNotificationPermission( + const base::Callback& callback) { + RequestPermission(content::PermissionType::NOTIFICATIONS, callback); +} + +void WebContentsPermissionHelper::RequestPointerLockPermission( + bool user_gesture) { + RequestPermission((content::PermissionType)(PermissionType::POINTER_LOCK), + base::Bind(&OnPointerLockResponse, web_contents_), + user_gesture); +} + +} // namespace atom diff --git a/atom/browser/web_contents_permission_helper.h b/atom/browser/web_contents_permission_helper.h new file mode 100644 index 00000000000..90ae6dff56f --- /dev/null +++ b/atom/browser/web_contents_permission_helper.h @@ -0,0 +1,50 @@ +// Copyright (c) 2016 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_PERMISSION_HELPER_H_ +#define ATOM_BROWSER_WEB_CONTENTS_PERMISSION_HELPER_H_ + +#include "content/public/browser/permission_type.h" +#include "content/public/browser/web_contents_user_data.h" +#include "content/public/common/media_stream_request.h" + +namespace atom { + +// Applies the permission requested for WebContents. +class WebContentsPermissionHelper + : public content::WebContentsUserData { + public: + ~WebContentsPermissionHelper() override; + + enum class PermissionType { + POINTER_LOCK = static_cast(content::PermissionType::NUM) + 1, + FULLSCREEN + }; + + void RequestFullscreenPermission( + const base::Callback& callback); + void RequestMediaAccessPermission( + const content::MediaStreamRequest& request, + const content::MediaResponseCallback& callback); + void RequestWebNotificationPermission( + const base::Callback& callback); + void RequestPointerLockPermission(bool user_gesture); + + private: + explicit WebContentsPermissionHelper(content::WebContents* web_contents); + friend class content::WebContentsUserData; + + void RequestPermission( + content::PermissionType permission, + const base::Callback& callback, + bool user_gesture = false); + + content::WebContents* web_contents_; + + DISALLOW_COPY_AND_ASSIGN(WebContentsPermissionHelper); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_WEB_CONTENTS_PERMISSION_HELPER_H_ diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index fb67d2516eb..bcdbd736676 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -4,12 +4,15 @@ #include "atom/browser/web_contents_preferences.h" +#include #include +#include #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/options_switches.h" #include "base/command_line.h" #include "base/strings/string_number_conversions.h" +#include "content/public/browser/render_process_host.h" #include "content/public/common/content_switches.h" #include "content/public/common/web_preferences.h" #include "native_mate/dictionary.h" @@ -23,9 +26,13 @@ DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPreferences); namespace atom { +// static +std::vector WebContentsPreferences::instances_; + WebContentsPreferences::WebContentsPreferences( content::WebContents* web_contents, - const mate::Dictionary& web_preferences) { + const mate::Dictionary& web_preferences) + : web_contents_(web_contents) { v8::Isolate* isolate = web_preferences.isolate(); mate::Dictionary copied(isolate, web_preferences.GetHandle()->Clone()); // Following fields should not be stored. @@ -35,15 +42,31 @@ WebContentsPreferences::WebContentsPreferences( mate::ConvertFromV8(isolate, copied.GetHandle(), &web_preferences_); web_contents->SetUserData(UserDataKey(), this); + + instances_.push_back(this); } WebContentsPreferences::~WebContentsPreferences() { + instances_.erase( + std::remove(instances_.begin(), instances_.end(), this), + instances_.end()); } void WebContentsPreferences::Merge(const base::DictionaryValue& extend) { web_preferences_.MergeDictionary(&extend); } +// static +content::WebContents* WebContentsPreferences::GetWebContentsFromProcessID( + int process_id) { + for (WebContentsPreferences* preferences : instances_) { + content::WebContents* web_contents = preferences->web_contents_; + if (web_contents->GetRenderProcessHost()->GetID() == process_id) + return web_contents; + } + return nullptr; +} + // static void WebContentsPreferences::AppendExtraCommandLineSwitches( content::WebContents* web_contents, base::CommandLine* command_line) { @@ -74,11 +97,6 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( // Check if we have node integration specified. bool node_integration = true; web_preferences.GetBoolean(options::kNodeIntegration, &node_integration); - // Be compatible with old API of "node-integration" option. - std::string old_token; - if (web_preferences.GetString(options::kNodeIntegration, &old_token) && - old_token != "disable") - node_integration = true; command_line->AppendSwitchASCII(switches::kNodeIntegration, node_integration ? "true" : "false"); diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index 8b04f9ee24e..dd98a9658ac 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -5,6 +5,8 @@ #ifndef ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ #define ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ +#include + #include "base/values.h" #include "content/public/browser/web_contents_user_data.h" @@ -26,6 +28,9 @@ namespace atom { class WebContentsPreferences : public content::WebContentsUserData { public: + // Get WebContents according to process ID. + static content::WebContents* GetWebContentsFromProcessID(int process_id); + // Append command paramters according to |web_contents|'s preferences. static void AppendExtraCommandLineSwitches( content::WebContents* web_contents, base::CommandLine* command_line); @@ -47,6 +52,9 @@ class WebContentsPreferences private: friend class content::WebContentsUserData; + static std::vector instances_; + + content::WebContents* web_contents_; base::DictionaryValue web_preferences_; DISALLOW_COPY_AND_ASSIGN(WebContentsPreferences); diff --git a/atom/browser/web_view_guest_delegate.cc b/atom/browser/web_view_guest_delegate.cc index 8e1810c4a39..6abb9713bfa 100644 --- a/atom/browser/web_view_guest_delegate.cc +++ b/atom/browser/web_view_guest_delegate.cc @@ -9,6 +9,7 @@ #include "content/public/browser/guest_host.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" namespace atom { @@ -21,8 +22,7 @@ const int kDefaultHeight = 300; } // namespace WebViewGuestDelegate::WebViewGuestDelegate() - : guest_opaque_(true), - guest_host_(nullptr), + : guest_host_(nullptr), auto_size_enabled_(false), is_full_page_plugin_(false), api_web_contents_(nullptr) { @@ -95,22 +95,6 @@ void WebViewGuestDelegate::SetSize(const SetSizeParams& params) { auto_size_enabled_ = enable_auto_size; } -void WebViewGuestDelegate::SetAllowTransparency(bool allow) { - if (guest_opaque_ != allow) - return; - - auto render_view_host = web_contents()->GetRenderViewHost(); - guest_opaque_ = !allow; - if (!render_view_host->GetView()) - return; - - if (guest_opaque_) { - render_view_host->GetView()->SetBackgroundColorToDefault(); - } else { - render_view_host->GetView()->SetBackgroundColor(SK_ColorTRANSPARENT); - } -} - void WebViewGuestDelegate::HandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) { @@ -118,18 +102,6 @@ void WebViewGuestDelegate::HandleKeyboardEvent( embedder_web_contents_->GetDelegate()->HandleKeyboardEvent(source, event); } -void WebViewGuestDelegate::RenderViewReady() { - // We don't want to accidentally set the opacity of an interstitial page. - // WebContents::GetRenderWidgetHostView will return the RWHV of an - // interstitial page if one is showing at this time. We only want opacity - // to apply to web pages. - auto render_view_host_view = web_contents()->GetRenderViewHost()->GetView(); - if (guest_opaque_) - render_view_host_view->SetBackgroundColorToDefault(); - else - render_view_host_view->SetBackgroundColor(SK_ColorTRANSPARENT); -} - void WebViewGuestDelegate::DidCommitProvisionalLoadForFrame( content::RenderFrameHost* render_frame_host, const GURL& url, ui::PageTransition transition_type) { diff --git a/atom/browser/web_view_guest_delegate.h b/atom/browser/web_view_guest_delegate.h index 65e0bcde191..95888ff749f 100644 --- a/atom/browser/web_view_guest_delegate.h +++ b/atom/browser/web_view_guest_delegate.h @@ -49,16 +49,12 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, // and normal sizes. void SetSize(const SetSizeParams& params); - // Sets the transparency of the guest. - void SetAllowTransparency(bool allow); - // Transfer the keyboard event to embedder. void HandleKeyboardEvent(content::WebContents* source, const content::NativeWebKeyboardEvent& event); protected: // content::WebContentsObserver: - void RenderViewReady() override; void DidCommitProvisionalLoadForFrame( content::RenderFrameHost* render_frame_host, const GURL& url, ui::PageTransition transition_type) override; @@ -85,9 +81,6 @@ class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, // Returns the default size of the guestview. gfx::Size GetDefaultSize() const; - // Stores whether the contents of the guest can be transparent. - bool guest_opaque_; - // The WebContents that attaches this guest view. content::WebContents* embedder_web_contents_; diff --git a/atom/browser/window_list.h b/atom/browser/window_list.h index bfb9a2b0aec..3dd87b2c34c 100644 --- a/atom/browser/window_list.h +++ b/atom/browser/window_list.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/lazy_instance.h" #include "base/observer_list.h" diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index 7aee71fc329..4bfb0ed4c1b 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -27,12 +27,12 @@ class Archive : public mate::Wrappable { scoped_ptr archive(new asar::Archive(path)); if (!archive->Init()) return v8::False(isolate); - return (new Archive(archive.Pass()))->GetWrapper(isolate); + return (new Archive(std::move(archive)))->GetWrapper(isolate); } protected: explicit Archive(scoped_ptr archive) - : archive_(archive.Pass()) {} + : archive_(std::move(archive)) {} // Reads the offset and size of file. v8::Local GetFileInfo(v8::Isolate* isolate, @@ -129,9 +129,11 @@ void InitAsarSupport(v8::Isolate* isolate, v8::Local process, v8::Local require) { // Evaluate asar_init.coffee. + const char* asar_init_native = reinterpret_cast( + static_cast(node::asar_init_native)); v8::Local asar_init = v8::Script::Compile(v8::String::NewFromUtf8( isolate, - node::asar_init_native, + asar_init_native, v8::String::kNormalString, sizeof(node::asar_init_native) -1)); v8::Local result = asar_init->Run(); @@ -141,9 +143,11 @@ void InitAsarSupport(v8::Isolate* isolate, v8::Local, std::string)> init; if (mate::ConvertFromV8(isolate, result, &init)) { + const char* asar_native = reinterpret_cast( + static_cast(node::asar_native)); init.Run(process, require, - std::string(node::asar_native, sizeof(node::asar_native) - 1)); + std::string(asar_native, sizeof(node::asar_native) - 1)); } } diff --git a/atom/common/api/atom_api_clipboard.cc b/atom/common/api/atom_api_clipboard.cc index 0837a3dc93f..1f75f2cd3fe 100644 --- a/atom/common/api/atom_api_clipboard.cc +++ b/atom/common/api/atom_api_clipboard.cc @@ -60,6 +60,11 @@ void Write(const mate::Dictionary& data, if (data.Get("text", &text)) writer.WriteText(text); + if (data.Get("rtf", &text)) { + std::string rtf = base::UTF16ToUTF8(text); + writer.WriteRTF(rtf); + } + if (data.Get("html", &html)) writer.WriteHTML(html, std::string()); @@ -88,12 +93,24 @@ void WriteText(const base::string16& text, mate::Arguments* args) { writer.WriteText(text); } +base::string16 ReadRtf(mate::Arguments* args) { + std::string data; + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + clipboard->ReadRTF(GetClipboardType(args), &data); + return base::UTF8ToUTF16(data); +} + +void WriteRtf(const std::string& text, mate::Arguments* args) { + ui::ScopedClipboardWriter writer(GetClipboardType(args)); + writer.WriteRTF(text); +} + base::string16 ReadHtml(mate::Arguments* args) { base::string16 data; base::string16 html; std::string url; - uint32 start; - uint32 end; + uint32_t start; + uint32_t end; ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); clipboard->ReadHTML(GetClipboardType(args), &html, &url, &start, &end); data = html.substr(start, end - start); @@ -129,6 +146,8 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("write", &Write); dict.SetMethod("readText", &ReadText); dict.SetMethod("writeText", &WriteText); + dict.SetMethod("readRtf", &ReadRtf); + dict.SetMethod("writeRtf", &WriteRtf); dict.SetMethod("readHtml", &ReadHtml); dict.SetMethod("writeHtml", &WriteHtml); dict.SetMethod("readImage", &ReadImage); diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index a810069e71b..3dda326f59f 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -13,6 +13,7 @@ #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/node_includes.h" #include "base/base64.h" +#include "base/files/file_util.h" #include "base/strings/string_util.h" #include "base/strings/pattern.h" #include "native_mate/dictionary.h" @@ -119,6 +120,20 @@ bool PopulateImageSkiaRepsFromPath(gfx::ImageSkia* image, return succeed; } +base::FilePath NormalizePath(const base::FilePath& path) { + if (!path.ReferencesParent()) { + return path; + } + + base::FilePath absolute_path = MakeAbsoluteFilePath(path); + // MakeAbsoluteFilePath returns an empty path on failures so use original path + if (absolute_path.empty()) { + return path; + } else { + return absolute_path; + } +} + #if defined(OS_MACOSX) bool IsTemplateFilename(const base::FilePath& path) { return (base::MatchPattern(path.value(), "*Template.*") || @@ -143,11 +158,11 @@ bool ReadImageSkiaFromICO(gfx::ImageSkia* image, const base::FilePath& path) { base::win::ScopedHICON icon(static_cast( LoadImage(NULL, image_path.value().c_str(), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE))); - if (!icon) + if (!icon.get()) return false; // Convert the icon from the Windows specific HICON to gfx::ImageSkia. - scoped_ptr bitmap(IconUtil::CreateSkBitmapFromHICON(icon)); + scoped_ptr bitmap(IconUtil:: CreateSkBitmapFromHICON(icon.get())); image->AddRepresentation(gfx::ImageSkiaRep(*bitmap, 1.0f)); return true; } @@ -169,6 +184,7 @@ mate::ObjectTemplateBuilder NativeImage::GetObjectTemplateBuilder( template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate) .SetMethod("toPng", &NativeImage::ToPNG) .SetMethod("toJpeg", &NativeImage::ToJPEG) + .SetMethod("getNativeHandle", &NativeImage::GetNativeHandle) .SetMethod("toDataURL", &NativeImage::ToDataURL) .SetMethod("toDataUrl", &NativeImage::ToDataURL) // deprecated. .SetMethod("isEmpty", &NativeImage::IsEmpty) @@ -206,6 +222,20 @@ std::string NativeImage::ToDataURL() { return data_url; } +v8::Local NativeImage::GetNativeHandle(v8::Isolate* isolate, + mate::Arguments* args) { +#if defined(OS_MACOSX) + NSImage* ptr = image_.AsNSImage(); + return node::Buffer::Copy( + isolate, + reinterpret_cast(ptr), + sizeof(void*)).ToLocalChecked(); +#else + args->ThrowError("Not implemented"); + return v8::Undefined(isolate); +#endif +} + bool NativeImage::IsEmpty() { return image_.IsEmpty(); } @@ -254,17 +284,19 @@ mate::Handle NativeImage::CreateFromJPEG( mate::Handle NativeImage::CreateFromPath( v8::Isolate* isolate, const base::FilePath& path) { gfx::ImageSkia image_skia; - if (path.MatchesExtension(FILE_PATH_LITERAL(".ico"))) { + base::FilePath image_path = NormalizePath(path); + + if (image_path.MatchesExtension(FILE_PATH_LITERAL(".ico"))) { #if defined(OS_WIN) - ReadImageSkiaFromICO(&image_skia, path); + ReadImageSkiaFromICO(&image_skia, image_path); #endif } else { - PopulateImageSkiaRepsFromPath(&image_skia, path); + PopulateImageSkiaRepsFromPath(&image_skia, image_path); } gfx::Image image(image_skia); mate::Handle handle = Create(isolate, image); #if defined(OS_MACOSX) - if (IsTemplateFilename(path)) + if (IsTemplateFilename(image_path)) handle->SetTemplateImage(true); #endif return handle; diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index 1f0fe946ba5..145f5ff1dcd 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -61,6 +61,9 @@ class NativeImage : public mate::Wrappable { private: v8::Local ToPNG(v8::Isolate* isolate); v8::Local ToJPEG(v8::Isolate* isolate, int quality); + v8::Local GetNativeHandle( + v8::Isolate* isolate, + mate::Arguments* args); std::string ToDataURL(); bool IsEmpty(); gfx::Size GetSize(); diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index a4599ee0c35..f99e2ba1854 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -12,12 +12,23 @@ namespace { +bool OpenExternal(const GURL& url, mate::Arguments* args) { + bool activate = true; + if (args->Length() == 2) { + mate::Dictionary options; + if (args->GetNext(&options)) { + options.Get("activate", &activate); + } + } + return platform_util::OpenExternal(url, activate); +} + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("showItemInFolder", &platform_util::ShowItemInFolder); dict.SetMethod("openItem", &platform_util::OpenItem); - dict.SetMethod("openExternal", &platform_util::OpenExternal); + dict.SetMethod("openExternal", &OpenExternal); dict.SetMethod("moveItemToTrash", &platform_util::MoveItemToTrash); dict.SetMethod("beep", &platform_util::Beep); } diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index c86335adb15..0ebd939398f 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -2,63 +2,49 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include #include #include "atom/common/api/object_life_monitor.h" #include "atom/common/node_includes.h" -#include "base/stl_util.h" #include "native_mate/dictionary.h" #include "v8/include/v8-profiler.h" namespace { -// A Persistent that can be copied and will not free itself. -template -struct LeakedPersistentTraits { - typedef v8::Persistent > LeakedPersistent; - static const bool kResetInDestructor = false; - template - static V8_INLINE void Copy(const v8::Persistent& source, - LeakedPersistent* dest) { - // do nothing, just allow copy - } -}; - -// The handles are leaked on purpose. -using FunctionTemplateHandle = - LeakedPersistentTraits::LeakedPersistent; -std::map function_templates_; - -v8::Local CreateObjectWithName(v8::Isolate* isolate, - const std::string& name) { - if (name == "Object") - return v8::Object::New(isolate); - - if (ContainsKey(function_templates_, name)) - return v8::Local::New( - isolate, function_templates_[name])->GetFunction()->NewInstance(); - - v8::Local t = v8::FunctionTemplate::New(isolate); - t->SetClassName(mate::StringToV8(isolate, name)); - function_templates_[name] = FunctionTemplateHandle(isolate, t); - return t->GetFunction()->NewInstance(); -} - -v8::Local GetHiddenValue(v8::Local object, +v8::Local GetHiddenValue(v8::Isolate* isolate, + v8::Local object, v8::Local key) { - return object->GetHiddenValue(key); + v8::Local context = isolate->GetCurrentContext(); + v8::Local privateKey = v8::Private::ForApi(isolate, key); + v8::Local value; + v8::Maybe result = object->HasPrivate(context, privateKey); + if (!(result.IsJust() && result.FromJust())) + return v8::Local(); + if (object->GetPrivate(context, privateKey).ToLocal(&value)) + return value; + return v8::Local(); } -void SetHiddenValue(v8::Local object, +void SetHiddenValue(v8::Isolate* isolate, + v8::Local object, v8::Local key, v8::Local value) { - object->SetHiddenValue(key, value); + if (value.IsEmpty()) + return; + v8::Local context = isolate->GetCurrentContext(); + v8::Local privateKey = v8::Private::ForApi(isolate, key); + object->SetPrivate(context, privateKey, value); } -void DeleteHiddenValue(v8::Local object, +void DeleteHiddenValue(v8::Isolate* isolate, + v8::Local object, v8::Local key) { - object->DeleteHiddenValue(key); + v8::Local context = isolate->GetCurrentContext(); + v8::Local privateKey = v8::Private::ForApi(isolate, key); + // Actually deleting the value would make force the object into + // dictionary mode which is unnecessarily slow. Instead, we replace + // the hidden value with "undefined". + object->SetPrivate(context, privateKey, v8::Undefined(isolate)); } int32_t GetObjectHash(v8::Local object) { @@ -78,7 +64,6 @@ void TakeHeapSnapshot(v8::Isolate* isolate) { void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); - dict.SetMethod("createObjectWithName", &CreateObjectWithName); dict.SetMethod("getHiddenValue", &GetHiddenValue); dict.SetMethod("setHiddenValue", &SetHiddenValue); dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue); diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index a000f6fc743..fe53d8793f2 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -98,24 +98,8 @@ void AtomBindings::OnCallNextTick(uv_async_t* handle) { self->pending_next_ticks_.begin(); it != self->pending_next_ticks_.end(); ++it) { node::Environment* env = *it; - node::Environment::TickInfo* tick_info = env->tick_info(); - - v8::Context::Scope context_scope(env->context()); - if (tick_info->in_tick()) - continue; - - if (tick_info->length() == 0) { - env->isolate()->RunMicrotasks(); - } - - if (tick_info->length() == 0) { - tick_info->set_index(0); - continue; - } - - tick_info->set_in_tick(true); - env->tick_callback_function()->Call(env->process_object(), 0, NULL); - tick_info->set_in_tick(false); + node::Environment::AsyncCallbackScope callback_scope(env); + env->KickNextTick(&callback_scope); } self->pending_next_ticks_.clear(); diff --git a/atom/common/api/atom_bindings.h b/atom/common/api/atom_bindings.h index b3536c23403..9460145d239 100644 --- a/atom/common/api/atom_bindings.h +++ b/atom/common/api/atom_bindings.h @@ -7,6 +7,7 @@ #include +#include "base/macros.h" #include "base/strings/string16.h" #include "v8/include/v8.h" #include "vendor/node/deps/uv/include/uv.h" diff --git a/atom/common/api/lib/callbacks-registry.js b/atom/common/api/lib/callbacks-registry.js deleted file mode 100644 index 5b528aa3a6c..00000000000 --- a/atom/common/api/lib/callbacks-registry.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -var slice = [].slice; - -const v8Util = process.atomBinding('v8_util'); - -class CallbacksRegistry { - constructor() { - this.nextId = 0; - this.callbacks = {}; - } - - add(callback) { - // The callback is already added. - var filenameAndLine, id, location, match, ref, regexp, stackString; - id = v8Util.getHiddenValue(callback, 'callbackId'); - if (id != null) { - return id; - } - id = ++this.nextId; - - // Capture the location of the function and put it in the ID string, - // so that release errors can be tracked down easily. - regexp = /at (.*)/gi; - stackString = (new Error).stack; - while ((match = regexp.exec(stackString)) !== null) { - location = match[1]; - if (location.indexOf('(native)') !== -1) { - continue; - } - if (location.indexOf('atom.asar') !== -1) { - continue; - } - ref = /([^\/^\)]*)\)?$/gi.exec(location); - filenameAndLine = ref[1]; - break; - } - this.callbacks[id] = callback; - v8Util.setHiddenValue(callback, 'callbackId', id); - v8Util.setHiddenValue(callback, 'location', filenameAndLine); - return id; - } - - get(id) { - var ref; - return (ref = this.callbacks[id]) != null ? ref : function() {}; - } - - call() { - var args, id, ref; - id = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - return (ref = this.get(id)).call.apply(ref, [global].concat(slice.call(args))); - } - - apply() { - var args, id, ref; - id = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - return (ref = this.get(id)).apply.apply(ref, [global].concat(slice.call(args))); - } - - remove(id) { - return delete this.callbacks[id]; - } -} - -module.exports = CallbacksRegistry; diff --git a/atom/common/api/lib/crash-reporter.js b/atom/common/api/lib/crash-reporter.js deleted file mode 100644 index e3a327118fd..00000000000 --- a/atom/common/api/lib/crash-reporter.js +++ /dev/null @@ -1,94 +0,0 @@ -const os = require('os'); -const path = require('path'); -const spawn = require('child_process').spawn; -const electron = require('electron'); -const binding = process.atomBinding('crash_reporter'); - -var CrashReporter = (function() { - function CrashReporter() {} - - CrashReporter.prototype.start = function(options) { - var app, args, autoSubmit, companyName, deprecate, env, extra, ignoreSystemCrashHandler, start, submitURL; - if (options == null) { - options = {}; - } - this.productName = options.productName, companyName = options.companyName, submitURL = options.submitURL, autoSubmit = options.autoSubmit, ignoreSystemCrashHandler = options.ignoreSystemCrashHandler, extra = options.extra; - - // Deprecated. - deprecate = electron.deprecate; - if (options.submitUrl) { - if (submitURL == null) { - submitURL = options.submitUrl; - } - deprecate.warn('submitUrl', 'submitURL'); - } - app = (process.type === 'browser' ? electron : electron.remote).app; - if (this.productName == null) { - this.productName = app.getName(); - } - if (autoSubmit == null) { - autoSubmit = true; - } - if (ignoreSystemCrashHandler == null) { - ignoreSystemCrashHandler = false; - } - if (extra == null) { - extra = {}; - } - if (extra._productName == null) { - extra._productName = this.productName; - } - if (extra._companyName == null) { - extra._companyName = companyName; - } - if (extra._version == null) { - extra._version = app.getVersion(); - } - if (companyName == null) { - deprecate.log('companyName is now a required option to crashReporter.start'); - return; - } - if (submitURL == null) { - deprecate.log('submitURL is now a required option to crashReporter.start'); - return; - } - start = (function(_this) { - return function() { - return binding.start(_this.productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra); - }; - })(this); - if (process.platform === 'win32') { - args = ["--reporter-url=" + submitURL, "--application-name=" + this.productName, "--v=1"]; - env = { - ATOM_SHELL_INTERNAL_CRASH_SERVICE: 1 - }; - spawn(process.execPath, args, { - env: env, - detached: true - }); - } - return start(); - }; - - CrashReporter.prototype.getLastCrashReport = function() { - var reports; - reports = this.getUploadedReports(); - if (reports.length > 0) { - return reports[0]; - } else { - return null; - } - }; - - CrashReporter.prototype.getUploadedReports = function() { - var log, tmpdir; - tmpdir = process.platform === 'win32' ? os.tmpdir() : '/tmp'; - log = process.platform === 'darwin' ? path.join(tmpdir, this.productName + " Crashes") : path.join(tmpdir, this.productName + " Crashes", 'uploads.log'); - return binding._getUploadedReports(log); - }; - - return CrashReporter; - -})(); - -module.exports = new CrashReporter; diff --git a/atom/common/api/lib/deprecate.js b/atom/common/api/lib/deprecate.js deleted file mode 100644 index dcaf5f6cf6f..00000000000 --- a/atom/common/api/lib/deprecate.js +++ /dev/null @@ -1,102 +0,0 @@ -// Deprecate a method. -var deprecate, - slice = [].slice; - -deprecate = function(oldName, newName, fn) { - var warned; - warned = false; - return function() { - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn(oldName, newName); - } - return fn.apply(this, arguments); - }; -}; - -// The method is renamed. -deprecate.rename = function(object, oldName, newName) { - var newMethod, warned; - warned = false; - newMethod = function() { - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn(oldName, newName); - } - return this[newName].apply(this, arguments); - }; - if (typeof object === 'function') { - return object.prototype[oldName] = newMethod; - } else { - return object[oldName] = newMethod; - } -}; - -// Forward the method to member. -deprecate.member = function(object, method, member) { - var warned; - warned = false; - return object.prototype[method] = function() { - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn(method, member + "." + method); - } - return this[member][method].apply(this[member], arguments); - }; -}; - -// Deprecate a property. -deprecate.property = function(object, property, method) { - return Object.defineProperty(object, property, { - get: function() { - var warned; - warned = false; - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn(property + " property", method + " method"); - } - return this[method](); - } - }); -}; - -// Deprecate an event. -deprecate.event = function(emitter, oldName, newName, fn) { - var warned; - warned = false; - return emitter.on(newName, function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - - // there is listeners for old API. - if (this.listenerCount(oldName) > 0) { - if (!(warned || process.noDeprecation)) { - warned = true; - deprecate.warn("'" + oldName + "' event", "'" + newName + "' event"); - } - if (fn != null) { - return fn.apply(this, arguments); - } else { - return this.emit.apply(this, [oldName].concat(slice.call(args))); - } - } - }); -}; - -// Print deprecation warning. -deprecate.warn = function(oldName, newName) { - return deprecate.log(oldName + " is deprecated. Use " + newName + " instead."); -}; - -// Print deprecation message. -deprecate.log = function(message) { - if (process.throwDeprecation) { - throw new Error(message); - } else if (process.traceDeprecation) { - return console.trace(message); - } else { - return console.warn("(electron) " + message); - } -}; - -module.exports = deprecate; diff --git a/atom/common/api/lib/exports/electron.js b/atom/common/api/lib/exports/electron.js deleted file mode 100644 index 5b93d286da6..00000000000 --- a/atom/common/api/lib/exports/electron.js +++ /dev/null @@ -1,55 +0,0 @@ -// Do not expose the internal modules to `require`. -exports.hideInternalModules = function() { - var globalPaths = require('module').globalPaths; - if (globalPaths.length === 3) { - - // Remove the "common/api/lib" and "browser-or-renderer/api/lib". - return globalPaths.splice(0, 2); - } -}; - -// 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'); - } - } - }); -}; diff --git a/atom/common/api/lib/native-image.js b/atom/common/api/lib/native-image.js deleted file mode 100644 index 6f9a9bd7d54..00000000000 --- a/atom/common/api/lib/native-image.js +++ /dev/null @@ -1,7 +0,0 @@ -const deprecate = require('electron').deprecate; -const nativeImage = process.atomBinding('native_image'); - -// Deprecated. -deprecate.rename(nativeImage, 'createFromDataUrl', 'createFromDataURL'); - -module.exports = nativeImage; diff --git a/atom/common/api/lib/shell.js b/atom/common/api/lib/shell.js deleted file mode 100644 index 08cc4e8eb41..00000000000 --- a/atom/common/api/lib/shell.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = process.atomBinding('shell'); diff --git a/atom/common/api/object_life_monitor.cc b/atom/common/api/object_life_monitor.cc index 9b7c7fe6d05..916ad8a5177 100644 --- a/atom/common/api/object_life_monitor.cc +++ b/atom/common/api/object_life_monitor.cc @@ -31,16 +31,16 @@ ObjectLifeMonitor::ObjectLifeMonitor(v8::Isolate* isolate, // static void ObjectLifeMonitor::OnObjectGC( const v8::WeakCallbackInfo& data) { - // Usually FirstWeakCallback should do nothing other than reset |object_| - // and then set a second weak callback to run later. We can sidestep that, - // because posting a task to the current message loop is all but free - but - // DO NOT add any more work to this method. The only acceptable place to add - // code is RunCallback. ObjectLifeMonitor* self = data.GetParameter(); self->target_.Reset(); - base::MessageLoop::current()->PostTask( - FROM_HERE, base::Bind(&ObjectLifeMonitor::RunCallback, - self->weak_ptr_factory_.GetWeakPtr())); + self->RunCallback(); + data.SetSecondPassCallback(Free); +} + +// static +void ObjectLifeMonitor::Free( + const v8::WeakCallbackInfo& data) { + delete data.GetParameter(); } void ObjectLifeMonitor::RunCallback() { @@ -50,7 +50,6 @@ void ObjectLifeMonitor::RunCallback() { v8::Context::Scope context_scope(context); v8::Local::New(isolate_, destructor_)->Call( context->Global(), 0, nullptr); - delete this; } } // namespace atom diff --git a/atom/common/api/object_life_monitor.h b/atom/common/api/object_life_monitor.h index 90216d8227a..82d923fcedb 100644 --- a/atom/common/api/object_life_monitor.h +++ b/atom/common/api/object_life_monitor.h @@ -5,7 +5,7 @@ #ifndef ATOM_COMMON_API_OBJECT_LIFE_MONITOR_H_ #define ATOM_COMMON_API_OBJECT_LIFE_MONITOR_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "base/memory/weak_ptr.h" #include "v8/include/v8.h" @@ -23,6 +23,7 @@ class ObjectLifeMonitor { v8::Local destructor); static void OnObjectGC(const v8::WeakCallbackInfo& data); + static void Free(const v8::WeakCallbackInfo& data); void RunCallback(); diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index d6abf1cee82..01fe23a8892 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -90,12 +90,12 @@ bool GetNodeFromPath(std::string path, } bool FillFileInfoWithNode(Archive::FileInfo* info, - uint32 header_size, + uint32_t header_size, const base::DictionaryValue* node) { int size; if (!node->GetInteger("size", &size)) return false; - info->size = static_cast(size); + info->size = static_cast(size); if (node->GetBoolean("unpacked", &info->unpacked) && info->unpacked) return true; @@ -130,14 +130,22 @@ Archive::Archive(const base::FilePath& path) Archive::~Archive() { #if defined(OS_WIN) - file_.Close(); - _close(fd_); + if (fd_ != -1) { + _close(fd_); + // Don't close the handle since we already closed the fd. + file_.TakePlatformFile(); + } #endif } bool Archive::Init() { - if (!file_.IsValid()) + if (!file_.IsValid()) { + if (file_.error_details() != base::File::FILE_ERROR_NOT_FOUND) { + LOG(WARNING) << "Opening " << path_.value() + << ": " << base::File::ErrorToString(file_.error_details()); + } return false; + } std::vector buf; int len; @@ -149,7 +157,7 @@ bool Archive::Init() { return false; } - uint32 size; + uint32_t size; if (!base::PickleIterator(base::Pickle(buf.data(), buf.size())).ReadUInt32( &size)) { LOG(ERROR) << "Failed to parse header size from " << path_.value(); @@ -288,7 +296,7 @@ bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) { #endif *out = temp_file->path(); - external_files_.set(path, temp_file.Pass()); + external_files_.set(path, std::move(temp_file)); return true; } diff --git a/atom/common/asar/archive.h b/atom/common/asar/archive.h index de5b9de605a..79b848623c9 100644 --- a/atom/common/asar/archive.h +++ b/atom/common/asar/archive.h @@ -28,8 +28,8 @@ class Archive { FileInfo() : unpacked(false), executable(false), size(0), offset(0) {} bool unpacked; bool executable; - uint32 size; - uint64 offset; + uint32_t size; + uint64_t offset; }; struct Stats : public FileInfo { @@ -71,7 +71,7 @@ class Archive { base::FilePath path_; base::File file_; int fd_; - uint32 header_size_; + uint32_t header_size_; scoped_ptr header_; // Cached external temporary files. diff --git a/atom/common/asar/scoped_temporary_file.cc b/atom/common/asar/scoped_temporary_file.cc index 6dd12782d8e..8578d90d907 100644 --- a/atom/common/asar/scoped_temporary_file.cc +++ b/atom/common/asar/scoped_temporary_file.cc @@ -51,7 +51,7 @@ bool ScopedTemporaryFile::Init(const base::FilePath::StringType& ext) { bool ScopedTemporaryFile::InitFromFile(base::File* src, const base::FilePath::StringType& ext, - uint64 offset, uint64 size) { + uint64_t offset, uint64_t size) { if (!src->IsValid()) return false; diff --git a/atom/common/asar/scoped_temporary_file.h b/atom/common/asar/scoped_temporary_file.h index 23660a23901..5931d9b87af 100644 --- a/atom/common/asar/scoped_temporary_file.h +++ b/atom/common/asar/scoped_temporary_file.h @@ -28,7 +28,7 @@ class ScopedTemporaryFile { // Init an temporary file and fill it with content of |path|. bool InitFromFile(base::File* src, const base::FilePath::StringType& ext, - uint64 offset, uint64 size); + uint64_t offset, uint64_t size); base::FilePath path() const { return path_; } diff --git a/atom/common/atom_command_line.h b/atom/common/atom_command_line.h index 7c8840f7075..b5915533a41 100644 --- a/atom/common/atom_command_line.h +++ b/atom/common/atom_command_line.h @@ -8,7 +8,8 @@ #include #include -#include "base/basictypes.h" +#include "base/macros.h" +#include "build/build_config.h" namespace atom { diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index 838eca35dbc..cb470529340 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -6,8 +6,8 @@ #define ATOM_VERSION_H #define ATOM_MAJOR_VERSION 0 -#define ATOM_MINOR_VERSION 36 -#define ATOM_PATCH_VERSION 4 +#define ATOM_MINOR_VERSION 37 +#define ATOM_PATCH_VERSION 3 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/chrome_version.h b/atom/common/chrome_version.h index 37a5c64979e..8b9b7ef0c82 100644 --- a/atom/common/chrome_version.h +++ b/atom/common/chrome_version.h @@ -8,7 +8,7 @@ #ifndef ATOM_COMMON_CHROME_VERSION_H_ #define ATOM_COMMON_CHROME_VERSION_H_ -#define CHROME_VERSION_STRING "47.0.2526.110" +#define CHROME_VERSION_STRING "49.0.2623.75" #define CHROME_VERSION "v" CHROME_VERSION_STRING #endif // ATOM_COMMON_CHROME_VERSION_H_ diff --git a/atom/common/crash_reporter/crash_reporter.h b/atom/common/crash_reporter/crash_reporter.h index 98832fea45d..eebbe16dca8 100644 --- a/atom/common/crash_reporter/crash_reporter.h +++ b/atom/common/crash_reporter/crash_reporter.h @@ -10,7 +10,7 @@ #include #include -#include "base/basictypes.h" +#include "base/macros.h" namespace crash_reporter { diff --git a/atom/common/crash_reporter/crash_reporter_mac.mm b/atom/common/crash_reporter/crash_reporter_mac.mm index 74ac70125b7..130a421665f 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -48,7 +48,8 @@ void CrashReporterMac::InitBreakpad(const std::string& product_name, if (crashpad_client.StartHandler(handler_path, database_path, submit_url, StringMap(), - std::vector())) { + std::vector(), + true)) { crashpad_client.UseHandler(); } } // @autoreleasepool diff --git a/atom/common/crash_reporter/linux/crash_dump_handler.h b/atom/common/crash_reporter/linux/crash_dump_handler.h index f600c9e0d1b..f10c5212254 100644 --- a/atom/common/crash_reporter/linux/crash_dump_handler.h +++ b/atom/common/crash_reporter/linux/crash_dump_handler.h @@ -6,7 +6,11 @@ #ifndef ATOM_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_ #define ATOM_COMMON_CRASH_REPORTER_LINUX_CRASH_DUMP_HANDLER_H_ -#include "base/basictypes.h" +#include +#include +#include + +#include "base/macros.h" #include "vendor/breakpad/src/common/simple_string_dictionary.h" namespace crash_reporter { diff --git a/atom/common/crash_reporter/win/crash_service.cc b/atom/common/crash_reporter/win/crash_service.cc index 67e22381aef..58c7c38632e 100644 --- a/atom/common/crash_reporter/win/crash_service.cc +++ b/atom/common/crash_reporter/win/crash_service.cc @@ -86,7 +86,7 @@ bool WriteReportIDToFile(const std::wstring& dump_path, if (!file.is_open()) return false; - int64 seconds_since_epoch = + int64_t seconds_since_epoch = (base::Time::Now() - base::Time::UnixEpoch()).InSeconds(); std::wstring line = base::Int64ToString16(seconds_since_epoch); line += L','; @@ -211,7 +211,7 @@ bool CrashService::Initialize(const base::string16& application_name, std::wstring pipe_name = kTestPipeName; int max_reports = -1; - // The checkpoint file allows CrashReportSender to enforce the the maximum + // The checkpoint file allows CrashReportSender to enforce the maximum // reports per day quota. Does not seem to serve any other purpose. base::FilePath checkpoint_path = operating_dir.Append(kCheckPointFile); diff --git a/atom/common/crash_reporter/win/crash_service.h b/atom/common/crash_reporter/win/crash_service.h index 7195ec2a958..c05e0d5bf6e 100644 --- a/atom/common/crash_reporter/win/crash_service.h +++ b/atom/common/crash_reporter/win/crash_service.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/files/file_path.h" #include "base/synchronization/lock.h" diff --git a/atom/common/keyboad_util.cc b/atom/common/keyboad_util.cc deleted file mode 100644 index 7d7c5d99fab..00000000000 --- a/atom/common/keyboad_util.cc +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2015 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include -#include "atom/common/keyboad_util.h" - -namespace atom { - -// Return key code of the char. -ui::KeyboardCode KeyboardCodeFromCharCode(base::char16 c, bool* shifted) { - *shifted = false; - switch (c) { - case 0x08: return ui::VKEY_BACK; - case 0x7F: return ui::VKEY_DELETE; - case 0x09: return ui::VKEY_TAB; - case 0x0D: return ui::VKEY_RETURN; - case 0x1B: return ui::VKEY_ESCAPE; - case ' ': return ui::VKEY_SPACE; - - case 'a': return ui::VKEY_A; - case 'b': return ui::VKEY_B; - case 'c': return ui::VKEY_C; - case 'd': return ui::VKEY_D; - case 'e': return ui::VKEY_E; - case 'f': return ui::VKEY_F; - case 'g': return ui::VKEY_G; - case 'h': return ui::VKEY_H; - case 'i': return ui::VKEY_I; - case 'j': return ui::VKEY_J; - case 'k': return ui::VKEY_K; - case 'l': return ui::VKEY_L; - case 'm': return ui::VKEY_M; - case 'n': return ui::VKEY_N; - case 'o': return ui::VKEY_O; - case 'p': return ui::VKEY_P; - case 'q': return ui::VKEY_Q; - case 'r': return ui::VKEY_R; - case 's': return ui::VKEY_S; - case 't': return ui::VKEY_T; - case 'u': return ui::VKEY_U; - case 'v': return ui::VKEY_V; - case 'w': return ui::VKEY_W; - case 'x': return ui::VKEY_X; - case 'y': return ui::VKEY_Y; - case 'z': return ui::VKEY_Z; - - case ')': *shifted = true; case '0': return ui::VKEY_0; - case '!': *shifted = true; case '1': return ui::VKEY_1; - case '@': *shifted = true; case '2': return ui::VKEY_2; - case '#': *shifted = true; case '3': return ui::VKEY_3; - case '$': *shifted = true; case '4': return ui::VKEY_4; - case '%': *shifted = true; case '5': return ui::VKEY_5; - case '^': *shifted = true; case '6': return ui::VKEY_6; - case '&': *shifted = true; case '7': return ui::VKEY_7; - case '*': *shifted = true; case '8': return ui::VKEY_8; - case '(': *shifted = true; case '9': return ui::VKEY_9; - - case ':': *shifted = true; case ';': return ui::VKEY_OEM_1; - case '+': *shifted = true; case '=': return ui::VKEY_OEM_PLUS; - case '<': *shifted = true; case ',': return ui::VKEY_OEM_COMMA; - case '_': *shifted = true; case '-': return ui::VKEY_OEM_MINUS; - case '>': *shifted = true; case '.': return ui::VKEY_OEM_PERIOD; - case '?': *shifted = true; case '/': return ui::VKEY_OEM_2; - case '~': *shifted = true; case '`': return ui::VKEY_OEM_3; - case '{': *shifted = true; case '[': return ui::VKEY_OEM_4; - case '|': *shifted = true; case '\\': return ui::VKEY_OEM_5; - case '}': *shifted = true; case ']': return ui::VKEY_OEM_6; - case '"': *shifted = true; case '\'': return ui::VKEY_OEM_7; - - default: return ui::VKEY_UNKNOWN; - } -} - -// Return key code of the char. -ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& chr) { - if (chr == "enter") return ui::VKEY_RETURN; - if (chr == "backspace") return ui::VKEY_BACK; - if (chr == "delete") return ui::VKEY_DELETE; - if (chr == "tab") return ui::VKEY_TAB; - if (chr == "escape") return ui::VKEY_ESCAPE; - if (chr == "control") return ui::VKEY_CONTROL; - if (chr == "alt") return ui::VKEY_MENU; - if (chr == "shift") return ui::VKEY_SHIFT; - if (chr == "end") return ui::VKEY_END; - if (chr == "home") return ui::VKEY_HOME; - if (chr == "insert") return ui::VKEY_INSERT; - if (chr == "left") return ui::VKEY_LEFT; - if (chr == "up") return ui::VKEY_UP; - if (chr == "right") return ui::VKEY_RIGHT; - if (chr == "down") return ui::VKEY_DOWN; - if (chr == "pageup") return ui::VKEY_PRIOR; - if (chr == "pagedown") return ui::VKEY_NEXT; - if (chr == "printscreen") return ui::VKEY_SNAPSHOT; - - return ui::VKEY_UNKNOWN; -} - -} // namespace atom diff --git a/atom/common/keyboad_util.h b/atom/common/keyboad_util.h deleted file mode 100644 index 4a85c190635..00000000000 --- a/atom/common/keyboad_util.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2015 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_COMMON_KEYBOAD_UTIL_H_ -#define ATOM_COMMON_KEYBOAD_UTIL_H_ - -#include -#include "ui/events/keycodes/keyboard_codes.h" -#include "base/strings/string_util.h" - -namespace atom { - -// Return key code of the char, and also determine whether the SHIFT key is -// pressed. -ui::KeyboardCode KeyboardCodeFromCharCode(base::char16 c, bool* shifted); - -// Return key code of the char from a string representation of the char -ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& chr); - -} // namespace atom - -#endif // ATOM_COMMON_KEYBOAD_UTIL_H_ diff --git a/atom/common/keyboard_util.cc b/atom/common/keyboard_util.cc new file mode 100644 index 00000000000..d860bc0c46c --- /dev/null +++ b/atom/common/keyboard_util.cc @@ -0,0 +1,176 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include + +#include "atom/common/keyboard_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" + +namespace atom { + +namespace { + +// Return key code of the char, and also determine whether the SHIFT key is +// pressed. +ui::KeyboardCode KeyboardCodeFromCharCode(base::char16 c, bool* shifted) { + c = base::ToLowerASCII(c); + *shifted = false; + switch (c) { + case 0x08: return ui::VKEY_BACK; + case 0x7F: return ui::VKEY_DELETE; + case 0x09: return ui::VKEY_TAB; + case 0x0D: return ui::VKEY_RETURN; + case 0x1B: return ui::VKEY_ESCAPE; + case ' ': return ui::VKEY_SPACE; + + case 'a': return ui::VKEY_A; + case 'b': return ui::VKEY_B; + case 'c': return ui::VKEY_C; + case 'd': return ui::VKEY_D; + case 'e': return ui::VKEY_E; + case 'f': return ui::VKEY_F; + case 'g': return ui::VKEY_G; + case 'h': return ui::VKEY_H; + case 'i': return ui::VKEY_I; + case 'j': return ui::VKEY_J; + case 'k': return ui::VKEY_K; + case 'l': return ui::VKEY_L; + case 'm': return ui::VKEY_M; + case 'n': return ui::VKEY_N; + case 'o': return ui::VKEY_O; + case 'p': return ui::VKEY_P; + case 'q': return ui::VKEY_Q; + case 'r': return ui::VKEY_R; + case 's': return ui::VKEY_S; + case 't': return ui::VKEY_T; + case 'u': return ui::VKEY_U; + case 'v': return ui::VKEY_V; + case 'w': return ui::VKEY_W; + case 'x': return ui::VKEY_X; + case 'y': return ui::VKEY_Y; + case 'z': return ui::VKEY_Z; + + case ')': *shifted = true; case '0': return ui::VKEY_0; + case '!': *shifted = true; case '1': return ui::VKEY_1; + case '@': *shifted = true; case '2': return ui::VKEY_2; + case '#': *shifted = true; case '3': return ui::VKEY_3; + case '$': *shifted = true; case '4': return ui::VKEY_4; + case '%': *shifted = true; case '5': return ui::VKEY_5; + case '^': *shifted = true; case '6': return ui::VKEY_6; + case '&': *shifted = true; case '7': return ui::VKEY_7; + case '*': *shifted = true; case '8': return ui::VKEY_8; + case '(': *shifted = true; case '9': return ui::VKEY_9; + + case ':': *shifted = true; case ';': return ui::VKEY_OEM_1; + case '+': *shifted = true; case '=': return ui::VKEY_OEM_PLUS; + case '<': *shifted = true; case ',': return ui::VKEY_OEM_COMMA; + case '_': *shifted = true; case '-': return ui::VKEY_OEM_MINUS; + case '>': *shifted = true; case '.': return ui::VKEY_OEM_PERIOD; + case '?': *shifted = true; case '/': return ui::VKEY_OEM_2; + case '~': *shifted = true; case '`': return ui::VKEY_OEM_3; + case '{': *shifted = true; case '[': return ui::VKEY_OEM_4; + case '|': *shifted = true; case '\\': return ui::VKEY_OEM_5; + case '}': *shifted = true; case ']': return ui::VKEY_OEM_6; + case '"': *shifted = true; case '\'': return ui::VKEY_OEM_7; + + default: return ui::VKEY_UNKNOWN; + } +} + +// Return key code represented by |str|. +ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& s, + bool* shifted) { + std::string str = base::ToLowerASCII(s); + if (str == "ctrl" || str == "control") { + return ui::VKEY_CONTROL; + } else if (str == "super" || str == "cmd" || str == "command" || + str == "meta") { + return ui::VKEY_COMMAND; + } else if (str == "commandorcontrol" || str == "cmdorctrl") { +#if defined(OS_MACOSX) + return ui::VKEY_COMMAND; +#else + return ui::VKEY_CONTROL; +#endif + } else if (str == "alt" || str == "option") { + return ui::VKEY_MENU; + } else if (str == "shift") { + return ui::VKEY_SHIFT; + } else if (str == "altgr") { + return ui::VKEY_ALTGR; + } else if (str == "plus") { + *shifted = true; + return ui::VKEY_OEM_PLUS; + } else if (str == "tab") { + return ui::VKEY_TAB; + } else if (str == "space") { + return ui::VKEY_SPACE; + } else if (str == "backspace") { + return ui::VKEY_BACK; + } else if (str == "delete") { + return ui::VKEY_DELETE; + } else if (str == "insert") { + return ui::VKEY_INSERT; + } else if (str == "enter" || str == "return") { + return ui::VKEY_RETURN; + } else if (str == "up") { + return ui::VKEY_UP; + } else if (str == "down") { + return ui::VKEY_DOWN; + } else if (str == "left") { + return ui::VKEY_LEFT; + } else if (str == "right") { + return ui::VKEY_RIGHT; + } else if (str == "home") { + return ui::VKEY_HOME; + } else if (str == "end") { + return ui::VKEY_END; + } else if (str == "pageup") { + return ui::VKEY_PRIOR; + } else if (str == "pagedown") { + return ui::VKEY_NEXT; + } else if (str == "esc" || str == "escape") { + return ui::VKEY_ESCAPE; + } else if (str == "volumemute") { + return ui::VKEY_VOLUME_MUTE; + } else if (str == "volumeup") { + return ui::VKEY_VOLUME_UP; + } else if (str == "volumedown") { + return ui::VKEY_VOLUME_DOWN; + } else if (str == "medianexttrack") { + return ui::VKEY_MEDIA_NEXT_TRACK; + } else if (str == "mediaprevioustrack") { + return ui::VKEY_MEDIA_PREV_TRACK; + } else if (str == "mediastop") { + return ui::VKEY_MEDIA_STOP; + } else if (str == "mediaplaypause") { + return ui::VKEY_MEDIA_PLAY_PAUSE; + } else if (str == "printscreen") { + return ui::VKEY_SNAPSHOT; + } else if (str.size() > 1 && str[0] == 'f') { + // F1 - F24. + int n; + if (base::StringToInt(str.c_str() + 1, &n) && n > 0 && n < 25) { + return static_cast(ui::VKEY_F1 + n - 1); + } else { + LOG(WARNING) << str << "is not available on keyboard"; + return ui::VKEY_UNKNOWN; + } + } else { + LOG(WARNING) << "Invalid accelerator token: " << str; + return ui::VKEY_UNKNOWN; + } +} + +} // namespace + +ui::KeyboardCode KeyboardCodeFromStr(const std::string& str, bool* shifted) { + if (str.size() == 1) + return KeyboardCodeFromCharCode(str[0], shifted); + else + return KeyboardCodeFromKeyIdentifier(str, shifted); +} + +} // namespace atom diff --git a/atom/common/keyboard_util.h b/atom/common/keyboard_util.h new file mode 100644 index 00000000000..c9d1b809e8f --- /dev/null +++ b/atom/common/keyboard_util.h @@ -0,0 +1,20 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_KEYBOARD_UTIL_H_ +#define ATOM_COMMON_KEYBOARD_UTIL_H_ + +#include + +#include "ui/events/keycodes/keyboard_codes.h" + +namespace atom { + +// Return key code of the |str|, and also determine whether the SHIFT key is +// pressed. +ui::KeyboardCode KeyboardCodeFromStr(const std::string& str, bool* shifted); + +} // namespace atom + +#endif // ATOM_COMMON_KEYBOARD_UTIL_H_ diff --git a/atom/common/lib/asar.js b/atom/common/lib/asar.js deleted file mode 100644 index 7033a956420..00000000000 --- a/atom/common/lib/asar.js +++ /dev/null @@ -1,582 +0,0 @@ -(function () { - const asar = process.binding('atom_common_asar'); - const child_process = require('child_process'); - const path = require('path'); - const util = require('util'); - - var hasProp = {}.hasOwnProperty; - - // Cache asar archive objects. - var cachedArchives = {}; - - var getOrCreateArchive = function(p) { - var archive; - archive = cachedArchives[p]; - if (archive != null) { - return archive; - } - archive = asar.createArchive(p); - if (!archive) { - return false; - } - return cachedArchives[p] = archive; - }; - - // Clean cache on quit. - process.on('exit', function() { - var archive, p, results; - results = []; - for (p in cachedArchives) { - if (!hasProp.call(cachedArchives, p)) continue; - archive = cachedArchives[p]; - results.push(archive.destroy()); - } - return results; - }); - - // Separate asar package's path from full path. - var splitPath = function(p) { - var index; - - // shortcut to disable asar. - if (process.noAsar) { - return [false]; - } - - if (typeof p !== 'string') { - return [false]; - } - if (p.substr(-5) === '.asar') { - return [true, p, '']; - } - p = path.normalize(p); - index = p.lastIndexOf(".asar" + path.sep); - if (index === -1) { - return [false]; - } - return [true, p.substr(0, index + 5), p.substr(index + 6)]; - }; - - // Convert asar archive's Stats object to fs's Stats object. - var nextInode = 0; - - var uid = process.getuid != null ? process.getuid() : 0; - - var gid = process.getgid != null ? process.getgid() : 0; - - var fakeTime = new Date(); - - var asarStatsToFsStats = function(stats) { - return { - dev: 1, - ino: ++nextInode, - mode: 33188, - nlink: 1, - uid: uid, - gid: gid, - rdev: 0, - atime: stats.atime || fakeTime, - birthtime: stats.birthtime || fakeTime, - mtime: stats.mtime || fakeTime, - ctime: stats.ctime || fakeTime, - size: stats.size, - isFile: function() { - return stats.isFile; - }, - isDirectory: function() { - return stats.isDirectory; - }, - isSymbolicLink: function() { - return stats.isLink; - }, - isBlockDevice: function() { - return false; - }, - isCharacterDevice: function() { - return false; - }, - isFIFO: function() { - return false; - }, - isSocket: function() { - return false; - } - }; - }; - - // Create a ENOENT error. - var notFoundError = function(asarPath, filePath, callback) { - var error; - error = new Error("ENOENT, " + filePath + " not found in " + asarPath); - error.code = "ENOENT"; - error.errno = -2; - if (typeof callback !== 'function') { - throw error; - } - return process.nextTick(function() { - return callback(error); - }); - }; - - // Create a ENOTDIR error. - var notDirError = function(callback) { - var error; - error = new Error('ENOTDIR, not a directory'); - error.code = 'ENOTDIR'; - error.errno = -20; - if (typeof callback !== 'function') { - throw error; - } - return process.nextTick(function() { - return callback(error); - }); - }; - - // Create invalid archive error. - var invalidArchiveError = function(asarPath, callback) { - var error; - error = new Error("Invalid package " + asarPath); - if (typeof callback !== 'function') { - throw error; - } - return process.nextTick(function() { - return callback(error); - }); - }; - - // Override APIs that rely on passing file path instead of content to C++. - var overrideAPISync = function(module, name, arg) { - var old; - if (arg == null) { - arg = 0; - } - old = module[name]; - return module[name] = function() { - var archive, asarPath, filePath, isAsar, newPath, p, ref; - p = arguments[arg]; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return old.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - newPath = archive.copyFileOut(filePath); - if (!newPath) { - notFoundError(asarPath, filePath); - } - arguments[arg] = newPath; - return old.apply(this, arguments); - }; - }; - - var overrideAPI = function(module, name, arg) { - var old; - if (arg == null) { - arg = 0; - } - old = module[name]; - return module[name] = function() { - var archive, asarPath, callback, filePath, isAsar, newPath, p, ref; - p = arguments[arg]; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return old.apply(this, arguments); - } - callback = arguments[arguments.length - 1]; - if (typeof callback !== 'function') { - return overrideAPISync(module, name, arg); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - newPath = archive.copyFileOut(filePath); - if (!newPath) { - return notFoundError(asarPath, filePath, callback); - } - arguments[arg] = newPath; - return old.apply(this, arguments); - }; - }; - - // Override fs APIs. - exports.wrapFsWithAsar = function(fs) { - var exists, existsSync, internalModuleReadFile, internalModuleStat, lstat, lstatSync, mkdir, mkdirSync, readFile, readFileSync, readdir, readdirSync, realpath, realpathSync, stat, statSync, statSyncNoException; - lstatSync = fs.lstatSync; - fs.lstatSync = function(p) { - var archive, asarPath, filePath, isAsar, ref, stats; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return lstatSync(p); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - stats = archive.stat(filePath); - if (!stats) { - notFoundError(asarPath, filePath); - } - return asarStatsToFsStats(stats); - }; - lstat = fs.lstat; - fs.lstat = function(p, callback) { - var archive, asarPath, filePath, isAsar, ref, stats; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return lstat(p, callback); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - stats = getOrCreateArchive(asarPath).stat(filePath); - if (!stats) { - return notFoundError(asarPath, filePath, callback); - } - return process.nextTick(function() { - return callback(null, asarStatsToFsStats(stats)); - }); - }; - statSync = fs.statSync; - fs.statSync = function(p) { - var isAsar = splitPath(p)[0]; - if (!isAsar) { - return statSync(p); - } - - // Do not distinguish links for now. - return fs.lstatSync(p); - }; - stat = fs.stat; - fs.stat = function(p, callback) { - var isAsar = splitPath(p)[0]; - if (!isAsar) { - return stat(p, callback); - } - - // Do not distinguish links for now. - return process.nextTick(function() { - return fs.lstat(p, callback); - }); - }; - statSyncNoException = fs.statSyncNoException; - fs.statSyncNoException = function(p) { - var archive, asarPath, filePath, isAsar, ref, stats; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return statSyncNoException(p); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return false; - } - stats = archive.stat(filePath); - if (!stats) { - return false; - } - return asarStatsToFsStats(stats); - }; - realpathSync = fs.realpathSync; - fs.realpathSync = function(p) { - var archive, asarPath, filePath, isAsar, real, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return realpathSync.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - real = archive.realpath(filePath); - if (real === false) { - notFoundError(asarPath, filePath); - } - return path.join(realpathSync(asarPath), real); - }; - realpath = fs.realpath; - fs.realpath = function(p, cache, callback) { - var archive, asarPath, filePath, isAsar, real, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return realpath.apply(this, arguments); - } - if (typeof cache === 'function') { - callback = cache; - cache = void 0; - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - real = archive.realpath(filePath); - if (real === false) { - return notFoundError(asarPath, filePath, callback); - } - return realpath(asarPath, function(err, p) { - if (err) { - return callback(err); - } - return callback(null, path.join(p, real)); - }); - }; - exists = fs.exists; - fs.exists = function(p, callback) { - var archive, asarPath, filePath, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return exists(p, callback); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - return process.nextTick(function() { - return callback(archive.stat(filePath) !== false); - }); - }; - existsSync = fs.existsSync; - fs.existsSync = function(p) { - var archive, asarPath, filePath, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return existsSync(p); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return false; - } - return archive.stat(filePath) !== false; - }; - readFile = fs.readFile; - fs.readFile = function(p, options, callback) { - var archive, asarPath, buffer, encoding, fd, filePath, info, isAsar, realPath, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return readFile.apply(this, arguments); - } - if (typeof options === 'function') { - callback = options; - options = void 0; - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - info = archive.getFileInfo(filePath); - if (!info) { - return notFoundError(asarPath, filePath, callback); - } - if (info.size === 0) { - return process.nextTick(function() { - return callback(null, new Buffer(0)); - }); - } - if (info.unpacked) { - realPath = archive.copyFileOut(filePath); - return fs.readFile(realPath, options, callback); - } - if (!options) { - options = { - encoding: null - }; - } else if (util.isString(options)) { - options = { - encoding: options - }; - } else if (!util.isObject(options)) { - throw new TypeError('Bad arguments'); - } - encoding = options.encoding; - buffer = new Buffer(info.size); - fd = archive.getFd(); - if (!(fd >= 0)) { - return notFoundError(asarPath, filePath, callback); - } - return fs.read(fd, buffer, 0, info.size, info.offset, function(error) { - return callback(error, encoding ? buffer.toString(encoding) : buffer); - }); - }; - readFileSync = fs.readFileSync; - fs.readFileSync = function(p, opts) { - // this allows v8 to optimize this function - var archive, asarPath, buffer, encoding, fd, filePath, info, isAsar, options, realPath, ref; - options = opts; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return readFileSync.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - info = archive.getFileInfo(filePath); - if (!info) { - notFoundError(asarPath, filePath); - } - if (info.size === 0) { - if (options) { - return ''; - } else { - return new Buffer(0); - } - } - if (info.unpacked) { - realPath = archive.copyFileOut(filePath); - return fs.readFileSync(realPath, options); - } - if (!options) { - options = { - encoding: null - }; - } else if (util.isString(options)) { - options = { - encoding: options - }; - } else if (!util.isObject(options)) { - throw new TypeError('Bad arguments'); - } - encoding = options.encoding; - buffer = new Buffer(info.size); - fd = archive.getFd(); - if (!(fd >= 0)) { - notFoundError(asarPath, filePath); - } - fs.readSync(fd, buffer, 0, info.size, info.offset); - if (encoding) { - return buffer.toString(encoding); - } else { - return buffer; - } - }; - readdir = fs.readdir; - fs.readdir = function(p, callback) { - var archive, asarPath, filePath, files, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return readdir.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return invalidArchiveError(asarPath, callback); - } - files = archive.readdir(filePath); - if (!files) { - return notFoundError(asarPath, filePath, callback); - } - return process.nextTick(function() { - return callback(null, files); - }); - }; - readdirSync = fs.readdirSync; - fs.readdirSync = function(p) { - var archive, asarPath, filePath, files, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return readdirSync.apply(this, arguments); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - invalidArchiveError(asarPath); - } - files = archive.readdir(filePath); - if (!files) { - notFoundError(asarPath, filePath); - } - return files; - }; - internalModuleReadFile = process.binding('fs').internalModuleReadFile; - process.binding('fs').internalModuleReadFile = function(p) { - var archive, asarPath, buffer, fd, filePath, info, isAsar, realPath, ref; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return internalModuleReadFile(p); - } - archive = getOrCreateArchive(asarPath); - if (!archive) { - return void 0; - } - info = archive.getFileInfo(filePath); - if (!info) { - return void 0; - } - if (info.size === 0) { - return ''; - } - if (info.unpacked) { - realPath = archive.copyFileOut(filePath); - return fs.readFileSync(realPath, { - encoding: 'utf8' - }); - } - buffer = new Buffer(info.size); - fd = archive.getFd(); - if (!(fd >= 0)) { - return void 0; - } - fs.readSync(fd, buffer, 0, info.size, info.offset); - return buffer.toString('utf8'); - }; - internalModuleStat = process.binding('fs').internalModuleStat; - process.binding('fs').internalModuleStat = function(p) { - var archive, asarPath, filePath, isAsar, ref, stats; - ref = splitPath(p), isAsar = ref[0], asarPath = ref[1], filePath = ref[2]; - if (!isAsar) { - return internalModuleStat(p); - } - archive = getOrCreateArchive(asarPath); - - // -ENOENT - if (!archive) { - return -34; - } - stats = archive.stat(filePath); - - // -ENOENT - if (!stats) { - return -34; - } - if (stats.isDirectory) { - return 1; - } else { - return 0; - } - }; - - // Calling mkdir for directory inside asar archive should throw ENOTDIR - // error, but on Windows it throws ENOENT. - // This is to work around the recursive looping bug of mkdirp since it is - // widely used. - if (process.platform === 'win32') { - mkdir = fs.mkdir; - fs.mkdir = function(p, mode, callback) { - var filePath, isAsar, ref; - if (typeof mode === 'function') { - callback = mode; - } - ref = splitPath(p), isAsar = ref[0], filePath = ref[2]; - if (isAsar && filePath.length) { - return notDirError(callback); - } - return mkdir(p, mode, callback); - }; - mkdirSync = fs.mkdirSync; - fs.mkdirSync = function(p, mode) { - var filePath, isAsar, ref; - ref = splitPath(p), isAsar = ref[0], filePath = ref[2]; - if (isAsar && filePath.length) { - notDirError(); - } - return mkdirSync(p, mode); - }; - } - overrideAPI(fs, 'open'); - overrideAPI(child_process, 'execFile'); - overrideAPISync(process, 'dlopen', 1); - overrideAPISync(require('module')._extensions, '.node', 1); - overrideAPISync(fs, 'openSync'); - return overrideAPISync(child_process, 'execFileSync'); - }; -})(); diff --git a/atom/common/lib/asar_init.js b/atom/common/lib/asar_init.js deleted file mode 100644 index 49b57907c8f..00000000000 --- a/atom/common/lib/asar_init.js +++ /dev/null @@ -1,14 +0,0 @@ -(function () { - return function(process, require, asarSource) { - // Make asar.coffee accessible via "require". - process.binding('natives').ATOM_SHELL_ASAR = asarSource; - - // Monkey-patch the fs module. - require('ATOM_SHELL_ASAR').wrapFsWithAsar(require('fs')); - - // Make graceful-fs work with asar. - var source = process.binding('natives'); - source['original-fs'] = source.fs; - return source['fs'] = "var src = '(function (exports, require, module, __filename, __dirname) { ' +\n process.binding('natives')['original-fs'] +\n ' });';\nvar vm = require('vm');\nvar fn = vm.runInThisContext(src, { filename: 'fs.js' });\nfn(exports, require, module);\nvar asar = require('ATOM_SHELL_ASAR');\nasar.wrapFsWithAsar(exports);"; - }; -})(); diff --git a/atom/common/lib/reset-search-paths.js b/atom/common/lib/reset-search-paths.js deleted file mode 100644 index abf67119673..00000000000 --- a/atom/common/lib/reset-search-paths.js +++ /dev/null @@ -1,36 +0,0 @@ -const path = require('path'); -const Module = require('module'); - -// Clear Node's global search paths. -Module.globalPaths.length = 0; - -// Clear current and parent(init.coffee)'s search paths. -module.paths = []; - -module.parent.paths = []; - -// Prevent Node from adding paths outside this app to search paths. -Module._nodeModulePaths = function(from) { - var dir, i, part, parts, paths, skipOutsidePaths, splitRe, tip; - from = path.resolve(from); - - // If "from" is outside the app then we do nothing. - skipOutsidePaths = from.startsWith(process.resourcesPath); - - // Following logoic is copied from module.js. - splitRe = process.platform === 'win32' ? /[\/\\]/ : /\//; - paths = []; - parts = from.split(splitRe); - for (tip = i = parts.length - 1; i >= 0; tip = i += -1) { - part = parts[tip]; - if (part === 'node_modules') { - continue; - } - dir = parts.slice(0, tip + 1).join(path.sep); - if (skipOutsidePaths && !dir.startsWith(process.resourcesPath)) { - break; - } - paths.push(path.join(dir, 'node_modules')); - } - return paths; -}; diff --git a/atom/common/mouse_util.cc b/atom/common/mouse_util.cc new file mode 100644 index 00000000000..69aadaa7a04 --- /dev/null +++ b/atom/common/mouse_util.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include +#include "atom/common/mouse_util.h" + +using Cursor = blink::WebCursorInfo::Type; + +namespace atom { + +std::string CursorTypeToString(const content::WebCursor::CursorInfo& info) { + switch (info.type) { + case Cursor::TypePointer: return "default"; + case Cursor::TypeCross: return "crosshair"; + case Cursor::TypeHand: return "pointer"; + case Cursor::TypeIBeam: return "text"; + case Cursor::TypeWait: return "wait"; + case Cursor::TypeHelp: return "help"; + case Cursor::TypeEastResize: return "e-resize"; + case Cursor::TypeNorthResize: return "n-resize"; + case Cursor::TypeNorthEastResize: return "ne-resize"; + case Cursor::TypeNorthWestResize: return "nw-resize"; + case Cursor::TypeSouthResize: return "s-resize"; + case Cursor::TypeSouthEastResize: return "se-resize"; + case Cursor::TypeSouthWestResize: return "sw-resize"; + case Cursor::TypeWestResize: return "w-resize"; + case Cursor::TypeNorthSouthResize: return "ns-resize"; + case Cursor::TypeEastWestResize: return "ew-resize"; + case Cursor::TypeNorthEastSouthWestResize: return "nesw-resize"; + case Cursor::TypeNorthWestSouthEastResize: return "nwse-resize"; + case Cursor::TypeColumnResize: return "col-resize"; + case Cursor::TypeRowResize: return "row-resize"; + case Cursor::TypeMiddlePanning: return "m-panning"; + case Cursor::TypeEastPanning: return "e-panning"; + case Cursor::TypeNorthPanning: return "n-panning"; + case Cursor::TypeNorthEastPanning: return "ne-panning"; + case Cursor::TypeNorthWestPanning: return "nw-panning"; + case Cursor::TypeSouthPanning: return "s-panning"; + case Cursor::TypeSouthEastPanning: return "se-panning"; + case Cursor::TypeSouthWestPanning: return "sw-panning"; + case Cursor::TypeWestPanning: return "w-panning"; + case Cursor::TypeMove: return "move"; + case Cursor::TypeVerticalText: return "vertical-text"; + case Cursor::TypeCell: return "cell"; + case Cursor::TypeContextMenu: return "context-menu"; + case Cursor::TypeAlias: return "alias"; + case Cursor::TypeProgress: return "progress"; + case Cursor::TypeNoDrop: return "nodrop"; + case Cursor::TypeCopy: return "copy"; + case Cursor::TypeNone: return "none"; + case Cursor::TypeNotAllowed: return "not-allowed"; + case Cursor::TypeZoomIn: return "zoom-in"; + case Cursor::TypeZoomOut: return "zoom-out"; + case Cursor::TypeGrab: return "grab"; + case Cursor::TypeGrabbing: return "grabbing"; + case Cursor::TypeCustom: return "custom"; + default: return "default"; + } +} + +} // namespace atom diff --git a/atom/common/mouse_util.h b/atom/common/mouse_util.h new file mode 100644 index 00000000000..2fd937422da --- /dev/null +++ b/atom/common/mouse_util.h @@ -0,0 +1,36 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_MOUSE_UTIL_H_ +#define ATOM_COMMON_MOUSE_UTIL_H_ + +#include +#include "content/common/cursors/webcursor.h" +#include "ipc/ipc_message_macros.h" + +// IPC macros similar to the already existing ones in the chromium source. +// We need these to listen to the cursor change IPC message while still +// letting chromium handle the actual cursor change by setting handled = false. +#define IPC_MESSAGE_HANDLER_CODE(msg_class, member_func, code) \ + IPC_MESSAGE_FORWARD_CODE(msg_class, this, \ + _IpcMessageHandlerClass::member_func, code) + +#define IPC_MESSAGE_FORWARD_CODE(msg_class, obj, member_func, code) \ + case msg_class::ID: { \ + TRACK_RUN_IN_THIS_SCOPED_REGION(member_func); \ + if (!msg_class::Dispatch(&ipc_message__, obj, this, param__, \ + &member_func)) \ + ipc_message__.set_dispatch_error(); \ + code; \ + } \ + break; + +namespace atom { + +// Returns the cursor's type as a string. +std::string CursorTypeToString(const content::WebCursor::CursorInfo& info); + +} // namespace atom + +#endif // ATOM_COMMON_MOUSE_UTIL_H_ diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc index 095490ab883..c58f830eb02 100644 --- a/atom/common/native_mate_converters/blink_converter.cc +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -7,7 +7,7 @@ #include #include -#include "atom/common/keyboad_util.h" +#include "atom/common/keyboard_util.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "content/public/browser/native_web_keyboard_event.h" @@ -159,25 +159,22 @@ bool Converter::FromV8( return false; if (!ConvertFromV8(isolate, val, static_cast(out))) return false; - base::char16 code; - std::string identifier; - bool shifted = false; - if (dict.Get("keyCode", &code)) - out->windowsKeyCode = atom::KeyboardCodeFromCharCode(code, &shifted); - else if (dict.Get("keyCode", &identifier)) - out->windowsKeyCode = atom::KeyboardCodeFromKeyIdentifier( - base::ToLowerASCII(identifier)); + std::string str; + bool shifted = false; + if (dict.Get("keyCode", &str)) + out->windowsKeyCode = atom::KeyboardCodeFromStr(str, &shifted); else return false; if (shifted) out->modifiers |= blink::WebInputEvent::ShiftKey; out->setKeyIdentifierFromWindowsKeyCode(); - if (out->type == blink::WebInputEvent::Char || - out->type == blink::WebInputEvent::RawKeyDown) { - out->text[0] = code; - out->unmodifiedText[0] = code; + if ((out->type == blink::WebInputEvent::Char || + out->type == blink::WebInputEvent::RawKeyDown) && + str.size() == 1) { + out->text[0] = str[0]; + out->unmodifiedText[0] = str[0]; } return true; } diff --git a/atom/common/native_mate_converters/content_converter.cc b/atom/common/native_mate_converters/content_converter.cc index d79094f79d4..f5d81d085bc 100644 --- a/atom/common/native_mate_converters/content_converter.cc +++ b/atom/common/native_mate_converters/content_converter.cc @@ -7,6 +7,8 @@ #include #include +#include "atom/browser/api/atom_api_web_contents.h" +#include "atom/browser/web_contents_permission_helper.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "content/public/browser/web_contents.h" @@ -98,6 +100,55 @@ v8::Local Converter::ToV8( return mate::ConvertToV8(isolate, dict); } +// static +bool Converter::FromV8( + v8::Isolate* isolate, + v8::Local val, + content::PermissionStatus* out) { + bool result; + if (!ConvertFromV8(isolate, val, &result)) + return false; + + if (result) + *out = content::PERMISSION_STATUS_GRANTED; + else + *out = content::PERMISSION_STATUS_DENIED; + + return true; +} + +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, const content::PermissionType& val) { + using PermissionType = atom::WebContentsPermissionHelper::PermissionType; + switch (val) { + case content::PermissionType::MIDI_SYSEX: + return StringToV8(isolate, "midiSysex"); + case content::PermissionType::PUSH_MESSAGING: + return StringToV8(isolate, "pushMessaging"); + case content::PermissionType::NOTIFICATIONS: + return StringToV8(isolate, "notifications"); + case content::PermissionType::GEOLOCATION: + return StringToV8(isolate, "geolocation"); + case content::PermissionType::AUDIO_CAPTURE: + case content::PermissionType::VIDEO_CAPTURE: + return StringToV8(isolate, "media"); + case content::PermissionType::PROTECTED_MEDIA_IDENTIFIER: + return StringToV8(isolate, "mediaKeySystem"); + case content::PermissionType::MIDI: + return StringToV8(isolate, "midi"); + default: + break; + } + + if (val == (content::PermissionType)(PermissionType::POINTER_LOCK)) + return StringToV8(isolate, "pointerLock"); + else if (val == (content::PermissionType)(PermissionType::FULLSCREEN)) + return StringToV8(isolate, "fullscreen"); + + return StringToV8(isolate, "unknown"); +} + // static bool Converter::FromV8( v8::Isolate* isolate, @@ -119,4 +170,12 @@ bool Converter::FromV8( return true; } +// static +v8::Local Converter::ToV8( + v8::Isolate* isolate, content::WebContents* val) { + if (!val) + return v8::Null(isolate); + return atom::api::WebContents::CreateFrom(isolate, val).ToV8(); +} + } // namespace mate diff --git a/atom/common/native_mate_converters/content_converter.h b/atom/common/native_mate_converters/content_converter.h index a5708e022b5..b1a42b6897c 100644 --- a/atom/common/native_mate_converters/content_converter.h +++ b/atom/common/native_mate_converters/content_converter.h @@ -7,7 +7,9 @@ #include +#include "content/public/browser/permission_type.h" #include "content/public/common/menu_item.h" +#include "content/public/common/permission_status.mojom.h" #include "content/public/common/stop_find_action.h" #include "native_mate/converter.h" @@ -33,12 +35,30 @@ struct Converter { const ContextMenuParamsWithWebContents& val); }; +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + content::PermissionStatus* out); +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const content::PermissionType& val); +}; + template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, content::StopFindAction* out); }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + content::WebContents* val); +}; + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_CONTENT_CONVERTER_H_ diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 7a1b48d9311..5223709ae58 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -10,6 +10,7 @@ #include "atom/common/node_includes.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "base/values.h" #include "native_mate/dictionary.h" #include "net/base/upload_bytes_element_reader.h" #include "net/base/upload_data_stream.h" @@ -24,35 +25,17 @@ namespace mate { // static v8::Local Converter::ToV8( v8::Isolate* isolate, const net::URLRequest* val) { - mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); - dict.Set("method", val->method()); - dict.Set("url", val->url().spec()); - dict.Set("referrer", val->referrer()); - const net::UploadDataStream* upload_data = val->get_upload(); - if (upload_data) { - const ScopedVector* readers = - upload_data->GetElementReaders(); - std::vector upload_data_list; - upload_data_list.reserve(readers->size()); - for (const auto& reader : *readers) { - auto upload_data_dict = mate::Dictionary::CreateEmpty(isolate); - if (reader->AsBytesReader()) { - const net::UploadBytesElementReader* bytes_reader = - reader->AsBytesReader(); - auto bytes = - node::Buffer::Copy(isolate, bytes_reader->bytes(), - bytes_reader->length()).ToLocalChecked(); - upload_data_dict.Set("bytes", bytes); - } else if (reader->AsFileReader()) { - const net::UploadFileElementReader* file_reader = - reader->AsFileReader(); - upload_data_dict.Set("file", file_reader->path().AsUTF8Unsafe()); - } - upload_data_list.push_back(upload_data_dict); - } - dict.Set("uploadData", upload_data_list); - } - return mate::ConvertToV8(isolate, dict); + scoped_ptr dict(new base::DictionaryValue); + dict->SetString("method", val->method()); + std::string url; + if (!val->url_chain().empty()) url = val->url().spec(); + dict->SetStringWithoutPathExpansion("url", url); + dict->SetString("referrer", val->referrer()); + scoped_ptr list(new base::ListValue); + atom::GetUploadData(list.get(), val); + if (!list->empty()) + dict->Set("uploadData", std::move(list)); + return mate::ConvertToV8(isolate, *(dict.get())); } // static @@ -83,3 +66,34 @@ v8::Local Converter>::ToV8( } } // namespace mate + +namespace atom { + +void GetUploadData(base::ListValue* upload_data_list, + const net::URLRequest* request) { + const net::UploadDataStream* upload_data = request->get_upload(); + if (!upload_data) + return; + const std::vector>* readers = + upload_data->GetElementReaders(); + for (const auto& reader : *readers) { + scoped_ptr upload_data_dict( + new base::DictionaryValue); + if (reader->AsBytesReader()) { + const net::UploadBytesElementReader* bytes_reader = + reader->AsBytesReader(); + scoped_ptr bytes( + base::BinaryValue::CreateWithCopiedBuffer(bytes_reader->bytes(), + bytes_reader->length())); + upload_data_dict->Set("bytes", std::move(bytes)); + } else if (reader->AsFileReader()) { + const net::UploadFileElementReader* file_reader = + reader->AsFileReader(); + auto file_path = file_reader->path().AsUTF8Unsafe(); + upload_data_dict->SetStringWithoutPathExpansion("file", file_path); + } + upload_data_list->Append(std::move(upload_data_dict)); + } +} + +} // namespace atom diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index b11c55929b9..b7fd9481a20 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -8,6 +8,10 @@ #include "base/memory/ref_counted.h" #include "native_mate/converter.h" +namespace base { +class ListValue; +} + namespace net { class AuthChallengeInfo; class URLRequest; @@ -36,4 +40,11 @@ struct Converter> { } // namespace mate +namespace atom { + +void GetUploadData(base::ListValue* upload_data_list, + const net::URLRequest* request); + +} // namespace atom + #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ diff --git a/atom/common/native_mate_converters/v8_value_converter.cc b/atom/common/native_mate_converters/v8_value_converter.cc index 7d3a1277cb8..99873cd1c4f 100644 --- a/atom/common/native_mate_converters/v8_value_converter.cc +++ b/atom/common/native_mate_converters/v8_value_converter.cc @@ -76,15 +76,10 @@ class V8ValueConverter::FromV8ValueState { }; V8ValueConverter::V8ValueConverter() - : date_allowed_(false), - reg_exp_allowed_(false), + : reg_exp_allowed_(false), function_allowed_(false), strip_null_from_objects_(false) {} -void V8ValueConverter::SetDateAllowed(bool val) { - date_allowed_ = val; -} - void V8ValueConverter::SetRegExpAllowed(bool val) { reg_exp_allowed_ = val; } @@ -174,7 +169,7 @@ v8::Local V8ValueConverter::ToV8Array( CHECK(!child_v8.IsEmpty()); v8::TryCatch try_catch; - result->Set(static_cast(i), child_v8); + result->Set(static_cast(i), child_v8); if (try_catch.HasCaught()) LOG(ERROR) << "Setter for index " << i << " threw an exception."; } @@ -243,12 +238,17 @@ base::Value* V8ValueConverter::FromV8ValueImpl( return NULL; if (val->IsDate()) { - if (!date_allowed_) - // JSON.stringify would convert this to a string, but an object is more - // consistent within this class. - return FromV8Object(val->ToObject(), state, isolate); v8::Date* date = v8::Date::Cast(*val); - return new base::FundamentalValue(date->NumberValue() / 1000.0); + v8::Local toISOString = + date->Get(v8::String::NewFromUtf8(isolate, "toISOString")); + if (toISOString->IsFunction()) { + v8::Local result = + toISOString.As()->Call(val, 0, nullptr); + if (!result.IsEmpty()) { + v8::String::Utf8Value utf8(result->ToString()); + return new base::StringValue(std::string(*utf8, utf8.length())); + } + } } if (val->IsRegExp()) { @@ -298,7 +298,7 @@ base::Value* V8ValueConverter::FromV8Array( base::ListValue* result = new base::ListValue(); // Only fields with integer keys are carried over to the ListValue. - for (uint32 i = 0; i < val->Length(); ++i) { + for (uint32_t i = 0; i < val->Length(); ++i) { v8::TryCatch try_catch; v8::Local child_v8 = val->Get(i); if (try_catch.HasCaught()) { @@ -345,7 +345,7 @@ base::Value* V8ValueConverter::FromV8Object( scoped_ptr result(new base::DictionaryValue()); v8::Local property_names(val->GetOwnPropertyNames()); - for (uint32 i = 0; i < property_names->Length(); ++i) { + for (uint32_t i = 0; i < property_names->Length(); ++i) { v8::Local key(property_names->Get(i)); // Extend this test to cover more types as necessary and if sensible. diff --git a/atom/common/native_mate_converters/v8_value_converter.h b/atom/common/native_mate_converters/v8_value_converter.h index 2b695b43747..632587022d1 100644 --- a/atom/common/native_mate_converters/v8_value_converter.h +++ b/atom/common/native_mate_converters/v8_value_converter.h @@ -5,7 +5,7 @@ #ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_V8_VALUE_CONVERTER_H_ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_V8_VALUE_CONVERTER_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "v8/include/v8.h" @@ -22,7 +22,6 @@ class V8ValueConverter { public: V8ValueConverter(); - void SetDateAllowed(bool val); void SetRegExpAllowed(bool val); void SetFunctionAllowed(bool val); void SetStripNullFromObjects(bool val); @@ -58,9 +57,6 @@ class V8ValueConverter { FromV8ValueState* state, v8::Isolate* isolate) const; - // If true, we will convert Date JavaScript objects to doubles. - bool date_allowed_; - // If true, we will convert RegExp JavaScript objects to string. bool reg_exp_allowed_; diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index b1cb84eead9..ce3b9a70815 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -35,6 +35,7 @@ REFERENCE_MODULE(atom_browser_app); REFERENCE_MODULE(atom_browser_auto_updater); 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_download_item); REFERENCE_MODULE(atom_browser_menu); @@ -84,7 +85,7 @@ scoped_ptr StringVectorToArgArray( for (size_t i = 0; i < vector.size(); ++i) { array[i] = vector[i].c_str(); } - return array.Pass(); + return array; } base::FilePath GetResourcesPath(bool is_browser) { @@ -105,8 +106,6 @@ base::FilePath GetResourcesPath(bool is_browser) { } // namespace -node::Environment* global_env = nullptr; - NodeBindings::NodeBindings(bool is_browser) : is_browser_(is_browser), message_loop_(nullptr), @@ -162,9 +161,8 @@ node::Environment* NodeBindings::CreateEnvironment( FILE_PATH_LITERAL("browser") : FILE_PATH_LITERAL("renderer"); base::FilePath resources_path = GetResourcesPath(is_browser_); base::FilePath script_path = - resources_path.Append(FILE_PATH_LITERAL("atom.asar")) + resources_path.Append(FILE_PATH_LITERAL("electron.asar")) .Append(process_type) - .Append(FILE_PATH_LITERAL("lib")) .Append(FILE_PATH_LITERAL("init.js")); std::string script_path_str = script_path.AsUTF8Unsafe(); args.insert(args.begin() + 1, script_path_str.c_str()); @@ -214,10 +212,8 @@ void NodeBindings::RunMessageLoop() { void NodeBindings::UvRunOnce() { DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); - // By default the global env would be used unless user specified another one - // (this happens for renderer process, which wraps the uv loop with web page - // context). - node::Environment* env = uv_env() ? uv_env() : global_env; + node::Environment* env = uv_env(); + CHECK(env); // Use Locker in browser process. mate::Locker locker(env->isolate()); diff --git a/atom/common/node_bindings.h b/atom/common/node_bindings.h index 93ad7714916..16d512d3bed 100644 --- a/atom/common/node_bindings.h +++ b/atom/common/node_bindings.h @@ -5,7 +5,7 @@ #ifndef ATOM_COMMON_NODE_BINDINGS_H_ #define ATOM_COMMON_NODE_BINDINGS_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "base/memory/weak_ptr.h" #include "v8/include/v8.h" #include "vendor/node/deps/uv/include/uv.h" diff --git a/atom/common/node_includes.h b/atom/common/node_includes.h index 3876d862291..bb76afb54db 100644 --- a/atom/common/node_includes.h +++ b/atom/common/node_includes.h @@ -28,11 +28,4 @@ #include "vendor/node/src/node_buffer.h" #include "vendor/node/src/node_internals.h" -namespace atom { -// Defined in node_bindings.cc. -// For renderer it's created in atom_renderer_client.cc. -// For browser it's created in atom_browser_main_parts.cc. -extern node::Environment* global_env; -} - #endif // ATOM_COMMON_NODE_INCLUDES_H_ diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 477fe6755fa..ac63ea1b727 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -8,22 +8,26 @@ namespace atom { namespace options { -const char kTitle[] = "title"; -const char kIcon[] = "icon"; -const char kFrame[] = "frame"; -const char kShow[] = "show"; -const char kCenter[] = "center"; -const char kX[] = "x"; -const char kY[] = "y"; -const char kWidth[] = "width"; -const char kHeight[] = "height"; -const char kMinWidth[] = "minWidth"; -const char kMinHeight[] = "minHeight"; -const char kMaxWidth[] = "maxWidth"; -const char kMaxHeight[] = "maxHeight"; -const char kResizable[] = "resizable"; -const char kMovable[] = "movable"; -const char kFullscreen[] = "fullscreen"; +const char kTitle[] = "title"; +const char kIcon[] = "icon"; +const char kFrame[] = "frame"; +const char kShow[] = "show"; +const char kCenter[] = "center"; +const char kX[] = "x"; +const char kY[] = "y"; +const char kWidth[] = "width"; +const char kHeight[] = "height"; +const char kMinWidth[] = "minWidth"; +const char kMinHeight[] = "minHeight"; +const char kMaxWidth[] = "maxWidth"; +const char kMaxHeight[] = "maxHeight"; +const char kResizable[] = "resizable"; +const char kMovable[] = "movable"; +const char kMinimizable[] = "minimizable"; +const char kMaximizable[] = "maximizable"; +const char kFullScreenable[] = "fullscreenable"; +const char kClosable[] = "closable"; +const char kFullscreen[] = "fullscreen"; // Whether the window should show in taskbar. const char kSkipTaskbar[] = "skipTaskbar"; @@ -68,6 +72,9 @@ const char kStandardWindow[] = "standardWindow"; // Default browser window background color. const char kBackgroundColor[] = "backgroundColor"; +// Whether the window should have a shadow. +const char kHasShadow[] = "hasShadow"; + // The WebPreferences. const char kWebPreferences[] = "webPreferences"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 78de53b825f..3c198555a5c 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -24,6 +24,10 @@ extern const char kMaxWidth[]; extern const char kMaxHeight[]; extern const char kResizable[]; extern const char kMovable[]; +extern const char kMinimizable[]; +extern const char kMaximizable[]; +extern const char kFullScreenable[]; +extern const char kClosable[]; extern const char kFullscreen[]; extern const char kSkipTaskbar[]; extern const char kKiosk[]; @@ -39,6 +43,7 @@ extern const char kType[]; extern const char kDisableAutoHideCursor[]; extern const char kStandardWindow[]; extern const char kBackgroundColor[]; +extern const char kHasShadow[]; extern const char kWebPreferences[]; // WebPreferences. diff --git a/atom/common/platform_util.h b/atom/common/platform_util.h index 312942c4f5d..4565221e9d8 100644 --- a/atom/common/platform_util.h +++ b/atom/common/platform_util.h @@ -23,7 +23,7 @@ void OpenItem(const base::FilePath& full_path); // Open the given external protocol URL in the desktop's default manner. // (For example, mailto: URLs in the default mail user agent.) -bool OpenExternal(const GURL& url); +bool OpenExternal(const GURL& url, bool activate); // Move a file to trash. bool MoveItemToTrash(const base::FilePath& full_path); diff --git a/atom/common/platform_util_linux.cc b/atom/common/platform_util_linux.cc index aa7439968da..1e437b866cc 100644 --- a/atom/common/platform_util_linux.cc +++ b/atom/common/platform_util_linux.cc @@ -64,7 +64,7 @@ void OpenItem(const base::FilePath& full_path) { XDGOpen(full_path.value()); } -bool OpenExternal(const GURL& url) { +bool OpenExternal(const GURL& url, bool activate) { if (url.SchemeIs("mailto")) return XDGEmail(url.spec()); else diff --git a/atom/common/platform_util_mac.mm b/atom/common/platform_util_mac.mm index 7184593ae19..98bc4e537d8 100644 --- a/atom/common/platform_util_mac.mm +++ b/atom/common/platform_util_mac.mm @@ -119,7 +119,7 @@ void OpenItem(const base::FilePath& full_path) { } } -bool OpenExternal(const GURL& url) { +bool OpenExternal(const GURL& url, bool activate) { DCHECK([NSThread isMainThread]); NSURL* ns_url = net::NSURLWithGURL(url); if (!ns_url) { @@ -136,7 +136,15 @@ bool OpenExternal(const GURL& url) { } CFRelease(openingApp); // NOT A BUG; LSGetApplicationForURL retains for us - return [[NSWorkspace sharedWorkspace] openURL:ns_url]; + NSUInteger launchOptions = NSWorkspaceLaunchDefault; + if (!activate) + launchOptions |= NSWorkspaceLaunchWithoutActivation; + + return [[NSWorkspace sharedWorkspace] openURLs: @[ns_url] + withAppBundleIdentifier: nil + options: launchOptions + additionalEventParamDescriptor: NULL + launchIdentifiers: NULL]; } bool MoveItemToTrash(const base::FilePath& full_path) { diff --git a/atom/common/platform_util_win.cc b/atom/common/platform_util_win.cc index 735e974d1f8..12591a94d56 100644 --- a/atom/common/platform_util_win.cc +++ b/atom/common/platform_util_win.cc @@ -238,7 +238,7 @@ void ShowItemInFolder(const base::FilePath& full_path) { (GetProcAddress(shell32_base, "SHOpenFolderAndSelectItems")); } if (!open_folder_and_select_itemsPtr) { - ShellExecute(NULL, L"open", dir.value().c_str(), NULL, NULL, SW_SHOW); + ui::win::OpenFolderViaShell(dir); return; } @@ -251,15 +251,19 @@ void ShowItemInFolder(const base::FilePath& full_path) { hr = desktop->ParseDisplayName(NULL, NULL, const_cast(dir.value().c_str()), NULL, &dir_item, NULL); - if (FAILED(hr)) + if (FAILED(hr)) { + ui::win::OpenFolderViaShell(dir); return; + } base::win::ScopedCoMem file_item; hr = desktop->ParseDisplayName(NULL, NULL, const_cast(full_path.value().c_str()), NULL, &file_item, NULL); - if (FAILED(hr)) + if (FAILED(hr)) { + ui::win::OpenFolderViaShell(dir); return; + } const ITEMIDLIST* highlight[] = { file_item }; @@ -271,7 +275,7 @@ void ShowItemInFolder(const base::FilePath& full_path) { // found" even though the file is there. In these cases, ShellExecute() // seems to work as a fallback (although it won't select the file). if (hr == ERROR_FILE_NOT_FOUND) { - ShellExecute(NULL, L"open", dir.value().c_str(), NULL, NULL, SW_SHOW); + ui::win::OpenFolderViaShell(dir); } else { LPTSTR message = NULL; DWORD message_length = FormatMessage( @@ -284,6 +288,8 @@ void ShowItemInFolder(const base::FilePath& full_path) { << " " << reinterpret_cast(&message); if (message) LocalFree(message); + + ui::win::OpenFolderViaShell(dir); } } } @@ -295,7 +301,7 @@ void OpenItem(const base::FilePath& full_path) { ui::win::OpenFileViaShell(full_path); } -bool OpenExternal(const GURL& url) { +bool OpenExternal(const GURL& url, bool activate) { // Quote the input scheme to be sure that the command does not have // parameters unexpected by the external program. This url should already // have been escaped. diff --git a/atom/renderer/api/atom_api_spell_check_client.cc b/atom/renderer/api/atom_api_spell_check_client.cc index 25d1e30b0aa..08f36efce58 100644 --- a/atom/renderer/api/atom_api_spell_check_client.cc +++ b/atom/renderer/api/atom_api_spell_check_client.cc @@ -21,13 +21,11 @@ namespace api { namespace { -const int kMaxAutoCorrectWordSize = 8; - bool HasWordCharacters(const base::string16& text, int index) { const base::char16* data = text.data(); int length = text.length(); while (index < length) { - uint32 code = 0; + uint32_t code = 0; U16_NEXT(data, index, length, code); UErrorCode error = U_ZERO_ERROR; if (uscript_getScript(code, &error) != USCRIPT_COMMON) @@ -42,8 +40,7 @@ SpellCheckClient::SpellCheckClient(const std::string& language, bool auto_spell_correct_turned_on, v8::Isolate* isolate, v8::Local provider) - : auto_spell_correct_turned_on_(auto_spell_correct_turned_on), - isolate_(isolate), + : isolate_(isolate), provider_(isolate, provider) { character_attributes_.SetDefaultLanguage(language); @@ -96,14 +93,6 @@ void SpellCheckClient::requestCheckingOfText( completionCallback->didFinishCheckingText(results); } -blink::WebString SpellCheckClient::autoCorrectWord( - const blink::WebString& misspelledWord) { - if (auto_spell_correct_turned_on_) - return GetAutoCorrectionWord(base::string16(misspelledWord)); - else - return blink::WebString(); -} - void SpellCheckClient::showSpellingUI(bool show) { } @@ -170,53 +159,6 @@ bool SpellCheckClient::SpellCheckWord(const base::string16& word_to_check) { return true; } -base::string16 SpellCheckClient::GetAutoCorrectionWord( - const base::string16& word) { - base::string16 autocorrect_word; - - int word_length = static_cast(word.size()); - if (word_length < 2 || word_length > kMaxAutoCorrectWordSize) - return autocorrect_word; - - base::char16 misspelled_word[kMaxAutoCorrectWordSize + 1]; - const base::char16* word_char = word.c_str(); - for (int i = 0; i <= kMaxAutoCorrectWordSize; ++i) { - if (i >= word_length) - misspelled_word[i] = 0; - else - misspelled_word[i] = word_char[i]; - } - - // Swap adjacent characters and spellcheck. - int misspelling_start, misspelling_len; - for (int i = 0; i < word_length - 1; i++) { - // Swap. - std::swap(misspelled_word[i], misspelled_word[i + 1]); - - // Check spelling. - misspelling_start = misspelling_len = 0; - spellCheck(blink::WebString(misspelled_word, word_length), - misspelling_start, - misspelling_len, - NULL); - - // Make decision: if only one swap produced a valid word, then we want to - // return it. If we found two or more, we don't do autocorrection. - if (misspelling_len == 0) { - if (autocorrect_word.empty()) { - autocorrect_word.assign(misspelled_word); - } else { - autocorrect_word.clear(); - break; - } - } - - // Restore the swapped characters. - std::swap(misspelled_word[i], misspelled_word[i + 1]); - } - return autocorrect_word; -} - // Returns whether or not the given string is a valid contraction. // This function is a fall-back when the SpellcheckWordIterator class // returns a concatenated word which is not in the selected dictionary diff --git a/atom/renderer/api/atom_api_spell_check_client.h b/atom/renderer/api/atom_api_spell_check_client.h index 345cad186ac..af72756e2ec 100644 --- a/atom/renderer/api/atom_api_spell_check_client.h +++ b/atom/renderer/api/atom_api_spell_check_client.h @@ -41,8 +41,6 @@ class SpellCheckClient : public blink::WebSpellCheckClient { const blink::WebVector& markersInText, const blink::WebVector& markerOffsets, blink::WebTextCheckingCompletion* completionCallback) override; - blink::WebString autoCorrectWord( - const blink::WebString& misspelledWord) override; void showSpellingUI(bool show) override; bool isShowingSpellingUI() override; void updateSpellingUIWithMisspelledWord( diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index c72882886b9..e00b901bfff 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -15,7 +15,7 @@ #include "native_mate/object_template_builder.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" -#include "third_party/WebKit/public/web/WebScopedUserGesture.h" +#include "third_party/WebKit/public/web/WebScriptExecutionCallback.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" @@ -26,6 +26,34 @@ namespace atom { namespace api { +namespace { + +class ScriptExecutionCallback : public blink::WebScriptExecutionCallback { + public: + using CompletionCallback = + base::Callback& result)>; + + explicit ScriptExecutionCallback(const CompletionCallback& callback) + : callback_(callback) {} + ~ScriptExecutionCallback() {} + + void completed( + const blink::WebVector>& result) override { + if (!callback_.is_null() && !result.isEmpty() && !result[0].IsEmpty()) + // Right now only single results per frame is supported. + callback_.Run(result[0]); + delete this; + } + + private: + CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(ScriptExecutionCallback); +}; + +} // namespace + WebFrame::WebFrame() : web_frame_(blink::WebLocalFrame::frameForCurrentContext()) { } @@ -124,9 +152,14 @@ void WebFrame::ExecuteJavaScript(const base::string16& code, mate::Arguments* args) { bool has_user_gesture = false; args->GetNext(&has_user_gesture); - scoped_ptr gesture( - has_user_gesture ? new blink::WebScopedUserGesture : nullptr); - web_frame_->executeScriptAndReturnValue(blink::WebScriptSource(code)); + ScriptExecutionCallback::CompletionCallback completion_callback; + args->GetNext(&completion_callback); + scoped_ptr callback( + new ScriptExecutionCallback(completion_callback)); + web_frame_->requestExecuteScriptAndReturnValue( + blink::WebScriptSource(code), + has_user_gesture, + callback.release()); } mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( diff --git a/atom/renderer/api/lib/desktop-capturer.js b/atom/renderer/api/lib/desktop-capturer.js deleted file mode 100644 index 8912deea3a8..00000000000 --- a/atom/renderer/api/lib/desktop-capturer.js +++ /dev/null @@ -1,47 +0,0 @@ -const ipcRenderer = require('electron').ipcRenderer; -const nativeImage = require('electron').nativeImage; - -var nextId = 0; -var includes = [].includes; - -var getNextId = function() { - return ++nextId; -}; - -// |options.type| can not be empty and has to include 'window' or 'screen'. -var isValid = function(options) { - return ((options != null ? options.types : void 0) != null) && Array.isArray(options.types); -}; - -exports.getSources = function(options, callback) { - var captureScreen, captureWindow, id; - if (!isValid(options)) { - return callback(new Error('Invalid options')); - } - captureWindow = includes.call(options.types, 'window'); - captureScreen = includes.call(options.types, 'screen'); - if (options.thumbnailSize == null) { - options.thumbnailSize = { - width: 150, - height: 150 - }; - } - id = getNextId(); - ipcRenderer.send('ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id); - return ipcRenderer.once("ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_" + id, function(event, sources) { - var source; - return callback(null, (function() { - var i, len, results; - results = []; - for (i = 0, len = sources.length; i < len; i++) { - source = sources[i]; - results.push({ - id: source.id, - name: source.name, - thumbnail: nativeImage.createFromDataURL(source.thumbnail) - }); - } - return results; - })()); - }); -}; diff --git a/atom/renderer/api/lib/exports/electron.js b/atom/renderer/api/lib/exports/electron.js deleted file mode 100644 index 3f0d3254cb8..00000000000 --- a/atom/renderer/api/lib/exports/electron.js +++ /dev/null @@ -1,38 +0,0 @@ -const common = require('../../../../common/api/lib/exports/electron'); - -// 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'); - } - } -}); diff --git a/atom/renderer/api/lib/ipc-renderer.js b/atom/renderer/api/lib/ipc-renderer.js deleted file mode 100644 index dd614103cfa..00000000000 --- a/atom/renderer/api/lib/ipc-renderer.js +++ /dev/null @@ -1,27 +0,0 @@ -const binding = process.atomBinding('ipc'); -const v8Util = process.atomBinding('v8_util'); - -var slice = [].slice; - -// Created by init.coffee. -const ipcRenderer = v8Util.getHiddenValue(global, 'ipc'); - -ipcRenderer.send = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return binding.send('ipc-message', slice.call(args)); -}; - -ipcRenderer.sendSync = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return JSON.parse(binding.sendSync('ipc-message-sync', slice.call(args))); -}; - -ipcRenderer.sendToHost = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return binding.send('ipc-message-host', slice.call(args)); -}; - -module.exports = ipcRenderer; diff --git a/atom/renderer/api/lib/ipc.js b/atom/renderer/api/lib/ipc.js deleted file mode 100644 index a4bab33af81..00000000000 --- a/atom/renderer/api/lib/ipc.js +++ /dev/null @@ -1,31 +0,0 @@ -const ipcRenderer = require('electron').ipcRenderer; -const deprecate = require('electron').deprecate; -const EventEmitter = require('events').EventEmitter; - -var slice = [].slice; - -// This module is deprecated, we mirror everything from ipcRenderer. -deprecate.warn('ipc module', 'require("electron").ipcRenderer'); - -// Routes events of ipcRenderer. -var ipc = new EventEmitter; - -ipcRenderer.emit = function() { - var channel = arguments[0]; - var args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - ipc.emit.apply(ipc, [channel].concat(slice.call(args))); - return EventEmitter.prototype.emit.apply(ipcRenderer, arguments); -}; - -// Deprecated. -for (var method in ipcRenderer) { - if (method.startsWith('send')) { - ipc[method] = ipcRenderer[method]; - } -} - -deprecate.rename(ipc, 'sendChannel', 'send'); - -deprecate.rename(ipc, 'sendChannelSync', 'sendSync'); - -module.exports = ipc; diff --git a/atom/renderer/api/lib/remote.js b/atom/renderer/api/lib/remote.js deleted file mode 100644 index ad01e77ac51..00000000000 --- a/atom/renderer/api/lib/remote.js +++ /dev/null @@ -1,344 +0,0 @@ -const ipcRenderer = require('electron').ipcRenderer; -const CallbacksRegistry = require('electron').CallbacksRegistry; -const v8Util = process.atomBinding('v8_util'); - -const callbacksRegistry = new CallbacksRegistry; - -var includes = [].includes; - -// Check for circular reference. -var isCircular = function(field, visited) { - if (typeof field === 'object') { - if (includes.call(visited, field)) { - return true; - } - visited.push(field); - } - return false; -}; - -// Convert the arguments object into an array of meta data. -var wrapArgs = function(args, visited) { - var valueToMeta; - if (visited == null) { - visited = []; - } - valueToMeta = function(value) { - var field, prop, ret; - if (Array.isArray(value)) { - return { - type: 'array', - value: wrapArgs(value, visited) - }; - } else if (Buffer.isBuffer(value)) { - return { - type: 'buffer', - value: Array.prototype.slice.call(value, 0) - }; - } else if (value instanceof Date) { - return { - type: 'date', - value: value.getTime() - }; - } else if ((value != null ? value.constructor.name : void 0) === 'Promise') { - return { - type: 'promise', - then: valueToMeta(value.then.bind(value)) - }; - } else if ((value != null) && typeof value === 'object' && v8Util.getHiddenValue(value, 'atomId')) { - return { - type: 'remote-object', - id: v8Util.getHiddenValue(value, 'atomId') - }; - } else if ((value != null) && typeof value === 'object') { - ret = { - type: 'object', - name: value.constructor.name, - members: [] - }; - for (prop in value) { - field = value[prop]; - ret.members.push({ - name: prop, - value: valueToMeta(isCircular(field, visited) ? null : field) - }); - } - return ret; - } else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) { - return { - type: 'function-with-return-value', - value: valueToMeta(value()) - }; - } else if (typeof value === 'function') { - return { - type: 'function', - id: callbacksRegistry.add(value), - location: v8Util.getHiddenValue(value, 'location') - }; - } else { - return { - type: 'value', - value: value - }; - } - }; - return Array.prototype.slice.call(args).map(valueToMeta); -}; - -// Convert meta data from browser into real value. -var metaToValue = function(meta) { - var el, i, j, len, len1, member, ref1, ref2, results, ret; - switch (meta.type) { - case 'value': - return meta.value; - case 'array': - ref1 = meta.members; - results = []; - for (i = 0, len = ref1.length; i < len; i++) { - el = ref1[i]; - results.push(metaToValue(el)); - } - return results; - case 'buffer': - return new Buffer(meta.value); - case 'promise': - return Promise.resolve({ - then: metaToValue(meta.then) - }); - case 'error': - return metaToPlainObject(meta); - case 'date': - return new Date(meta.value); - case 'exception': - throw new Error(meta.message + "\n" + meta.stack); - default: - if (meta.type === 'function') { - // A shadow class to represent the remote function object. - ret = (function() { - function RemoteFunction() { - var obj; - if (this.constructor === RemoteFunction) { - - // Constructor call. - obj = ipcRenderer.sendSync('ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments)); - - /* - Returning object in constructor will replace constructed object - with the returned object. - http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this - */ - return metaToValue(obj); - } else { - - // Function call. - obj = ipcRenderer.sendSync('ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments)); - return metaToValue(obj); - } - } - - return RemoteFunction; - - })(); - } else { - ret = v8Util.createObjectWithName(meta.name); - } - - // Polulate delegate members. - ref2 = meta.members; - for (j = 0, len1 = ref2.length; j < len1; j++) { - member = ref2[j]; - if (member.type === 'function') { - ret[member.name] = createRemoteMemberFunction(meta.id, member.name); - } else { - Object.defineProperty(ret, member.name, createRemoteMemberProperty(meta.id, member.name)); - } - } - - // Track delegate object's life time, and tell the browser to clean up - // when the object is GCed. - v8Util.setDestructor(ret, function() { - return ipcRenderer.send('ATOM_BROWSER_DEREFERENCE', meta.id); - }); - - // Remember object's id. - v8Util.setHiddenValue(ret, 'atomId', meta.id); - return ret; - } -}; - -// Construct a plain object from the meta. -var metaToPlainObject = function(meta) { - var i, len, name, obj, ref1, ref2, value; - obj = (function() { - switch (meta.type) { - case 'error': - return new Error; - default: - return {}; - } - })(); - ref1 = meta.members; - for (i = 0, len = ref1.length; i < len; i++) { - ref2 = ref1[i], name = ref2.name, value = ref2.value; - obj[name] = value; - } - return obj; -}; - -// Create a RemoteMemberFunction instance. -// This function's content should not be inlined into metaToValue, otherwise V8 -// may consider it circular reference. -var createRemoteMemberFunction = function(metaId, name) { - return (function() { - function RemoteMemberFunction() { - var ret; - if (this.constructor === RemoteMemberFunction) { - - // Constructor call. - ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, name, wrapArgs(arguments)); - return metaToValue(ret); - } else { - - // Call member function. - ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CALL', metaId, name, wrapArgs(arguments)); - return metaToValue(ret); - } - } - - return RemoteMemberFunction; - - })(); -}; - -// Create configuration for defineProperty. -// This function's content should not be inlined into metaToValue, otherwise V8 -// may consider it circular reference. -var createRemoteMemberProperty = function(metaId, name) { - return { - enumerable: true, - configurable: false, - set: function(value) { - - // Set member data. - ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_SET', metaId, name, value); - return value; - }, - get: function() { - - // Get member data. - return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, name)); - } - }; -}; - -// Browser calls a callback in renderer. -ipcRenderer.on('ATOM_RENDERER_CALLBACK', function(event, id, args) { - return callbacksRegistry.apply(id, metaToValue(args)); -}); - -// A callback in browser is released. -ipcRenderer.on('ATOM_RENDERER_RELEASE_CALLBACK', function(event, id) { - return callbacksRegistry.remove(id); -}); - -// List all built-in modules in browser process. -const browserModules = require('../../../browser/api/lib/exports/electron'); - -// And add a helper receiver for each one. -var fn = function(name) { - return Object.defineProperty(exports, name, { - get: function() { - return exports.getBuiltin(name); - } - }); -}; -for (var name in browserModules) { - fn(name); -} - -// Get remote module. -// (Just like node's require, the modules are cached permanently, note that this -// is safe leak since the object is not expected to get freed in browser) -var moduleCache = {}; - -exports.require = function(module) { - var meta; - if (moduleCache[module] != null) { - return moduleCache[module]; - } - meta = ipcRenderer.sendSync('ATOM_BROWSER_REQUIRE', module); - return moduleCache[module] = metaToValue(meta); -}; - -// Optimize require('electron'). -moduleCache.electron = exports; - -// Alias to remote.require('electron').xxx. -var builtinCache = {}; - -exports.getBuiltin = function(module) { - var meta; - if (builtinCache[module] != null) { - return builtinCache[module]; - } - meta = ipcRenderer.sendSync('ATOM_BROWSER_GET_BUILTIN', module); - return builtinCache[module] = metaToValue(meta); -}; - -// Get current BrowserWindow object. -var windowCache = null; - -exports.getCurrentWindow = function() { - var meta; - if (windowCache != null) { - return windowCache; - } - meta = ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WINDOW'); - return windowCache = metaToValue(meta); -}; - -// Get current WebContents object. -var webContentsCache = null; - -exports.getCurrentWebContents = function() { - var meta; - if (webContentsCache != null) { - return webContentsCache; - } - meta = ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WEB_CONTENTS'); - return webContentsCache = metaToValue(meta); -}; - -// Get a global object in browser. -exports.getGlobal = function(name) { - var meta; - meta = ipcRenderer.sendSync('ATOM_BROWSER_GLOBAL', name); - return metaToValue(meta); -}; - -// Get the process object in browser. -var processCache = null; - -exports.__defineGetter__('process', function() { - if (processCache == null) { - processCache = exports.getGlobal('process'); - } - return processCache; -}); - -// Create a funtion that will return the specifed value when called in browser. -exports.createFunctionWithReturnValue = function(returnValue) { - var func; - func = function() { - return returnValue; - }; - v8Util.setHiddenValue(func, 'returnValue', true); - return func; -}; - -// Get the guest WebContents from guestInstanceId. -exports.getGuestWebContents = function(guestInstanceId) { - var meta; - meta = ipcRenderer.sendSync('ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId); - return metaToValue(meta); -}; diff --git a/atom/renderer/api/lib/screen.js b/atom/renderer/api/lib/screen.js deleted file mode 100644 index fb74d78f79d..00000000000 --- a/atom/renderer/api/lib/screen.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('electron').remote.screen; diff --git a/atom/renderer/api/lib/web-frame.js b/atom/renderer/api/lib/web-frame.js deleted file mode 100644 index 8ae35d7b8ac..00000000000 --- a/atom/renderer/api/lib/web-frame.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -const deprecate = require('electron').deprecate; -const EventEmitter = require('events').EventEmitter; - -const webFrame = process.atomBinding('web_frame').webFrame; - -// webFrame is an EventEmitter. -webFrame.__proto__ = EventEmitter.prototype; - -// Lots of webview would subscribe to webFrame's events. -webFrame.setMaxListeners(0); - -// Deprecated. -deprecate.rename(webFrame, 'registerUrlSchemeAsSecure', 'registerURLSchemeAsSecure'); -deprecate.rename(webFrame, 'registerUrlSchemeAsBypassingCSP', 'registerURLSchemeAsBypassingCSP'); -deprecate.rename(webFrame, 'registerUrlSchemeAsPrivileged', 'registerURLSchemeAsPrivileged'); - -module.exports = webFrame; diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index cdbdb3d7c3c..bbaea351378 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -39,7 +39,11 @@ bool GetIPCObject(v8::Isolate* isolate, v8::Local context, v8::Local* ipc) { v8::Local key = mate::StringToV8(isolate, "ipc"); - v8::Local value = context->Global()->GetHiddenValue(key); + v8::Local privateKey = v8::Private::ForApi(isolate, key); + v8::Local global_object = context->Global(); + v8::Local value; + if (!global_object->GetPrivate(context, privateKey).ToLocal(&value)) + return false; if (value.IsEmpty() || !value->IsObject()) return false; *ipc = value->ToObject(); diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 15165efa330..7746ce123e4 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -9,6 +9,7 @@ #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/node_bindings.h" #include "atom/common/node_includes.h" #include "atom/common/options_switches.h" @@ -24,6 +25,7 @@ #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 "third_party/WebKit/public/web/WebCustomElement.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" @@ -47,17 +49,27 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { AtomRenderFrameObserver(content::RenderFrame* frame, AtomRendererClient* renderer_client) : content::RenderFrameObserver(frame), + world_id_(-1), renderer_client_(renderer_client) {} // content::RenderFrameObserver: void DidCreateScriptContext(v8::Handle context, int extension_group, - int world_id) { - renderer_client_->DidCreateScriptContext( - render_frame()->GetWebFrame(), context); + int world_id) override { + if (world_id_ != -1 && world_id_ != world_id) + return; + world_id_ = world_id; + renderer_client_->DidCreateScriptContext(context); + } + void WillReleaseScriptContext(v8::Local context, + int world_id) override { + if (world_id_ != world_id) + return; + renderer_client_->WillReleaseScriptContext(context); } private: + int world_id_; AtomRendererClient* renderer_client_; DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); @@ -78,18 +90,6 @@ void AtomRendererClient::WebKitInitialized() { blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); OverrideNodeArrayBuffer(); - - node_bindings_->Initialize(); - node_bindings_->PrepareMessageLoop(); - - DCHECK(!global_env); - - // Create a default empty environment which would be used when we need to - // run V8 code out of a window context (like running a uv callback). - v8::Isolate* isolate = blink::mainThreadIsolate(); - v8::HandleScope handle_scope(isolate); - v8::Local context = v8::Context::New(isolate); - global_env = node::Environment::New(context, uv_default_loop()); } void AtomRendererClient::RenderThreadStarted() { @@ -108,13 +108,21 @@ void AtomRendererClient::RenderThreadStarted() { void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { new PepperHelper(render_frame); - new AtomRenderFrameObserver(render_frame, this); // Allow file scheme to handle service worker by default. blink::WebSecurityPolicy::registerURLSchemeAsAllowingServiceWorkers("file"); + + // Only insert node integration for the main frame. + if (!render_frame->IsMainFrame()) + return; + + new AtomRenderFrameObserver(render_frame, this); } void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) { + // Set default UA-dependent background as transparent. + render_view->GetWebView()->setBaseBackgroundColor(SK_ColorTRANSPARENT); + new printing::PrintWebViewHelper(render_view); new AtomRenderViewObserver(render_view, this); } @@ -139,14 +147,15 @@ bool AtomRendererClient::OverrideCreatePlugin( } void AtomRendererClient::DidCreateScriptContext( - blink::WebFrame* frame, v8::Handle context) { - // Only insert node integration for the main frame. - if (frame->parent()) - return; + // Whether the node binding has been initialized. + bool first_time = node_bindings_->uv_env() == nullptr; - // Give the node loop a run to make sure everything is ready. - node_bindings_->RunMessageLoop(); + // Prepare the node bindings. + if (first_time) { + node_bindings_->Initialize(); + node_bindings_->PrepareMessageLoop(); + } // Setup node environment for each window. node::Environment* env = node_bindings_->CreateEnvironment(context); @@ -154,12 +163,22 @@ void AtomRendererClient::DidCreateScriptContext( // Add atom-shell extended APIs. atom_bindings_->BindTo(env->isolate(), env->process_object()); - // Make uv loop being wrapped by window context. - if (node_bindings_->uv_env() == nullptr) - node_bindings_->set_uv_env(env); - // Load everything. node_bindings_->LoadEnvironment(env); + + if (first_time) { + // 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 AtomRendererClient::WillReleaseScriptContext( + v8::Handle context) { + node::Environment* env = node::Environment::GetCurrent(context); + mate::EmitEvent(env->isolate(), env->process_object(), "exit"); } bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, @@ -173,7 +192,7 @@ bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, // the OpenURLFromTab is triggered, which means form posting would not work, // we should solve this by patching Chromium in future. *send_referrer = true; - return http_method == "GET" && !is_server_redirect; + return http_method == "GET"; } content::BrowserPluginDelegate* AtomRendererClient::CreateBrowserPluginDelegate( diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index beeeb9d530b..59b407ba1ef 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -22,8 +22,8 @@ class AtomRendererClient : public content::ContentRendererClient, AtomRendererClient(); virtual ~AtomRendererClient(); - void DidCreateScriptContext(blink::WebFrame* frame, - v8::Handle context); + void DidCreateScriptContext(v8::Handle context); + void WillReleaseScriptContext(v8::Handle context); private: enum NodeIntegration { diff --git a/atom/renderer/lib/chrome-api.js b/atom/renderer/lib/chrome-api.js deleted file mode 100644 index 7ff7d2e87b9..00000000000 --- a/atom/renderer/lib/chrome-api.js +++ /dev/null @@ -1,13 +0,0 @@ -const url = require('url'); -const chrome = window.chrome = window.chrome || {}; - -chrome.extension = { - getURL: function(path) { - return url.format({ - protocol: location.protocol, - slashes: true, - hostname: location.hostname, - pathname: path - }); - } -}; diff --git a/atom/renderer/lib/init.js b/atom/renderer/lib/init.js deleted file mode 100644 index d25dbfbbbb8..00000000000 --- a/atom/renderer/lib/init.js +++ /dev/null @@ -1,136 +0,0 @@ -'user strict'; - -const events = require('events'); -const path = require('path'); -const Module = require('module'); - -// We modified the original process.argv to let node.js load the -// atom-renderer.js, we need to restore it here. -process.argv.splice(1, 1); - -// Clear search paths. -require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'reset-search-paths')); - -// Import common settings. -require(path.resolve(__dirname, '..', '..', 'common', 'lib', 'init')); - -var globalPaths = Module.globalPaths; - -if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { - globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib')); -} - -// Expose public APIs. -globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib', 'exports')); - -// The global variable will be used by ipc for event dispatching -var v8Util = process.atomBinding('v8_util'); - -v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter); - -// Use electron module after everything is ready. -const electron = require('electron'); - -// Call webFrame method. -electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => { - electron.webFrame[method].apply(electron.webFrame, args); -}); - -// Process command line arguments. -var nodeIntegration = 'false'; -var preloadScript = null; - -var ref = process.argv; -var i, len, arg; -for (i = 0, len = ref.length; i < len; i++) { - arg = ref[i]; - if (arg.indexOf('--guest-instance-id=') === 0) { - // This is a guest web view. - process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1)); - } else if (arg.indexOf('--opener-id=') === 0) { - // This is a guest BrowserWindow. - process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1)); - } else if (arg.indexOf('--node-integration=') === 0) { - nodeIntegration = arg.substr(arg.indexOf('=') + 1); - } else if (arg.indexOf('--preload=') === 0) { - preloadScript = arg.substr(arg.indexOf('=') + 1); - } -} - -if (location.protocol === 'chrome-devtools:') { - // Override some inspector APIs. - require('./inspector'); - nodeIntegration = 'true'; -} else if (location.protocol === 'chrome-extension:') { - // Add implementations of chrome API. - require('./chrome-api'); - nodeIntegration = 'true'; -} else { - // Override default web functions. - require('./override'); - - // Load webview tag implementation. - if (process.guestInstanceId == null) { - require('./web-view/web-view'); - require('./web-view/web-view-attributes'); - } -} - -if (nodeIntegration === 'true' || nodeIntegration === 'all' || nodeIntegration === 'except-iframe' || nodeIntegration === 'manual-enable-iframe') { - // 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 (window.location.protocol === 'file:') { - var pathname = process.platform === 'win32' && window.location.pathname[0] === '/' ? window.location.pathname.substr(1) : window.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; - } - - // Redirect window.onerror to uncaughtException. - window.onerror = function(message, filename, lineno, colno, error) { - if (global.process.listeners('uncaughtException').length > 0) { - global.process.emit('uncaughtException', error); - return true; - } else { - return false; - } - }; - - // Emit the 'exit' event when page is unloading. - window.addEventListener('unload', function() { - return process.emit('exit'); - }); -} else { - // Delete Node's symbols after the Environment has been loaded. - process.once('loaded', function() { - delete global.process; - delete global.setImmediate; - delete global.clearImmediate; - return delete global.global; - }); -} - -// Load the script specfied by the "preload" attribute. -if (preloadScript) { - try { - require(preloadScript); - } catch (error) { - if (error.code === 'MODULE_NOT_FOUND') { - console.error("Unable to load preload script " + preloadScript); - } else { - console.error(error); - console.error(error.stack); - } - } -} diff --git a/atom/renderer/lib/inspector.js b/atom/renderer/lib/inspector.js deleted file mode 100644 index edec3074b95..00000000000 --- a/atom/renderer/lib/inspector.js +++ /dev/null @@ -1,81 +0,0 @@ -window.onload = function() { - // Use menu API to show context menu. - InspectorFrontendHost.showContextMenuAtPoint = createMenu; - - // Use dialog API to override file chooser dialog. - return WebInspector.createFileSelectorElement = createFileSelectorElement; -}; - -var convertToMenuTemplate = function(items) { - var fn, i, item, len, template; - template = []; - fn = function(item) { - var transformed; - transformed = item.type === 'subMenu' ? { - type: 'submenu', - label: item.label, - enabled: item.enabled, - submenu: convertToMenuTemplate(item.subItems) - } : item.type === 'separator' ? { - type: 'separator' - } : item.type === 'checkbox' ? { - type: 'checkbox', - label: item.label, - enabled: item.enabled, - checked: item.checked - } : { - type: 'normal', - label: item.label, - enabled: item.enabled - }; - if (item.id != null) { - transformed.click = function() { - DevToolsAPI.contextMenuItemSelected(item.id); - return DevToolsAPI.contextMenuCleared(); - }; - } - return template.push(transformed); - }; - for (i = 0, len = items.length; i < len; i++) { - item = items[i]; - fn(item); - } - return template; -}; - -var createMenu = function(x, y, items) { - const remote = require('electron').remote; - const Menu = remote.Menu; - const menu = Menu.buildFromTemplate(convertToMenuTemplate(items)); - - // The menu is expected to show asynchronously. - return setTimeout(function() { - return menu.popup(remote.getCurrentWindow()); - }); -}; - -var showFileChooserDialog = function(callback) { - var dialog, files, remote; - remote = require('electron').remote; - dialog = remote.dialog; - files = dialog.showOpenDialog({}); - if (files != null) { - return callback(pathToHtml5FileObject(files[0])); - } -}; - -var pathToHtml5FileObject = function(path) { - var blob, fs; - fs = require('fs'); - blob = new Blob([fs.readFileSync(path)]); - blob.name = path; - return blob; -}; - -var createFileSelectorElement = function(callback) { - var fileSelectorElement; - fileSelectorElement = document.createElement('span'); - fileSelectorElement.style.display = 'none'; - fileSelectorElement.click = showFileChooserDialog.bind(this, callback); - return fileSelectorElement; -}; diff --git a/atom/renderer/lib/override.js b/atom/renderer/lib/override.js deleted file mode 100644 index 6ff1e80785d..00000000000 --- a/atom/renderer/lib/override.js +++ /dev/null @@ -1,230 +0,0 @@ -const ipcRenderer = require('electron').ipcRenderer; -const remote = require('electron').remote; - -var slice = [].slice; - -// Helper function to resolve relative url. -var a = window.top.document.createElement('a'); - -var resolveURL = function(url) { - a.href = url; - return a.href; -}; - -// Window object returned by "window.open". -var BrowserWindowProxy = (function() { - BrowserWindowProxy.proxies = {}; - - BrowserWindowProxy.getOrCreate = function(guestId) { - var base; - return (base = this.proxies)[guestId] != null ? base[guestId] : base[guestId] = new BrowserWindowProxy(guestId); - }; - - BrowserWindowProxy.remove = function(guestId) { - return delete this.proxies[guestId]; - }; - - function BrowserWindowProxy(guestId1) { - this.guestId = guestId1; - this.closed = false; - ipcRenderer.once("ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_" + this.guestId, (function(_this) { - return function() { - BrowserWindowProxy.remove(_this.guestId); - return _this.closed = true; - }; - })(this)); - } - - BrowserWindowProxy.prototype.close = function() { - return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId); - }; - - BrowserWindowProxy.prototype.focus = function() { - return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus'); - }; - - BrowserWindowProxy.prototype.blur = function() { - return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur'); - }; - - BrowserWindowProxy.prototype.postMessage = function(message, targetOrigin) { - if (targetOrigin == null) { - targetOrigin = '*'; - } - return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, location.origin); - }; - - BrowserWindowProxy.prototype["eval"] = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript'].concat(slice.call(args))); - }; - - return BrowserWindowProxy; - -})(); - -if (process.guestInstanceId == null) { - // Override default window.close. - window.close = function() { - return remote.getCurrentWindow().close(); - }; -} - -// Make the browser window or guest view emit "new-window" event. -window.open = function(url, frameName, features) { - var feature, guestId, i, ints, j, len, len1, name, options, ref1, ref2, value; - if (frameName == null) { - frameName = ''; - } - if (features == null) { - features = ''; - } - options = {}; - ints = ['x', 'y', 'width', 'height', 'min-width', 'max-width', 'min-height', 'max-height', 'zoom-factor']; - - // Make sure to get rid of excessive whitespace in the property name - ref1 = features.split(/,\s*/); - for (i = 0, len = ref1.length; i < len; i++) { - feature = ref1[i]; - ref2 = feature.split(/\s*=/), name = ref2[0], value = ref2[1]; - options[name] = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value; - } - if (options.left) { - if (options.x == null) { - options.x = options.left; - } - } - if (options.top) { - if (options.y == null) { - options.y = options.top; - } - } - if (options.title == null) { - options.title = frameName; - } - if (options.width == null) { - options.width = 800; - } - if (options.height == null) { - options.height = 600; - } - - // Resolve relative urls. - url = resolveURL(url); - for (j = 0, len1 = ints.length; j < len1; j++) { - name = ints[j]; - if (options[name] != null) { - options[name] = parseInt(options[name], 10); - } - } - guestId = ipcRenderer.sendSync('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options); - if (guestId) { - return BrowserWindowProxy.getOrCreate(guestId); - } else { - return null; - } -}; - -// Use the dialog API to implement alert(). -window.alert = function(message, title) { - var buttons; - if (title == null) { - title = ''; - } - buttons = ['OK']; - message = message.toString(); - remote.dialog.showMessageBox(remote.getCurrentWindow(), { - message: message, - title: title, - buttons: buttons - }); - - // Alert should always return undefined. -}; - -// And the confirm(). -window.confirm = function(message, title) { - var buttons, cancelId; - if (title == null) { - title = ''; - } - buttons = ['OK', 'Cancel']; - cancelId = 1; - return !remote.dialog.showMessageBox(remote.getCurrentWindow(), { - message: message, - title: title, - buttons: buttons, - cancelId: cancelId - }); -}; - -// But we do not support prompt(). -window.prompt = function() { - throw new Error('prompt() is and will not be supported.'); -}; - -if (process.openerId != null) { - window.opener = BrowserWindowProxy.getOrCreate(process.openerId); -} - -ipcRenderer.on('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', function(event, sourceId, message, sourceOrigin) { - // Manually dispatch event instead of using postMessage because we also need to - // set event.source. - event = document.createEvent('Event'); - event.initEvent('message', false, false); - event.data = message; - event.origin = sourceOrigin; - event.source = BrowserWindowProxy.getOrCreate(sourceId); - return window.dispatchEvent(event); -}); - -// Forward history operations to browser. -var sendHistoryOperation = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_NAVIGATION_CONTROLLER'].concat(slice.call(args))); -}; - -var getHistoryOperation = function() { - var args; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - return ipcRenderer.sendSync.apply(ipcRenderer, ['ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER'].concat(slice.call(args))); -}; - -window.history.back = function() { - return sendHistoryOperation('goBack'); -}; - -window.history.forward = function() { - return sendHistoryOperation('goForward'); -}; - -window.history.go = function(offset) { - return sendHistoryOperation('goToOffset', offset); -}; - -Object.defineProperty(window.history, 'length', { - get: function() { - return getHistoryOperation('length'); - } -}); - -// Make document.hidden and document.visibilityState return the correct value. -Object.defineProperty(document, 'hidden', { - get: function() { - var currentWindow; - currentWindow = remote.getCurrentWindow(); - return currentWindow.isMinimized() || !currentWindow.isVisible(); - } -}); - -Object.defineProperty(document, 'visibilityState', { - get: function() { - if (document.hidden) { - return "hidden"; - } else { - return "visible"; - } - } -}); diff --git a/atom/renderer/lib/web-view/guest-view-internal.js b/atom/renderer/lib/web-view/guest-view-internal.js deleted file mode 100644 index a7427abd631..00000000000 --- a/atom/renderer/lib/web-view/guest-view-internal.js +++ /dev/null @@ -1,112 +0,0 @@ -const ipcRenderer = require('electron').ipcRenderer; -const webFrame = require('electron').webFrame; - -var slice = [].slice; -var requestId = 0; - -var WEB_VIEW_EVENTS = { - 'load-commit': ['url', 'isMainFrame'], - 'did-finish-load': [], - 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL'], - 'did-frame-finish-load': ['isMainFrame'], - 'did-start-loading': [], - 'did-stop-loading': [], - 'did-get-response-details': ['status', 'newURL', 'originalURL', 'httpResponseCode', 'requestMethod', 'referrer', 'headers'], - 'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'], - 'dom-ready': [], - 'console-message': ['level', 'message', 'line', 'sourceId'], - 'devtools-opened': [], - 'devtools-closed': [], - 'devtools-focused': [], - 'new-window': ['url', 'frameName', 'disposition', 'options'], - 'will-navigate': ['url'], - 'did-navigate': ['url'], - 'did-navigate-in-page': ['url'], - 'close': [], - 'crashed': [], - 'gpu-crashed': [], - 'plugin-crashed': ['name', 'version'], - 'media-started-playing': [], - 'media-paused': [], - 'did-change-theme-color': ['themeColor'], - 'destroyed': [], - 'page-title-updated': ['title', 'explicitSet'], - 'page-favicon-updated': ['favicons'], - 'enter-html-full-screen': [], - 'leave-html-full-screen': [], - 'found-in-page': ['result'] -}; - -var DEPRECATED_EVENTS = { - 'page-title-updated': 'page-title-set' -}; - -var dispatchEvent = function() { - var args, domEvent, eventKey, eventName, f, i, j, len, ref1, webView; - webView = arguments[0], eventName = arguments[1], eventKey = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : []; - if (DEPRECATED_EVENTS[eventName] != null) { - dispatchEvent.apply(null, [webView, DEPRECATED_EVENTS[eventName], eventKey].concat(slice.call(args))); - } - domEvent = new Event(eventName); - ref1 = WEB_VIEW_EVENTS[eventKey]; - for (i = j = 0, len = ref1.length; j < len; i = ++j) { - f = ref1[i]; - domEvent[f] = args[i]; - } - webView.dispatchEvent(domEvent); - if (eventName === 'load-commit') { - return webView.onLoadCommit(domEvent); - } -}; - -module.exports = { - registerEvents: function(webView, viewInstanceId) { - ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + viewInstanceId, function() { - var eventName = arguments[1]; - var args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - return dispatchEvent.apply(null, [webView, eventName, eventName].concat(slice.call(args))); - }); - ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + viewInstanceId, function() { - var channel = arguments[1]; - var args = 3 <= arguments.length ? slice.call(arguments, 2) : []; - var domEvent = new Event('ipc-message'); - domEvent.channel = channel; - domEvent.args = slice.call(args); - return webView.dispatchEvent(domEvent); - }); - return ipcRenderer.on("ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + viewInstanceId, function() { - var args, domEvent, f, i, j, len, ref1; - args = 2 <= arguments.length ? slice.call(arguments, 1) : []; - domEvent = new Event('size-changed'); - ref1 = ['oldWidth', 'oldHeight', 'newWidth', 'newHeight']; - for (i = j = 0, len = ref1.length; j < len; i = ++j) { - f = ref1[i]; - domEvent[f] = args[i]; - } - return webView.onSizeChanged(domEvent); - }); - }, - deregisterEvents: function(viewInstanceId) { - ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-" + viewInstanceId); - ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-" + viewInstanceId); - return ipcRenderer.removeAllListeners("ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-" + viewInstanceId); - }, - createGuest: function(params, callback) { - requestId++; - ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId); - return ipcRenderer.once("ATOM_SHELL_RESPONSE_" + requestId, callback); - }, - attachGuest: function(elementInstanceId, guestInstanceId, params) { - ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params); - return webFrame.attachGuest(elementInstanceId); - }, - destroyGuest: function(guestInstanceId) { - return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId); - }, - setSize: function(guestInstanceId, params) { - return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params); - }, - setAllowTransparency: function(guestInstanceId, allowtransparency) { - return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', guestInstanceId, allowtransparency); - } -}; diff --git a/atom/renderer/lib/web-view/web-view.js b/atom/renderer/lib/web-view/web-view.js deleted file mode 100644 index 4aa4632d35e..00000000000 --- a/atom/renderer/lib/web-view/web-view.js +++ /dev/null @@ -1,457 +0,0 @@ -'user strict'; - -const deprecate = require('electron').deprecate; -const webFrame = require('electron').webFrame; -const remote = require('electron').remote; -const ipcRenderer = require('electron').ipcRenderer; - -const v8Util = process.atomBinding('v8_util'); -const guestViewInternal = require('./guest-view-internal'); -const webViewConstants = require('./web-view-constants'); - -var hasProp = {}.hasOwnProperty; -var slice = [].slice; - -// ID generator. -var nextId = 0; - -var getNextId = function() { - return ++nextId; -}; - -// Represents the internal state of the WebView node. -var WebViewImpl = (function() { - function WebViewImpl(webviewNode) { - var shadowRoot; - this.webviewNode = webviewNode; - v8Util.setHiddenValue(this.webviewNode, 'internal', this); - this.attached = false; - this.elementAttached = false; - this.beforeFirstNavigation = true; - - // on* Event handlers. - this.on = {}; - this.browserPluginNode = this.createBrowserPluginNode(); - shadowRoot = this.webviewNode.createShadowRoot(); - this.setupWebViewAttributes(); - 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); - } - - WebViewImpl.prototype.createBrowserPluginNode = function() { - // We create BrowserPlugin as a custom element in order to observe changes - // to attributes synchronously. - var browserPluginNode; - browserPluginNode = new WebViewImpl.BrowserPlugin(); - v8Util.setHiddenValue(browserPluginNode, 'internal', this); - return browserPluginNode; - }; - - // Resets some state upon reattaching element to the DOM. - WebViewImpl.prototype.reset = function() { - // Unlisten the zoom-level-changed event. - webFrame.removeListener('zoom-level-changed', this.onZoomLevelChanged); - - // 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 - // guestInstanceId has yet to be initialized. This means that we have not - // heard back from createGuest yet. We will not reset the flag in this case so - // that we don't end up allocating a second guest. - if (this.guestInstanceId) { - guestViewInternal.destroyGuest(this.guestInstanceId); - this.webContents = null; - this.guestInstanceId = void 0; - this.beforeFirstNavigation = true; - this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true; - } - return this.internalInstanceId = 0; - }; - - // Sets the .request property. - WebViewImpl.prototype.setRequestPropertyOnWebViewNode = function(request) { - return Object.defineProperty(this.webviewNode, 'request', { - value: request, - enumerable: true - }); - }; - - WebViewImpl.prototype.setupFocusPropagation = function() { - if (!this.webviewNode.hasAttribute('tabIndex')) { - - // needs a tabIndex in order to be focusable. - // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute - // to allow to be focusable. - // See http://crbug.com/231664. - this.webviewNode.setAttribute('tabIndex', -1); - } - this.webviewNode.addEventListener('focus', (function(_this) { - return function() { - // Focus the BrowserPlugin when the takes focus. - return _this.browserPluginNode.focus(); - }; - })(this)); - return this.webviewNode.addEventListener('blur', (function(_this) { - return function() { - // Blur the BrowserPlugin when the loses focus. - return _this.browserPluginNode.blur(); - }; - })(this)); - }; - - - // This observer monitors mutations to attributes of the and - // updates the BrowserPlugin properties accordingly. In turn, updating - // a BrowserPlugin property will update the corresponding BrowserPlugin - // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more - // details. - WebViewImpl.prototype.handleWebviewAttributeMutation = function(attributeName, oldValue, newValue) { - if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) { - return; - } - - // Let the changed attribute handle its own mutation; - return this.attributes[attributeName].handleMutation(oldValue, newValue); - }; - - WebViewImpl.prototype.handleBrowserPluginAttributeMutation = function(attributeName, oldValue, newValue) { - if (attributeName === webViewConstants.ATTRIBUTE_INTERNALINSTANCEID && !oldValue && !!newValue) { - this.browserPluginNode.removeAttribute(webViewConstants.ATTRIBUTE_INTERNALINSTANCEID); - this.internalInstanceId = parseInt(newValue); - - // Track when the element resizes using the element resize callback. - webFrame.registerElementResizeCallback(this.internalInstanceId, this.onElementResize.bind(this)); - if (!this.guestInstanceId) { - return; - } - return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()); - } - }; - - WebViewImpl.prototype.onSizeChanged = function(webViewEvent) { - var maxHeight, maxWidth, minHeight, minWidth, newHeight, newWidth, node, width; - newWidth = webViewEvent.newWidth; - newHeight = webViewEvent.newHeight; - node = this.webviewNode; - width = node.offsetWidth; - - // Check the current bounds to make sure we do not resize - // outside of current constraints. - maxWidth = this.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width; - maxHeight = this.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width; - minWidth = this.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width; - minHeight = this.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width; - minWidth = Math.min(minWidth, maxWidth); - minHeight = Math.min(minHeight, maxHeight); - if (!this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() || (newWidth >= minWidth && newWidth <= maxWidth && newHeight >= minHeight && newHeight <= maxHeight)) { - node.style.width = newWidth + 'px'; - node.style.height = newHeight + 'px'; - - // Only fire the DOM event if the size of the has actually - // changed. - return this.dispatchEvent(webViewEvent); - } - }; - - WebViewImpl.prototype.onElementResize = function(newSize) { - // Dispatch the 'resize' event. - var resizeEvent; - resizeEvent = new Event('resize', { - bubbles: true - }); - resizeEvent.newWidth = newSize.width; - resizeEvent.newHeight = newSize.height; - this.dispatchEvent(resizeEvent); - if (this.guestInstanceId) { - return guestViewInternal.setSize(this.guestInstanceId, { - normal: newSize - }); - } - }; - - WebViewImpl.prototype.createGuest = function() { - return guestViewInternal.createGuest(this.buildParams(), (function(_this) { - return function(event, guestInstanceId) { - return _this.attachWindow(guestInstanceId); - }; - })(this)); - }; - - WebViewImpl.prototype.dispatchEvent = function(webViewEvent) { - return this.webviewNode.dispatchEvent(webViewEvent); - }; - - // Adds an 'on' property on the webview, which can be used to set/unset - // an event handler. - WebViewImpl.prototype.setupEventProperty = function(eventName) { - var propertyName; - propertyName = 'on' + eventName.toLowerCase(); - return Object.defineProperty(this.webviewNode, propertyName, { - get: (function(_this) { - return function() { - return _this.on[propertyName]; - }; - })(this), - set: (function(_this) { - return function(value) { - if (_this.on[propertyName]) { - _this.webviewNode.removeEventListener(eventName, _this.on[propertyName]); - } - _this.on[propertyName] = value; - if (value) { - return _this.webviewNode.addEventListener(eventName, value); - } - }; - })(this), - enumerable: true - }); - }; - - // Updates state upon loadcommit. - WebViewImpl.prototype.onLoadCommit = function(webViewEvent) { - var newValue, oldValue; - oldValue = this.webviewNode.getAttribute(webViewConstants.ATTRIBUTE_SRC); - newValue = webViewEvent.url; - if (webViewEvent.isMainFrame && (oldValue !== newValue)) { - - // Touching the src attribute triggers a navigation. To avoid - // triggering a page reload on every guest-initiated navigation, - // we do not handle this mutation. - return this.attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue); - } - }; - - WebViewImpl.prototype.onAttach = function(storagePartitionId) { - return this.attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue(storagePartitionId); - }; - - WebViewImpl.prototype.buildParams = function() { - var attribute, attributeName, css, elementRect, params, ref1; - params = { - instanceId: this.viewInstanceId, - userAgentOverride: this.userAgentOverride - }; - ref1 = this.attributes; - for (attributeName in ref1) { - if (!hasProp.call(ref1, attributeName)) continue; - attribute = ref1[attributeName]; - params[attributeName] = attribute.getValue(); - } - - // When the WebView is not participating in layout (display:none) - // then getBoundingClientRect() would report a width and height of 0. - // However, in the case where the WebView has a fixed size we can - // use that value to initially size the guest so as to avoid a relayout of - // the on display:block. - css = window.getComputedStyle(this.webviewNode, null); - elementRect = this.webviewNode.getBoundingClientRect(); - params.elementWidth = parseInt(elementRect.width) || parseInt(css.getPropertyValue('width')); - params.elementHeight = parseInt(elementRect.height) || parseInt(css.getPropertyValue('height')); - return params; - }; - - WebViewImpl.prototype.attachWindow = function(guestInstanceId) { - this.guestInstanceId = guestInstanceId; - this.webContents = remote.getGuestWebContents(this.guestInstanceId); - if (!this.internalInstanceId) { - return true; - } - return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()); - }; - - return WebViewImpl; - -})(); - -// Registers browser plugin custom element. -var registerBrowserPluginElement = function() { - var proto; - proto = Object.create(HTMLObjectElement.prototype); - proto.createdCallback = function() { - this.setAttribute('type', 'application/browser-plugin'); - this.setAttribute('id', 'browser-plugin-' + getNextId()); - - // The node fills in the container. - this.style.display = 'block'; - this.style.width = '100%'; - return this.style.height = '100%'; - }; - proto.attributeChangedCallback = function(name, oldValue, newValue) { - var internal; - internal = v8Util.getHiddenValue(this, 'internal'); - if (!internal) { - return; - } - return internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue); - }; - proto.attachedCallback = function() { - // Load the plugin immediately. - return this.nonExistentAttribute; - }; - WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement('browserplugin', { - "extends": 'object', - prototype: proto - }); - delete proto.createdCallback; - delete proto.attachedCallback; - delete proto.detachedCallback; - return delete proto.attributeChangedCallback; -}; - -// Registers custom element. -var registerWebViewElement = function() { - var createBlockHandler, createNonBlockHandler, i, j, len, len1, m, methods, nonblockMethods, proto; - 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'); - if (!internal) { - return; - } - return internal.handleWebviewAttributeMutation(name, oldValue, newValue); - }; - proto.detachedCallback = function() { - var internal; - internal = v8Util.getHiddenValue(this, 'internal'); - if (!internal) { - return; - } - guestViewInternal.deregisterEvents(internal.viewInstanceId); - internal.elementAttached = false; - return internal.reset(); - }; - proto.attachedCallback = function() { - var internal; - internal = v8Util.getHiddenValue(this, 'internal'); - if (!internal) { - return; - } - if (!internal.elementAttached) { - guestViewInternal.registerEvents(internal, internal.viewInstanceId); - internal.elementAttached = true; - return internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse(); - } - }; - - // Public-facing API methods. - methods = [ - 'getURL', - 'loadURL', - 'getTitle', - 'isLoading', - 'isWaitingForResponse', - 'stop', - 'reload', - 'reloadIgnoringCache', - 'canGoBack', - 'canGoForward', - 'canGoToOffset', - 'clearHistory', - 'goBack', - 'goForward', - 'goToIndex', - 'goToOffset', - 'isCrashed', - 'setUserAgent', - 'getUserAgent', - 'openDevTools', - 'closeDevTools', - 'isDevToolsOpened', - 'isDevToolsFocused', - 'inspectElement', - 'setAudioMuted', - 'isAudioMuted', - 'undo', - 'redo', - 'cut', - 'copy', - 'paste', - 'pasteAndMatchStyle', - 'delete', - 'selectAll', - 'unselect', - 'replace', - 'replaceMisspelling', - 'findInPage', - 'stopFindInPage', - 'getId', - 'downloadURL', - 'inspectServiceWorker', - 'print', - 'printToPDF' - ]; - nonblockMethods = [ - 'executeJavaScript', - 'insertCSS', - 'insertText', - 'send', - 'sendInputEvent', - 'setZoomFactor', - 'setZoomLevel', - 'setZoomLevelLimits', - ]; - - // Forward proto.foo* method calls to WebViewImpl.foo*. - createBlockHandler = function(m) { - return function() { - var args, internal, ref1; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - internal = v8Util.getHiddenValue(this, 'internal'); - return (ref1 = internal.webContents)[m].apply(ref1, args); - }; - }; - for (i = 0, len = methods.length; i < len; i++) { - m = methods[i]; - proto[m] = createBlockHandler(m); - } - createNonBlockHandler = function(m) { - return function() { - var args, internal; - args = 1 <= arguments.length ? slice.call(arguments, 0) : []; - internal = v8Util.getHiddenValue(this, 'internal'); - return ipcRenderer.send.apply(ipcRenderer, ['ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', internal.guestInstanceId, m].concat(slice.call(args))); - }; - }; - for (j = 0, len1 = nonblockMethods.length; j < len1; j++) { - m = nonblockMethods[j]; - proto[m] = createNonBlockHandler(m); - } - - // Deprecated. - deprecate.rename(proto, 'getUrl', 'getURL'); - window.WebView = webFrame.registerEmbedderCustomElement('webview', { - prototype: proto - }); - - // Delete the callbacks so developers cannot call them and produce unexpected - // behavior. - delete proto.createdCallback; - delete proto.attachedCallback; - delete proto.detachedCallback; - return delete proto.attributeChangedCallback; -}; - -var useCapture = true; - -var listener = function(event) { - if (document.readyState === 'loading') { - return; - } - registerBrowserPluginElement(); - registerWebViewElement(); - return window.removeEventListener(event.type, listener, useCapture); -}; - -window.addEventListener('readystatechange', listener, true); - -module.exports = WebViewImpl; diff --git a/atom/renderer/node_array_buffer_bridge.cc b/atom/renderer/node_array_buffer_bridge.cc index 80f2530524d..c4b8d26adaa 100644 --- a/atom/renderer/node_array_buffer_bridge.cc +++ b/atom/renderer/node_array_buffer_bridge.cc @@ -4,7 +4,7 @@ #include "atom/renderer/node_array_buffer_bridge.h" -#include "base/basictypes.h" +#include "base/macros.h" #include "atom/common/node_includes.h" #include "native_mate/converter.h" #include "third_party/WebKit/public/web/WebArrayBuffer.h" diff --git a/chromium_src/chrome/browser/browser_process.h b/chromium_src/chrome/browser/browser_process.h index f971320f821..8baa899f7ad 100644 --- a/chromium_src/chrome/browser/browser_process.h +++ b/chromium_src/chrome/browser/browser_process.h @@ -12,7 +12,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" namespace printing { diff --git a/chromium_src/chrome/browser/chrome_notification_types.h b/chromium_src/chrome/browser/chrome_notification_types.h index eb5ed40342d..05df960c5ea 100644 --- a/chromium_src/chrome/browser/chrome_notification_types.h +++ b/chromium_src/chrome/browser/chrome_notification_types.h @@ -32,7 +32,7 @@ enum NotificationType { NOTIFICATION_BROWSER_CLOSING, // This message is sent after a window has been closed. The source is a - // Source containing the affected Browser. No details are exptected. + // Source containing the affected Browser. No details are expected. NOTIFICATION_BROWSER_CLOSED, // This message is sent when closing a browser has been cancelled, either by @@ -411,7 +411,7 @@ enum NotificationType { // the source is a Profile. NOTIFICATION_EXTENSION_LOADED_DEPRECATED, - // An error occured while attempting to load an extension. The details are a + // An error occurred while attempting to load an extension. The details are a // string with details about why the load failed. NOTIFICATION_EXTENSION_LOAD_ERROR, @@ -434,7 +434,7 @@ enum NotificationType { // The details are an InstalledExtensionInfo, and the source is a Profile. NOTIFICATION_EXTENSION_INSTALLED, - // An error occured during extension install. The details are a string with + // An error occurred during extension install. The details are a string with // details about why the install failed. NOTIFICATION_EXTENSION_INSTALL_ERROR, @@ -625,7 +625,7 @@ enum NotificationType { // TabSpecificContentSettings object, there are no details. NOTIFICATION_COLLECTED_COOKIES_SHOWN, - // Sent when a non-default setting in the the notification content settings + // Sent when a non-default setting in the notification content settings // map has changed. The source is the DesktopNotificationService, the // details are None. NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED, @@ -776,7 +776,7 @@ enum NotificationType { NOTIFICATION_USER_LIST_CHANGED, // Sent when the screen lock state has changed. The source is - // ScreenLocker and the details is a bool specifing that the + // ScreenLocker and the details is a bool specifying that the // screen is locked. When details is a false, the source object // is being deleted, so the receiver shouldn't use the screen locker // object. @@ -838,7 +838,7 @@ enum NotificationType { // which was installed. NOTIFICATION_APP_INSTALLED_TO_NTP, - // Similar to NOTIFICATION_APP_INSTALLED_TO_NTP but used to nofity ash AppList + // Similar to NOTIFICATION_APP_INSTALLED_TO_NTP but used to notify ash AppList // about installed app. Source is the profile in which the app is installed // and Details is the string ID of the extension. NOTIFICATION_APP_INSTALLED_TO_APPLIST, diff --git a/chromium_src/chrome/browser/extensions/global_shortcut_listener.h b/chromium_src/chrome/browser/extensions/global_shortcut_listener.h index 1f07df2b6e1..9aec54a3263 100644 --- a/chromium_src/chrome/browser/extensions/global_shortcut_listener.h +++ b/chromium_src/chrome/browser/extensions/global_shortcut_listener.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "ui/events/keycodes/keyboard_codes.h" namespace ui { diff --git a/chromium_src/chrome/browser/media/desktop_media_list.h b/chromium_src/chrome/browser/media/desktop_media_list.h index 7ef703e8b7b..6572e792a12 100644 --- a/chromium_src/chrome/browser/media/desktop_media_list.h +++ b/chromium_src/chrome/browser/media/desktop_media_list.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ #define CHROME_BROWSER_MEDIA_DESKTOP_MEDIA_LIST_H_ -#include "base/basictypes.h" #include "base/strings/string16.h" #include "base/time/time.h" #include "content/public/browser/desktop_media_id.h" diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.cc b/chromium_src/chrome/browser/media/native_desktop_media_list.cc index 4a7fb58962c..3c1848df3ef 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.cc +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.cc @@ -34,7 +34,7 @@ const int kDefaultUpdatePeriod = 1000; // Returns a hash of a DesktopFrame content to detect when image for a desktop // media source has changed. -uint32 GetFrameHash(webrtc::DesktopFrame* frame) { +uint32_t GetFrameHash(webrtc::DesktopFrame* frame) { int data_size = frame->stride() * frame->size().height(); return base::SuperFastHash(reinterpret_cast(frame->data()), data_size); } @@ -117,8 +117,8 @@ NativeDesktopMediaList::Worker::Worker( scoped_ptr screen_capturer, scoped_ptr window_capturer) : media_list_(media_list), - screen_capturer_(screen_capturer.Pass()), - window_capturer_(window_capturer.Pass()) { + screen_capturer_(std::move(screen_capturer)), + window_capturer_(std::move(window_capturer)) { if (screen_capturer_) screen_capturer_->Start(this); if (window_capturer_) @@ -195,14 +195,14 @@ void NativeDesktopMediaList::Worker::Refresh( // |current_frame_| may be NULL if capture failed (e.g. because window has // been closed). if (current_frame_) { - uint32 frame_hash = GetFrameHash(current_frame_.get()); + uint32_t frame_hash = GetFrameHash(current_frame_.get()); new_image_hashes[source.id] = frame_hash; // Scale the image only if it has changed. ImageHashesMap::iterator it = image_hashes_.find(source.id); if (it == image_hashes_.end() || it->second != frame_hash) { gfx::ImageSkia thumbnail = - ScaleDesktopFrame(current_frame_.Pass(), thumbnail_size); + ScaleDesktopFrame(std::move(current_frame_), thumbnail_size); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&NativeDesktopMediaList::OnSourceThumbnail, @@ -231,8 +231,8 @@ void NativeDesktopMediaList::Worker::OnCaptureCompleted( NativeDesktopMediaList::NativeDesktopMediaList( scoped_ptr screen_capturer, scoped_ptr window_capturer) - : screen_capturer_(screen_capturer.Pass()), - window_capturer_(window_capturer.Pass()), + : screen_capturer_(std::move(screen_capturer)), + window_capturer_(std::move(window_capturer)), update_period_(base::TimeDelta::FromMilliseconds(kDefaultUpdatePeriod)), thumbnail_size_(100, 100), view_dialog_id_(-1), @@ -269,7 +269,8 @@ void NativeDesktopMediaList::StartUpdating(DesktopMediaListObserver* observer) { observer_ = observer; worker_.reset(new Worker(weak_factory_.GetWeakPtr(), - screen_capturer_.Pass(), window_capturer_.Pass())); + std::move(screen_capturer_), + std::move(window_capturer_))); Refresh(); } diff --git a/chromium_src/chrome/browser/media/native_desktop_media_list.h b/chromium_src/chrome/browser/media/native_desktop_media_list.h index 943d3dd3256..f789a368be9 100644 --- a/chromium_src/chrome/browser/media/native_desktop_media_list.h +++ b/chromium_src/chrome/browser/media/native_desktop_media_list.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ #define CHROME_BROWSER_MEDIA_NATIVE_DESKTOP_MEDIA_LIST_H_ -#include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/sequenced_task_runner.h" diff --git a/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc b/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc index 9064cd961bd..5d42264144a 100644 --- a/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc +++ b/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc @@ -109,21 +109,21 @@ class PdfToEmfUtilityProcessHostClient private: class GetPageCallbackData { - MOVE_ONLY_TYPE_FOR_CPP_03(GetPageCallbackData, RValue); + MOVE_ONLY_TYPE_FOR_CPP_03(GetPageCallbackData); public: GetPageCallbackData(int page_number, PdfToEmfConverter::GetPageCallback callback) : page_number_(page_number), callback_(callback) {} - // Move constructor for STL. - GetPageCallbackData(RValue other) { this->operator=(other); } + GetPageCallbackData(GetPageCallbackData&& other) { + *this = std::move(other); + } - // Move assignment for STL. - GetPageCallbackData& operator=(RValue rhs) { - page_number_ = rhs.object->page_number_; - callback_ = rhs.object->callback_; - emf_ = rhs.object->emf_.Pass(); + GetPageCallbackData& operator=(GetPageCallbackData&& rhs) { + page_number_ = rhs.page_number_; + callback_ = rhs.callback_; + emf_ = std::move(rhs.emf_); return *this; } @@ -256,7 +256,7 @@ void LazyEmf::Close() const { bool LazyEmf::LoadEmf(Emf* emf) const { file_->Seek(base::File::FROM_BEGIN, 0); - int64 size = file_->GetLength(); + int64_t size = file_->GetLength(); if (size <= 0) return false; std::vector data(size); diff --git a/chromium_src/chrome/browser/printing/print_job.cc b/chromium_src/chrome/browser/printing/print_job.cc index 65449ef121e..60dcebe0803 100644 --- a/chromium_src/chrome/browser/printing/print_job.cc +++ b/chromium_src/chrome/browser/printing/print_job.cc @@ -441,7 +441,7 @@ void PrintJob::HoldUntilStopIsCalled() { } void PrintJob::Quit() { - base::MessageLoop::current()->Quit(); + base::MessageLoop::current()->QuitWhenIdle(); } // Takes settings_ ownership and will be deleted in the receiving thread. diff --git a/chromium_src/chrome/browser/printing/print_job.h b/chromium_src/chrome/browser/printing/print_job.h index 48daaa6cb97..4963a94f14f 100644 --- a/chromium_src/chrome/browser/printing/print_job.h +++ b/chromium_src/chrome/browser/printing/print_job.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_PRINTING_PRINT_JOB_H_ #define CHROME_BROWSER_PRINTING_PRINT_JOB_H_ -#include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" diff --git a/chromium_src/chrome/browser/printing/print_preview_message_handler.cc b/chromium_src/chrome/browser/printing/print_preview_message_handler.cc index 613f3f2343c..a195ad4c6ad 100644 --- a/chromium_src/chrome/browser/printing/print_preview_message_handler.cc +++ b/chromium_src/chrome/browser/printing/print_preview_message_handler.cc @@ -118,7 +118,7 @@ void PrintPreviewMessageHandler::PrintToPDF( } void PrintPreviewMessageHandler::RunPrintToPDFCallback( - int request_id, uint32 data_size, char* data) { + int request_id, uint32_t data_size, char* data) { DCHECK_CURRENTLY_ON(BrowserThread::UI); v8::Isolate* isolate = v8::Isolate::GetCurrent(); diff --git a/chromium_src/chrome/browser/printing/print_preview_message_handler.h b/chromium_src/chrome/browser/printing/print_preview_message_handler.h index 453d78761bb..1aac74baa22 100644 --- a/chromium_src/chrome/browser/printing/print_preview_message_handler.h +++ b/chromium_src/chrome/browser/printing/print_preview_message_handler.h @@ -47,7 +47,7 @@ class PrintPreviewMessageHandler const PrintHostMsg_DidPreviewDocument_Params& params); void OnPrintPreviewFailed(int document_cookie, int request_id); - void RunPrintToPDFCallback(int request_id, uint32 data_size, char* data); + void RunPrintToPDFCallback(int request_id, uint32_t data_size, char* data); PrintToPDFCallbackMap print_to_pdf_callback_map_; 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 ede1d3b8ba8..688256e4251 100644 --- a/chromium_src/chrome/browser/printing/print_view_manager_base.cc +++ b/chromium_src/chrome/browser/printing/print_view_manager_base.cc @@ -144,7 +144,7 @@ void PrintViewManagerBase::OnDidPrintPage( #if !defined(OS_WIN) // Update the rendered document. It will send notifications to the listener. document->SetPage(params.page_number, - metafile.Pass(), + std::move(metafile), params.page_size, params.content_area); @@ -305,7 +305,7 @@ void PrintViewManagerBase::ShouldQuitFromInnerMessageLoop() { inside_inner_message_loop_) { // We are in a message loop created by RenderAllMissingPagesNow. Quit from // it. - base::MessageLoop::current()->Quit(); + base::MessageLoop::current()->QuitWhenIdle(); inside_inner_message_loop_ = false; } } @@ -411,9 +411,9 @@ bool PrintViewManagerBase::RunInnerMessageLoop() { // memory-bound. static const int kPrinterSettingsTimeout = 60000; base::OneShotTimer quit_timer; - quit_timer.Start(FROM_HERE, - TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), - base::MessageLoop::current(), &base::MessageLoop::Quit); + quit_timer.Start( + FROM_HERE, TimeDelta::FromMilliseconds(kPrinterSettingsTimeout), + base::MessageLoop::current(), &base::MessageLoop::QuitWhenIdle); inside_inner_message_loop_ = true; diff --git a/chromium_src/chrome/browser/printing/printing_message_filter.cc b/chromium_src/chrome/browser/printing/printing_message_filter.cc index 6fd536ef68c..eac4405fbcf 100644 --- a/chromium_src/chrome/browser/printing/printing_message_filter.cc +++ b/chromium_src/chrome/browser/printing/printing_message_filter.cc @@ -394,7 +394,7 @@ void PrintingMessageFilter::OnUpdatePrintSettings( printer_query = queue_->CreatePrinterQuery(host_id, routing_id); } printer_query->SetSettings( - new_settings.Pass(), + std::move(new_settings), base::Bind(&PrintingMessageFilter::OnUpdatePrintSettingsReply, this, printer_query, reply_msg)); } diff --git a/chromium_src/chrome/browser/printing/printing_message_filter.h b/chromium_src/chrome/browser/printing/printing_message_filter.h index 624b28fd35d..e8536a69c5a 100644 --- a/chromium_src/chrome/browser/printing/printing_message_filter.h +++ b/chromium_src/chrome/browser/printing/printing_message_filter.h @@ -107,7 +107,7 @@ class PrintingMessageFilter : public content::BrowserMessageFilter { #if defined(ENABLE_FULL_PRINTING) // Check to see if print preview has been cancelled. - void OnCheckForCancel(int32 preview_ui_id, + void OnCheckForCancel(int32_t preview_ui_id, int preview_request_id, bool* cancel); #endif diff --git a/chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.h b/chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.h index 66d51e7fba2..de969f5cdb5 100644 --- a/chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.h +++ b/chromium_src/chrome/browser/printing/printing_ui_web_contents_observer.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_PRINTING_PRINTING_UI_WEB_CONTENTS_OBSERVER_H_ #define CHROME_BROWSER_PRINTING_PRINTING_UI_WEB_CONTENTS_OBSERVER_H_ -#include "base/basictypes.h" #include "content/public/browser/web_contents_observer.h" #include "ui/gfx/native_widget_types.h" diff --git a/chromium_src/chrome/browser/process_singleton.h b/chromium_src/chrome/browser/process_singleton.h index 3eeb53393e1..eab6c35479a 100644 --- a/chromium_src/chrome/browser/process_singleton.h +++ b/chromium_src/chrome/browser/process_singleton.h @@ -12,7 +12,6 @@ #include #include -#include "base/basictypes.h" #include "base/callback.h" #include "base/command_line.h" #include "base/files/file_path.h" diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index 98fb948730e..7e54d9b5d37 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -54,7 +54,6 @@ #include "atom/common/atom_command_line.h" #include "base/base_paths.h" -#include "base/basictypes.h" #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" @@ -75,6 +74,7 @@ #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "base/thread_task_runner_handle.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/timer/timer.h" @@ -222,7 +222,7 @@ int SetupSocketOnly() { int sock = socket(PF_UNIX, SOCK_STREAM, 0); PCHECK(sock >= 0) << "socket() failed"; - int rv = net::SetNonBlocking(sock); + int rv = base::SetNonBlocking(sock); DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; rv = SetCloseOnExec(sock); DCHECK_EQ(0, rv) << "Failed to set CLOEXEC on socket."; @@ -577,7 +577,7 @@ void ProcessSingleton::LinuxWatcher::OnFileCanReadWithoutBlocking(int fd) { PLOG(ERROR) << "accept() failed"; return; } - int rv = net::SetNonBlocking(connection_socket); + int rv = base::SetNonBlocking(connection_socket); DCHECK_EQ(0, rv) << "Failed to make non-blocking socket."; SocketReader* reader = new SocketReader(this, ui_message_loop_, @@ -990,8 +990,8 @@ bool ProcessSingleton::Create() { // In Electron the ProcessSingleton is created earlier than the IO // thread gets created, so we have to postpone the call until message // loop is up an running. - scoped_refptr task_runner( - base::ThreadTaskRunnerHandle::Get()); + scoped_refptr task_runner = + base::ThreadTaskRunnerHandle::Get(); task_runner->PostTask( FROM_HERE, base::Bind(&ProcessSingleton::StartListening, diff --git a/chromium_src/chrome/browser/process_singleton_win.cc b/chromium_src/chrome/browser/process_singleton_win.cc index 14e53bec5fa..fd4c22e7405 100644 --- a/chromium_src/chrome/browser/process_singleton_win.cc +++ b/chromium_src/chrome/browser/process_singleton_win.cc @@ -16,7 +16,6 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" -#include "base/win/metro.h" #include "base/win/registry.h" #include "base/win/scoped_handle.h" #include "base/win/windows_version.h" diff --git a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h index 6fb4aced181..40a03a1ff56 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h +++ b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_browser_host.h @@ -5,7 +5,6 @@ #ifndef CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_BROWSER_HOST_H_ #define CHROME_BROWSER_RENDERER_HOST_PEPPER_PEPPER_FLASH_BROWSER_HOST_H_ -#include "base/basictypes.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "ppapi/host/host_message_context.h" diff --git a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.cc b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.cc index fdc054f59fb..9ddb9fa56d7 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.cc +++ b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.cc @@ -240,8 +240,8 @@ int32_t PepperFlashClipboardMessageFilter::OnMsgReadData( base::string16 html; std::string url; - uint32 fragment_start; - uint32 fragment_end; + uint32_t fragment_start; + uint32_t fragment_end; clipboard->ReadHTML(type, &html, &url, &fragment_start, &fragment_end); result = PP_OK; clipboard_string = base::UTF16ToUTF8( diff --git a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h index ff07eb73750..4c146dd5daa 100644 --- a/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h +++ b/chromium_src/chrome/browser/renderer_host/pepper/pepper_flash_clipboard_message_filter.h @@ -8,8 +8,6 @@ #include #include -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "ppapi/host/resource_message_filter.h" #include "ppapi/shared_impl/flash_clipboard_format_registry.h" diff --git a/chromium_src/chrome/browser/speech/tts_win.cc b/chromium_src/chrome/browser/speech/tts_win.cc index ac258205889..bc9411a8c15 100644 --- a/chromium_src/chrome/browser/speech/tts_win.cc +++ b/chromium_src/chrome/browser/speech/tts_win.cc @@ -87,7 +87,7 @@ bool TtsPlatformImplWin::Speak( // 0.1 -> -10 // 1.0 -> 0 // 10.0 -> 10 - speech_synthesizer_->SetRate(static_cast(10 * log10(params.rate))); + speech_synthesizer_->SetRate(static_cast(10 * log10(params.rate))); } if (params.pitch >= 0.0) { @@ -102,7 +102,7 @@ bool TtsPlatformImplWin::Speak( if (params.volume >= 0.0) { // The TTS api allows a range of 0 to 100 for speech volume. - speech_synthesizer_->SetVolume(static_cast(params.volume * 100)); + speech_synthesizer_->SetVolume(static_cast(params.volume * 100)); } // TODO(dmazzoni): convert SSML to SAPI xml. http://crbug.com/88072 diff --git a/chromium_src/chrome/browser/ui/cocoa/color_chooser_mac.mm b/chromium_src/chrome/browser/ui/cocoa/color_chooser_mac.mm index cb366022d6e..6183dd5d5bd 100644 --- a/chromium_src/chrome/browser/ui/cocoa/color_chooser_mac.mm +++ b/chromium_src/chrome/browser/ui/cocoa/color_chooser_mac.mm @@ -74,7 +74,7 @@ ColorChooserMac::ColorChooserMac(content::WebContents* web_contents, SkColor initial_color) : web_contents_(web_contents) { panel_.reset([[ColorPanelCocoa alloc] initWithChooser:this]); - [panel_ setColor:gfx::SkColorToDeviceNSColor(initial_color)]; + [panel_ setColor:skia::SkColorToDeviceNSColor(initial_color)]; [[NSColorPanel sharedColorPanel] makeKeyAndOrderFront:nil]; } @@ -101,7 +101,7 @@ void ColorChooserMac::End() { } void ColorChooserMac::SetSelectedColor(SkColor color) { - [panel_ setColor:gfx::SkColorToDeviceNSColor(color)]; + [panel_ setColor:skia::SkColorToDeviceNSColor(color)]; } @implementation ColorPanelCocoa @@ -139,7 +139,7 @@ void ColorChooserMac::SetSelectedColor(SkColor color) { nonUserChange_ = NO; return; } - chooser_->DidChooseColorInColorPanel(gfx::NSDeviceColorToSkColor( + chooser_->DidChooseColorInColorPanel(skia::NSDeviceColorToSkColor( [[panel color] colorUsingColorSpaceName:NSDeviceRGBColorSpace])); nonUserChange_ = NO; } diff --git a/chromium_src/chrome/browser/ui/views/color_chooser_aura.h b/chromium_src/chrome/browser/ui/views/color_chooser_aura.h index 6394b973a3d..355f540b19d 100644 --- a/chromium_src/chrome/browser/ui/views/color_chooser_aura.h +++ b/chromium_src/chrome/browser/ui/views/color_chooser_aura.h @@ -5,8 +5,7 @@ #ifndef CHROME_BROWSER_UI_VIEWS_COLOR_CHOOSER_AURA_H_ #define CHROME_BROWSER_UI_VIEWS_COLOR_CHOOSER_AURA_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" +#include "base/macros.h" #include "content/public/browser/color_chooser.h" #include "ui/views/color_chooser/color_chooser_listener.h" diff --git a/chromium_src/chrome/browser/ui/views/color_chooser_win.cc b/chromium_src/chrome/browser/ui/views/color_chooser_win.cc index b62801399e8..7a4f7573333 100644 --- a/chromium_src/chrome/browser/ui/views/color_chooser_win.cc +++ b/chromium_src/chrome/browser/ui/views/color_chooser_win.cc @@ -9,8 +9,10 @@ #include "chrome/browser/ui/views/color_chooser_dialog.h" #include "content/public/browser/color_chooser.h" #include "content/public/browser/render_view_host.h" +#include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" +#include "ui/aura/window.h" #include "ui/views/color_chooser/color_chooser_listener.h" class ColorChooserWin : public content::ColorChooser, @@ -55,9 +57,11 @@ ColorChooserWin* ColorChooserWin::Open(content::WebContents* web_contents, ColorChooserWin::ColorChooserWin(content::WebContents* web_contents, SkColor initial_color) : web_contents_(web_contents) { - gfx::NativeWindow owning_window = (gfx::NativeWindow)::GetAncestor( - (HWND)web_contents->GetRenderViewHost()->GetView()->GetNativeView(), - GA_ROOT); + gfx::NativeWindow owning_window = web_contents->GetRenderViewHost() + ->GetWidget() + ->GetView() + ->GetNativeView() + ->GetToplevelWindow(); color_chooser_dialog_ = new ColorChooserDialog(this, initial_color, owning_window); diff --git a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h index 694f776b24e..95a08b229f4 100644 --- a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h +++ b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h @@ -9,7 +9,6 @@ #include -#include "base/basictypes.h" #include "base/memory/ref_counted.h" #include "base/memory/singleton.h" #include "ui/base/glib/glib_signal.h" diff --git a/chromium_src/chrome/common/chrome_paths_win.cc b/chromium_src/chrome/common/chrome_paths_win.cc index 37f4ec2b05b..89c2ae48eaa 100644 --- a/chromium_src/chrome/common/chrome_paths_win.cc +++ b/chromium_src/chrome/common/chrome_paths_win.cc @@ -12,7 +12,6 @@ #include "base/files/file_path.h" #include "base/path_service.h" -#include "base/win/metro.h" #include "base/win/scoped_co_mem.h" #include "chrome/common/chrome_constants.h" diff --git a/chromium_src/chrome/common/pref_names.cc b/chromium_src/chrome/common/pref_names.cc index 3e3a73b9983..23235cd1f4b 100644 --- a/chromium_src/chrome/common/pref_names.cc +++ b/chromium_src/chrome/common/pref_names.cc @@ -8,5 +8,6 @@ namespace prefs { const char kSelectFileLastDirectory[] = "selectfile.last_directory"; const char kDownloadDefaultDirectory[] = "download.default_directory"; +const char kDevToolsFileSystemPaths[] = "devtools.file_system_paths"; } // namespace prefs diff --git a/chromium_src/chrome/common/pref_names.h b/chromium_src/chrome/common/pref_names.h index 542a2d2c733..5101c720133 100644 --- a/chromium_src/chrome/common/pref_names.h +++ b/chromium_src/chrome/common/pref_names.h @@ -8,5 +8,6 @@ namespace prefs { extern const char kSelectFileLastDirectory[]; extern const char kDownloadDefaultDirectory[]; +extern const char kDevToolsFileSystemPaths[]; } // namespace prefs diff --git a/chromium_src/chrome/common/print_messages.cc b/chromium_src/chrome/common/print_messages.cc index 5f8e30f3698..b0ec282382d 100644 --- a/chromium_src/chrome/common/print_messages.cc +++ b/chromium_src/chrome/common/print_messages.cc @@ -4,7 +4,6 @@ #include "chrome/common/print_messages.h" -#include "base/basictypes.h" #include "base/strings/string16.h" #include "ui/gfx/geometry/size.h" diff --git a/chromium_src/chrome/common/print_messages.h b/chromium_src/chrome/common/print_messages.h index cd775d0478b..034691b5b95 100644 --- a/chromium_src/chrome/common/print_messages.h +++ b/chromium_src/chrome/common/print_messages.h @@ -161,7 +161,7 @@ IPC_STRUCT_BEGIN(PrintHostMsg_DidPrintPage_Params) IPC_STRUCT_MEMBER(base::SharedMemoryHandle, metafile_data_handle) // Size of the metafile data. - IPC_STRUCT_MEMBER(uint32, data_size) + IPC_STRUCT_MEMBER(uint32_t, data_size) // Cookie for the document to ensure correctness. IPC_STRUCT_MEMBER(int, document_cookie) @@ -193,7 +193,7 @@ IPC_STRUCT_BEGIN(PrintHostMsg_DidPreviewDocument_Params) IPC_STRUCT_MEMBER(base::SharedMemoryHandle, metafile_data_handle) // Size of metafile data. - IPC_STRUCT_MEMBER(uint32, data_size) + IPC_STRUCT_MEMBER(uint32_t, data_size) // Cookie for the document to ensure correctness. IPC_STRUCT_MEMBER(int, document_cookie) diff --git a/chromium_src/chrome/common/tts_utterance_request.h b/chromium_src/chrome/common/tts_utterance_request.h index e0b7adfa4a0..a4b4cab68ca 100644 --- a/chromium_src/chrome/common/tts_utterance_request.h +++ b/chromium_src/chrome/common/tts_utterance_request.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/strings/string16.h" struct TtsUtteranceRequest { @@ -41,4 +41,4 @@ struct TtsUtteranceResponse { int id; }; -#endif // CHROME_COMMON_TTS_UTTERANCE_REQUEST_H_ \ No newline at end of file +#endif // CHROME_COMMON_TTS_UTTERANCE_REQUEST_H_ diff --git a/chromium_src/chrome/common/widevine_cdm_constants.cc b/chromium_src/chrome/common/widevine_cdm_constants.cc index 60f487e2ae8..587966a9c36 100644 --- a/chromium_src/chrome/common/widevine_cdm_constants.cc +++ b/chromium_src/chrome/common/widevine_cdm_constants.cc @@ -12,5 +12,5 @@ const base::FilePath::CharType kWidevineCdmBaseDirectory[] = const char kWidevineCdmPluginExtension[] = ""; -const int32 kWidevineCdmPluginPermissions = ppapi::PERMISSION_DEV | - ppapi::PERMISSION_PRIVATE; +const int32_t kWidevineCdmPluginPermissions = ppapi::PERMISSION_DEV | + ppapi::PERMISSION_PRIVATE; diff --git a/chromium_src/chrome/common/widevine_cdm_constants.h b/chromium_src/chrome/common/widevine_cdm_constants.h index b626079a11b..9597b1cb41f 100644 --- a/chromium_src/chrome/common/widevine_cdm_constants.h +++ b/chromium_src/chrome/common/widevine_cdm_constants.h @@ -5,7 +5,7 @@ #ifndef CHROME_COMMON_WIDEVINE_CDM_CONSTANTS_H_ #define CHROME_COMMON_WIDEVINE_CDM_CONSTANTS_H_ -#include "base/basictypes.h" +#include "base/macros.h" #include "base/files/file_path.h" // The Widevine CDM adapter and Widevine CDM are in this directory. @@ -14,6 +14,6 @@ extern const base::FilePath::CharType kWidevineCdmBaseDirectory[]; extern const char kWidevineCdmPluginExtension[]; // Permission bits for Widevine CDM plugin. -extern const int32 kWidevineCdmPluginPermissions; +extern const int32_t kWidevineCdmPluginPermissions; #endif // CHROME_COMMON_WIDEVINE_CDM_CONSTANTS_H_ diff --git a/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h b/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h index 13ab2853a35..dd12e9d9160 100644 --- a/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h +++ b/chromium_src/chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h @@ -5,8 +5,6 @@ #ifndef CHROME_RENDERER_PEPPER_CHROME_RENDERER_PEPPER_HOST_FACTORY_H_ #define CHROME_RENDERER_PEPPER_CHROME_RENDERER_PEPPER_HOST_FACTORY_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "ppapi/host/host_factory.h" namespace content { diff --git a/chromium_src/chrome/renderer/pepper/pepper_flash_font_file_host.h b/chromium_src/chrome/renderer/pepper/pepper_flash_font_file_host.h index 02bb30f315f..eeaa7209b5f 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_flash_font_file_host.h +++ b/chromium_src/chrome/renderer/pepper/pepper_flash_font_file_host.h @@ -5,8 +5,6 @@ #ifndef CHROME_RENDERER_PEPPER_PEPPER_FLASH_FONT_FILE_HOST_H_ #define CHROME_RENDERER_PEPPER_PEPPER_FLASH_FONT_FILE_HOST_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "ppapi/c/private/pp_private_font_charset.h" #include "ppapi/host/resource_host.h" diff --git a/chromium_src/chrome/renderer/pepper/pepper_flash_fullscreen_host.h b/chromium_src/chrome/renderer/pepper/pepper_flash_fullscreen_host.h index 3550ea13663..86d0af73aee 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_flash_fullscreen_host.h +++ b/chromium_src/chrome/renderer/pepper/pepper_flash_fullscreen_host.h @@ -5,8 +5,6 @@ #ifndef CHROME_RENDERER_PEPPER_PEPPER_FLASH_FULLSCREEN_HOST_H_ #define CHROME_RENDERER_PEPPER_PEPPER_FLASH_FULLSCREEN_HOST_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "ppapi/host/resource_host.h" namespace content { diff --git a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.h b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.h index de22f46045a..f37907a8655 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.h +++ b/chromium_src/chrome/renderer/pepper/pepper_flash_renderer_host.h @@ -8,7 +8,6 @@ #include #include -#include "base/basictypes.h" #include "base/memory/weak_ptr.h" #include "ppapi/host/host_message_context.h" #include "ppapi/host/resource_host.h" diff --git a/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.cc b/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.cc index 3ef6dff0c8b..6fbadd12116 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.cc +++ b/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.cc @@ -43,9 +43,8 @@ void PepperSharedMemoryMessageFilter::OnHostMsgCreateSharedMemory( ppapi::proxy::SerializedHandle* plugin_handle) { plugin_handle->set_null_shmem(); *host_handle_id = -1; - scoped_ptr shm(content::RenderThread::Get() - ->HostAllocateSharedMemoryBuffer(size) - .Pass()); + scoped_ptr shm( + content::RenderThread::Get()->HostAllocateSharedMemoryBuffer(size)); if (!shm.get()) return; diff --git a/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.h b/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.h index d7e0934cd6e..860e1c9dbd1 100644 --- a/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.h +++ b/chromium_src/chrome/renderer/pepper/pepper_shared_memory_message_filter.h @@ -5,8 +5,6 @@ #ifndef CHROME_RENDERER_PEPPER_PEPPER_SHARED_MEMORY_MESSAGE_FILTER_H_ #define CHROME_RENDERER_PEPPER_PEPPER_SHARED_MEMORY_MESSAGE_FILTER_H_ -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "ppapi/c/pp_instance.h" #include "ppapi/host/instance_message_filter.h" diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper.cc b/chromium_src/chrome/renderer/printing/print_web_view_helper.cc index 3bfe719a0c9..eb3599198d9 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper.cc @@ -386,7 +386,7 @@ class PrepareFrameAndViewForPrint : public blink::WebViewClient, blink::WebLocalFrame* frame, const blink::WebNode& node, bool ignore_css_margins); - virtual ~PrepareFrameAndViewForPrint(); + ~PrepareFrameAndViewForPrint() override; // Optional. Replaces |frame_| with selection if needed. Will call |on_ready| // when completed. @@ -415,22 +415,19 @@ class PrepareFrameAndViewForPrint : public blink::WebViewClient, return owns_web_view_ && frame() && frame()->isLoading(); } - // TODO(ojan): Remove this override and have this class use a non-null - // layerTreeView. - // blink::WebViewClient override: - virtual bool allowsBrokenNullLayerTreeView() const; - protected: // blink::WebViewClient override: - virtual void didStopLoading(); + void didStopLoading() override; + bool allowsBrokenNullLayerTreeView() const override; - // blink::WebFrameClient override: - virtual blink::WebFrame* createChildFrame( + // blink::WebFrameClient: + blink::WebFrame* createChildFrame( blink::WebLocalFrame* parent, blink::WebTreeScopeType scope, const blink::WebString& name, - blink::WebSandboxFlags sandboxFlags); - virtual void frameDetached(blink::WebFrame* frame, DetachType type); + blink::WebSandboxFlags sandboxFlags, + const blink::WebFrameOwnerProperties& frameOwnerProperties) override; + void frameDetached(blink::WebFrame* frame, DetachType type) override; private: void CallOnReady(); @@ -576,7 +573,8 @@ blink::WebFrame* PrepareFrameAndViewForPrint::createChildFrame( blink::WebLocalFrame* parent, blink::WebTreeScopeType scope, const blink::WebString& name, - blink::WebSandboxFlags sandboxFlags) { + blink::WebSandboxFlags sandboxFlags, + const blink::WebFrameOwnerProperties& frameOwnerProperties) { blink::WebFrame* frame = blink::WebLocalFrame::create(scope, this); parent->appendChild(frame); return frame; @@ -812,13 +810,19 @@ bool PrintWebViewHelper::FinalizePrintReadyDocument() { DCHECK(!is_print_ready_metafile_sent_); print_preview_context_.FinalizePrintReadyDocument(); - // Get the size of the resulting metafile. PdfMetafileSkia* metafile = print_preview_context_.metafile(); - uint32 buf_size = metafile->GetDataSize(); - DCHECK_GT(buf_size, 0u); PrintHostMsg_DidPreviewDocument_Params preview_params; - preview_params.data_size = buf_size; + + // Ask the browser to create the shared memory for us. + if (!CopyMetafileDataToSharedMem(*metafile, + &(preview_params.metafile_data_handle))) { + LOG(ERROR) << "CopyMetafileDataToSharedMem failed"; + print_preview_context_.set_error(PREVIEW_ERROR_METAFILE_COPY_FAILED); + return false; + } + + preview_params.data_size = metafile->GetDataSize(); preview_params.document_cookie = print_pages_params_->params.document_cookie; preview_params.expected_pages_count = print_preview_context_.total_page_count(); @@ -826,13 +830,6 @@ bool PrintWebViewHelper::FinalizePrintReadyDocument() { preview_params.preview_request_id = print_pages_params_->params.preview_request_id; - // Ask the browser to create the shared memory for us. - if (!CopyMetafileDataToSharedMem(metafile, - &(preview_params.metafile_data_handle))) { - LOG(ERROR) << "CopyMetafileDataToSharedMem failed"; - print_preview_context_.set_error(PREVIEW_ERROR_METAFILE_COPY_FAILED); - return false; - } is_print_ready_metafile_sent_ = true; Send(new PrintHostMsg_MetafileReadyForPrinting(routing_id(), preview_params)); @@ -1162,21 +1159,25 @@ bool PrintWebViewHelper::RenderPagesForPrint(blink::WebLocalFrame* frame, #if defined(OS_POSIX) bool PrintWebViewHelper::CopyMetafileDataToSharedMem( - PdfMetafileSkia* metafile, + const PdfMetafileSkia& metafile, base::SharedMemoryHandle* shared_mem_handle) { - uint32 buf_size = metafile->GetDataSize(); - scoped_ptr shared_buf( - content::RenderThread::Get()->HostAllocateSharedMemoryBuffer( - buf_size).release()); + uint32_t buf_size = metafile.GetDataSize(); + if (buf_size == 0) + return false; - if (shared_buf) { - if (shared_buf->Map(buf_size)) { - metafile->GetData(shared_buf->memory(), buf_size); - return shared_buf->GiveToProcess(base::GetCurrentProcessHandle(), - shared_mem_handle); - } - } - return false; + scoped_ptr shared_buf( + content::RenderThread::Get()->HostAllocateSharedMemoryBuffer(buf_size)); + if (!shared_buf) + return false; + + if (!shared_buf->Map(buf_size)) + return false; + + if (!metafile.GetData(shared_buf->memory(), buf_size)) + return false; + + return shared_buf->GiveToProcess(base::GetCurrentProcessHandle(), + shared_mem_handle); } #endif // defined(OS_POSIX) diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper.h b/chromium_src/chrome/renderer/printing/print_web_view_helper.h index bfe9cb612d1..05f145b5bb8 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper.h +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper.h @@ -218,7 +218,7 @@ class PrintWebViewHelper // Helper methods ----------------------------------------------------------- - bool CopyMetafileDataToSharedMem(PdfMetafileSkia* metafile, + bool CopyMetafileDataToSharedMem(const PdfMetafileSkia& metafile, base::SharedMemoryHandle* shared_mem_handle); // Helper method to get page layout in points and fit to page if needed. diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper_linux.cc b/chromium_src/chrome/renderer/printing/print_web_view_helper_linux.cc index 82d7779d026..d37aec628ba 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper_linux.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper_linux.cc @@ -11,7 +11,6 @@ #include "printing/metafile_skia_wrapper.h" #include "printing/page_size_margins.h" #include "printing/pdf_metafile_skia.h" -#include "skia/ext/platform_device.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID) @@ -91,49 +90,15 @@ bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, metafile.FinishDocument(); - // Get the size of the resulting metafile. - uint32 buf_size = metafile.GetDataSize(); - DCHECK_GT(buf_size, 0u); - -#if defined(OS_CHROMEOS) || defined(OS_ANDROID) - int sequence_number = -1; - base::FileDescriptor fd; - - // Ask the browser to open a file for us. - Send(new PrintHostMsg_AllocateTempFileForPrinting(routing_id(), - &fd, - &sequence_number)); - if (!metafile.SaveToFD(fd)) - return false; - - // Tell the browser we've finished writing the file. - Send(new PrintHostMsg_TempFileForPrintingWritten(routing_id(), - sequence_number)); - return true; -#else PrintHostMsg_DidPrintPage_Params printed_page_params; - printed_page_params.data_size = 0; - printed_page_params.document_cookie = params.params.document_cookie; - - { - scoped_ptr shared_mem( - content::RenderThread::Get()->HostAllocateSharedMemoryBuffer( - buf_size).release()); - if (!shared_mem.get()) { - NOTREACHED() << "AllocateSharedMemoryBuffer failed"; - return false; - } - - if (!shared_mem->Map(buf_size)) { - NOTREACHED() << "Map failed"; - return false; - } - metafile.GetData(shared_mem->memory(), buf_size); - printed_page_params.data_size = buf_size; - shared_mem->GiveToProcess(base::GetCurrentProcessHandle(), - &(printed_page_params.metafile_data_handle)); + if (!CopyMetafileDataToSharedMem( + metafile, &printed_page_params.metafile_data_handle)) { + return false; } + printed_page_params.data_size = metafile.GetDataSize(); + printed_page_params.document_cookie = params.params.document_cookie; + for (size_t i = 0; i < printed_pages.size(); ++i) { printed_page_params.page_number = printed_pages[i]; Send(new PrintHostMsg_DidPrintPage(routing_id(), printed_page_params)); @@ -141,7 +106,6 @@ bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, printed_page_params.metafile_data_handle.fd = -1; } return true; -#endif // defined(OS_CHROMEOS) } void PrintWebViewHelper::PrintPageInternal( @@ -165,7 +129,6 @@ void PrintWebViewHelper::PrintPageInternal( return; MetafileSkiaWrapper::SetMetafileOnCanvas(*canvas, metafile); - skia::SetIsDraftMode(*canvas, is_print_ready_metafile_sent_); RenderPageContent(frame, params.page_number, canvas_area, content_area, scale_factor, canvas); diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper_mac.mm b/chromium_src/chrome/renderer/printing/print_web_view_helper_mac.mm index 0785e30a9cf..b10ba616a1f 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper_mac.mm +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper_mac.mm @@ -42,8 +42,9 @@ void PrintWebViewHelper::PrintPageInternal( page_params.content_area = content_area_in_dpi; // Ask the browser to create the shared memory for us. - if (!CopyMetafileDataToSharedMem(&metafile, + if (!CopyMetafileDataToSharedMem(metafile, &(page_params.metafile_data_handle))) { + // TODO(thestig): Fail and return false instead. page_params.data_size = 0; } @@ -116,15 +117,13 @@ void PrintWebViewHelper::RenderPage(const PrintMsg_Print_Params& params, gfx::Rect canvas_area = content_area; { - skia::PlatformCanvas* canvas = metafile->GetVectorCanvasForNewPage( + SkCanvas* canvas = metafile->GetVectorCanvasForNewPage( *page_size, canvas_area, scale_factor); if (!canvas) return; MetafileSkiaWrapper::SetMetafileOnCanvas(*canvas, metafile); - skia::SetIsDraftMode(*canvas, is_print_ready_metafile_sent_); skia::SetIsPreviewMetafile(*canvas, is_preview); - RenderPageContent(frame, page_number, canvas_area, content_area, scale_factor, static_cast(canvas)); } diff --git a/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc b/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc index 0b21de46995..ce1e962505f 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper_pdf_win.cc @@ -95,40 +95,16 @@ bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, metafile.FinishDocument(); - // Get the size of the resulting metafile. - uint32 buf_size = metafile.GetDataSize(); - DCHECK_GT(buf_size, 0u); - PrintHostMsg_DidPrintPage_Params printed_page_params; - printed_page_params.data_size = 0; + if (!CopyMetafileDataToSharedMem( + metafile, &printed_page_params.metafile_data_handle)) { + return false; + } + + printed_page_params.content_area = params.params.printable_area; + printed_page_params.data_size = metafile.GetDataSize(); printed_page_params.document_cookie = params.params.document_cookie; printed_page_params.page_size = params.params.page_size; - printed_page_params.content_area = params.params.printable_area; - - { - base::SharedMemory shared_buf; - // Allocate a shared memory buffer to hold the generated metafile data. - if (!shared_buf.CreateAndMapAnonymous(buf_size)) { - NOTREACHED() << "Buffer allocation failed"; - return false; - } - - // Copy the bits into shared memory. - if (!metafile.GetData(shared_buf.memory(), buf_size)) { - NOTREACHED() << "GetData() failed"; - shared_buf.Unmap(); - return false; - } - shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), - &printed_page_params.metafile_data_handle); - shared_buf.Unmap(); - - printed_page_params.data_size = buf_size; - Send(new PrintHostMsg_DuplicateSection( - routing_id(), - printed_page_params.metafile_data_handle, - &printed_page_params.metafile_data_handle)); - } for (size_t i = 0; i < printed_pages.size(); ++i) { printed_page_params.page_number = printed_pages[i]; @@ -188,7 +164,6 @@ void PrintWebViewHelper::PrintPageInternal( return; MetafileSkiaWrapper::SetMetafileOnCanvas(*canvas, metafile); - skia::SetIsDraftMode(*canvas, is_print_ready_metafile_sent_); #if 0 if (params.params.display_header_footer) { @@ -216,24 +191,25 @@ void PrintWebViewHelper::PrintPageInternal( } bool PrintWebViewHelper::CopyMetafileDataToSharedMem( - PdfMetafileSkia* metafile, + const PdfMetafileSkia& metafile, base::SharedMemoryHandle* shared_mem_handle) { - uint32 buf_size = metafile->GetDataSize(); + uint32_t buf_size = metafile.GetDataSize(); + if (buf_size == 0) + return false; + base::SharedMemory shared_buf; // Allocate a shared memory buffer to hold the generated metafile data. - if (!shared_buf.CreateAndMapAnonymous(buf_size)) { - NOTREACHED() << "Buffer allocation failed"; + if (!shared_buf.CreateAndMapAnonymous(buf_size)) return false; - } // Copy the bits into shared memory. - if (!metafile->GetData(shared_buf.memory(), buf_size)) { - NOTREACHED() << "GetData() failed"; - shared_buf.Unmap(); + if (!metafile.GetData(shared_buf.memory(), buf_size)) + return false; + + if (!shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), + shared_mem_handle)) { return false; } - shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), shared_mem_handle); - shared_buf.Unmap(); Send(new PrintHostMsg_DuplicateSection(routing_id(), *shared_mem_handle, shared_mem_handle)); diff --git a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc index 815a9c08b34..a3b51a8c46e 100644 --- a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc +++ b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.cc @@ -9,7 +9,6 @@ #include #include -#include "base/basictypes.h" #include "base/i18n/break_iterator.h" #include "base/logging.h" #include "base/strings/stringprintf.h" @@ -332,7 +331,7 @@ bool SpellcheckWordIterator::Initialize( NOTREACHED() << "failed to open iterator (broken rules)"; return false; } - iterator_ = iterator.Pass(); + iterator_ = std::move(iterator); // Set the character attributes so we can normalize the words extracted by // this iterator. diff --git a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h index 2ac28a2e240..03fd8e666f5 100644 --- a/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h +++ b/chromium_src/chrome/renderer/spellchecker/spellcheck_worditerator.h @@ -11,7 +11,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string16.h" #include "third_party/icu/source/common/unicode/uscript.h" diff --git a/chromium_src/chrome/renderer/tts_dispatcher.cc b/chromium_src/chrome/renderer/tts_dispatcher.cc index 91b67ba1674..0d3b97c8454 100644 --- a/chromium_src/chrome/renderer/tts_dispatcher.cc +++ b/chromium_src/chrome/renderer/tts_dispatcher.cc @@ -4,7 +4,6 @@ #include "chrome/renderer/tts_dispatcher.h" -#include "base/basictypes.h" #include "base/strings/utf_string_conversions.h" #include "chrome/common/tts_messages.h" #include "chrome/common/tts_utterance_request.h" @@ -197,4 +196,4 @@ void TtsDispatcher::OnSpeakingErrorOccurred(int utterance_id, // The web speech API doesn't support an error message. synthesizer_client_->speakingErrorOccurred(utterance); utterance_id_map_.erase(utterance_id); -} \ No newline at end of file +} diff --git a/chromium_src/chrome/renderer/tts_dispatcher.h b/chromium_src/chrome/renderer/tts_dispatcher.h index fd18acba206..0a770d72183 100644 --- a/chromium_src/chrome/renderer/tts_dispatcher.h +++ b/chromium_src/chrome/renderer/tts_dispatcher.h @@ -7,8 +7,6 @@ #include -#include "base/basictypes.h" -#include "base/compiler_specific.h" #include "base/containers/hash_tables.h" #include "content/public/renderer/render_process_observer.h" #include "third_party/WebKit/public/platform/WebSpeechSynthesizer.h" @@ -75,4 +73,4 @@ class TtsDispatcher DISALLOW_COPY_AND_ASSIGN(TtsDispatcher); }; -#endif // CHROME_RENDERER_TTS_DISPATCHER_H_ \ No newline at end of file +#endif // CHROME_RENDERER_TTS_DISPATCHER_H_ diff --git a/chromium_src/chrome/utility/printing_handler_win.cc b/chromium_src/chrome/utility/printing_handler_win.cc index ec908d19fc5..805cd6e343c 100644 --- a/chromium_src/chrome/utility/printing_handler_win.cc +++ b/chromium_src/chrome/utility/printing_handler_win.cc @@ -177,7 +177,7 @@ int PrintingHandlerWin::LoadPDF(base::File pdf_file) { if (!g_pdf_lib.Get().IsValid()) return 0; - int64 length64 = pdf_file.GetLength(); + int64_t length64 = pdf_file.GetLength(); if (length64 <= 0 || length64 > std::numeric_limits::max()) return 0; int length = static_cast(length64); diff --git a/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc index 897b23bbd56..6495b23b840 100644 --- a/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc @@ -17,6 +17,7 @@ #include "net/base/net_errors.h" #endif +#include "base/files/file_util.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" @@ -27,6 +28,7 @@ #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" +#include "net/base/sockaddr_storage.h" #include "net/socket/socket_descriptor.h" using std::string; @@ -125,7 +127,7 @@ SocketDescriptor StreamListenSocket::AcceptSocket() { if (conn == kInvalidSocket) LOG(ERROR) << "Error accepting connection."; else - SetNonBlocking(conn); + base::SetNonBlocking(conn); return conn; } diff --git a/chromium_src/net/test/embedded_test_server/stream_listen_socket.h b/chromium_src/net/test/embedded_test_server/stream_listen_socket.h index 02a8b9827a2..9df51258306 100644 --- a/chromium_src/net/test/embedded_test_server/stream_listen_socket.h +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.h @@ -28,7 +28,7 @@ #include "base/message_loop/message_loop.h" #endif -#include "base/basictypes.h" +#include "base/macros.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "net/base/net_export.h" diff --git a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc index 418f3459212..1a72b2efe37 100644 --- a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc +++ b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc @@ -8,6 +8,7 @@ // winsock2.h must be included first in order to ensure it is included before // windows.h. #include +#include #elif defined(OS_POSIX) #include #include @@ -34,14 +35,14 @@ namespace test_server { // static scoped_ptr TCPListenSocket::CreateAndListen( const string& ip, - uint16 port, + uint16_t port, StreamListenSocket::Delegate* del) { SocketDescriptor s = CreateAndBind(ip, port); if (s == kInvalidSocket) return scoped_ptr(); scoped_ptr sock(new TCPListenSocket(s, del)); sock->Listen(); - return sock.Pass(); + return sock; } TCPListenSocket::TCPListenSocket(SocketDescriptor s, @@ -52,7 +53,8 @@ TCPListenSocket::TCPListenSocket(SocketDescriptor s, TCPListenSocket::~TCPListenSocket() { } -SocketDescriptor TCPListenSocket::CreateAndBind(const string& ip, uint16 port) { +SocketDescriptor TCPListenSocket::CreateAndBind(const string& ip, + uint16_t port) { SocketDescriptor s = CreatePlatformSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (s != kInvalidSocket) { #if defined(OS_POSIX) @@ -79,7 +81,7 @@ SocketDescriptor TCPListenSocket::CreateAndBind(const string& ip, uint16 port) { } SocketDescriptor TCPListenSocket::CreateAndBindAnyPort(const string& ip, - uint16* port) { + uint16_t* port) { SocketDescriptor s = CreateAndBind(ip, 0); if (s == kInvalidSocket) return kInvalidSocket; @@ -110,7 +112,7 @@ void TCPListenSocket::Accept() { #if defined(OS_POSIX) sock->WatchSocket(WAITING_READ); #endif - socket_delegate_->DidAccept(this, sock.Pass()); + socket_delegate_->DidAccept(this, std::move(sock)); } } // namespace test_server diff --git a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h index 12b3fa40745..6990845f392 100644 --- a/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h +++ b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h @@ -7,7 +7,7 @@ #include -#include "base/basictypes.h" +#include "base/macros.h" #include "net/base/net_export.h" #include "net/socket/socket_descriptor.h" #include "net/test/embedded_test_server/stream_listen_socket.h" @@ -25,7 +25,7 @@ class TCPListenSocket : public StreamListenSocket { // accept local connections. static scoped_ptr CreateAndListen( const std::string& ip, - uint16 port, + uint16_t port, StreamListenSocket::Delegate* del); protected: @@ -39,11 +39,11 @@ class TCPListenSocket : public StreamListenSocket { friend class TCPListenSocketTester; // Get raw TCP socket descriptor bound to ip:port. - static SocketDescriptor CreateAndBind(const std::string& ip, uint16 port); + static SocketDescriptor CreateAndBind(const std::string& ip, uint16_t port); // Get raw TCP socket descriptor bound to ip and return port it is bound to. static SocketDescriptor CreateAndBindAnyPort(const std::string& ip, - uint16* port); + uint16_t* port); DISALLOW_COPY_AND_ASSIGN(TCPListenSocket); }; diff --git a/common.gypi b/common.gypi index 7c41c3616df..a9f067bcc54 100644 --- a/common.gypi +++ b/common.gypi @@ -4,6 +4,8 @@ 'vendor/brightray/brightray.gypi', ], 'variables': { + # Tell crashpad to build as external project. + 'crashpad_dependencies': 'external', # Required by breakpad. 'os_bsd': 0, 'chromeos': 0, @@ -41,6 +43,7 @@ 'target_conditions': [ ['_target_name in ["libuv", "http_parser", "openssl", "cares", "node", "zlib"]', { 'msvs_disabled_warnings': [ + 4003, # not enough actual parameters for macro 'V' 4013, # 'free' undefined; assuming extern returning int 4018, # signed/unsigned mismatch 4054, # @@ -227,6 +230,7 @@ 'msvs_cygwin_shell': 0, # Strangely setting it to 1 would make building under cygwin fail. 'msvs_disabled_warnings': [ 4005, # (node.h) macro redefinition + 4091, # (node_extern.h) '__declspec(dllimport)' : ignored on left of 'node::Environment' when no variable is declared 4189, # local variable is initialized but not referenced 4201, # (uv.h) nameless struct/union 4267, # conversion from 'size_t' to 'int', possible loss of data diff --git a/default_app/default_app.js b/default_app/default_app.js new file mode 100644 index 00000000000..eda1300f007 --- /dev/null +++ b/default_app/default_app.js @@ -0,0 +1,23 @@ +const electron = require('electron') +const app = electron.app +const BrowserWindow = electron.BrowserWindow + +var mainWindow = null + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + app.quit() +}) + +exports.load = function (appUrl) { + app.on('ready', function () { + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + autoHideMenuBar: true, + useContentSize: true + }) + mainWindow.loadURL(appUrl) + mainWindow.focus() + }) +} diff --git a/atom/browser/default_app/index.html b/default_app/index.html similarity index 53% rename from atom/browser/default_app/index.html rename to default_app/index.html index ec16a38bc42..80c347eff6d 100644 --- a/atom/browser/default_app/index.html +++ b/default_app/index.html @@ -3,31 +3,39 @@ Electron @@ -78,40 +91,47 @@

-

- To run your app with Electron, execute the following command under your - Console (or Terminal): -

+
- +

+ To run your app with Electron, execute the following command in your + 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 - - on how to write one. -

+ -

- Or you can just drag your app here to run it: -

+

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

+ +

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

+ +

+ Or you can just drag your app here to run it: +

+ +
+ Drag your app here to run it +
-
- Drag your app here to run it
+ + +``` + +## `require('electron').xxx` is undefined. + +Lors de l'utilisation des modules d'Electron, vous pouvez avoir une erreur : + +``` +> require('electron').webFrame.setZoomFactor(1.0); +Uncaught TypeError: Cannot read property 'setZoomLevel' of undefined +``` + +Ceci se produit quand vous avez le [module npm `electron`][electron-module] +d'installé, soit en local ou en global, ce qui a pour effet d'écraser les +modules de base d'Electron. + +Vous vérifiez que vous utilisez les bons modules, vous pouvez afficher le +chemin du module `electron` : + +```javascript +console.log(require.resolve('electron')); +``` + +et vérifier si il est de la forme : + +``` +"/path/to/Electron.app/Contents/Resources/atom.asar/renderer/api/lib/exports/electron.js" +``` + +S'il est de la forme `node_modules/electron/index.js`, vous devez supprimer le +module npm `electron`, ou le renommer. + +```bash +npm uninstall electron +npm uninstall -g electron +``` + +Si vous utilisez le module de base mais que vous continuez d'avoir +l'erreur, ça vient probablement du fait que vous utilisez le module dans le +mauvais processus. Par exemple `electron.app` peut uniquement être utilisé +dans le processus principal, tandis que `electron.webFrame` est uniquement +disponible dans le processus d'affichage. + +[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 diff --git a/docs-translations/fr-FR/styleguide.md b/docs-translations/fr-FR/styleguide.md new file mode 100644 index 00000000000..5fe781773c1 --- /dev/null +++ b/docs-translations/fr-FR/styleguide.md @@ -0,0 +1,101 @@ +# Règles de style pour la documentation d'Electron + +Choisissez la section appropriée : [lire la documentation d'Electron](#reading-electron-documentation) +ou [écrire de la documentation pour Electron](#writing-electron-documentation). + +## Ecrire de la documentation pour Electron + +La documentation d'Electron a été écrite en suivant les règles ci-dessous : + +- Maximum un titre `h1` par page. +- Utilisation de `bash` au lieu de `cmd` dans les blocs de code (à cause de la + coloration syntaxique). +- Les titres `h1` devraient reprendre le nom de l'objet (i.e. `browser-window` → + `BrowserWindow`). + - Cependant, les traits d'union sont acceptés pour les noms de fichier. +- Pas de titre directement après un autre, ajoutez au minimum une ligne de + description entre les deux. +- Les entêtes des méthodes sont entre accents graves (backquotes) `code`. +- Les entêtes des évènements sont entre des apostrophes 'quotation'. +- Les listes ne doivent pas dépasser 2 niveaux (à cause du formattage du + markdown). +- Ajouter des titres de section: Evènements, Méthodes de classe, et Méthodes + d'instance. +- Utiliser 'will' au lieu de 'would' lors de la description du retour. +- Les évènements et méthodes sont des titres `h3`. +- Les arguments optionnels sont notés `function (required[, optional])`. +- Les arguments optionnels sont indiqués quand appelés dans la liste. +- La longueur des lignes ne dépasse pas 80 caractères. +- Les méthodes spécifiques à une plateforme sont notées en italique. + - ```### `method(foo, bar)` _OS X_``` +- Préférer 'in the ___ process' au lieu de 'on' + +### Traductions de la Documentation + +Les traductions de la documentation d'Electron sont dans le dossier +`docs-translations`. + +Pour ajouter une nouvelle langue (ou commencer) : + +- Créer un sous-dossier avec comme nom le code langage. +- A l'intérieur de ce dossier, dupliquer le dossier `docs`, en gardant le même + nom de dossiers et de fichiers. +- Traduire les fichiers. +- Mettre à jour le `README.md` à l'intérieur du dossier de langue en mettant les + liens vers les fichiers traduits. +- Ajouter un lien vers le nouveau dossier de langue dans le [README](https://github.com/atom/electron#documentation-translations) + principal d'Electron. + +## Lire la documentation d'Electron + +Quelques indications pour comprendre la syntaxe de la documentation d'Electron. + +### Méthodes + +Un exemple de la documentation d'une [méthode](https://developer.mozilla.org/en-US/docs/Glossary/Method) +(Anglais) + +--- + +`methodName(required[, optional]))` + +* `require` String (**required**) +* `optional` Integer + +--- + +Le nom de la méthode est suivi des arguments de celle-ci. Les arguments +optionnels sont notés entre crochets, avec une virgule si ceux-ci suivent un +autre argument. + +En-dessous de la méthode, chaque argument est détaillé avec son type. +Celui-ci peut être un type générique : +[`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) +ou un type personnalisé comme le [`webContent`](api/web-content.md) d'Electron. + +### Evènements + +Un exemple d'une documentation d'un [évènement](https://developer.mozilla.org/en-US/docs/Web/API/Event) +(Anglais) +--- + +Event: 'wake-up' + +Returns: + +* `time` String + +--- + +L'évènement est une chaine utilisée après un listener `.on`. Si il retourne une +valeur, elle est écrite en dessous ainsi que son type. Si vous voulez écouter et +répondre à l'évènement wake-up, ça donne quelque chose comme : + +```javascript +Alarm.on('wake-up', function(time) { + console.log(time) +}) +``` diff --git a/docs-translations/jp/README.md b/docs-translations/jp/README.md index accde973976..a71661a7e53 100644 --- a/docs-translations/jp/README.md +++ b/docs-translations/jp/README.md @@ -3,6 +3,7 @@ そうでない場合、おそらくご使用の Electron のバージョンと互換性のない API 変更を含んだ development ブランチのドキュメントを使っているものと思われます。 その場合、atom.io の [available versions](http://electron.atom.io/docs/) リストにある別のバージョンのドキュメントに切り替えることができます。また GitHub で閲覧している場合、"Switch branches/tags" ドロップダウンを開いて、バージョンに対応したタグを選ぶこともできます。 +_リンクになっていないリストは未翻訳のものです。_ ## FAQ 頻繁に聞かれる質問がありますので、issueを作成する前にこれをチェックしてください。 @@ -31,3 +32,57 @@ ## API リファレンス * [概要](api/synopsis.md) +* [Process Object](api/process.md) +* [サポートしているChromeコマンドラインスイッチ](api/chrome-command-line-switches.md) +* [環境変数](api/environment-variables.md) + +### カスタムDOM要素: + +* [`File` Object](api/file-object.md) +* `` Tag +* [`window.open` 関数](api/window-open.md) + +### Main Processのモジュール: + +* [app](api/app.md) +* [autoUpdater](api/auto-updater.md) +* BrowserWindow + * [フレームの無いウィンドウ](api/frameless-window.md) +* [contentTracing](api/content-tracing.md) +* [dialog](api/dialog.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) +* webContents +* [Tray](api/tray.md) + +### Renderer Processのモジュール (Web Page): + +* [desktopCapturer](api/desktop-capturer.md) +* [ipcRenderer](api/ipc-renderer.md) +* [remote](api/remote.md) +* [webFrame](api/web-frame.md) + +### 両方のProcessのモジュール : + +* [clipboard](api/clipboard.md) +* [crashReporter](api/crash-reporter.md) +* [nativeImage](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.md) + +## 開発 + +* Coding Style +* Source Code Directory Structure +* Technical Differences to NW.js (formerly node-webkit) +* Build System Overview +* Build Instructions (OS X) +* Build Instructions (Windows) +* Build Instructions (Linux) +* Setting Up Symbol Server in debugger diff --git a/docs-translations/jp/api/app.md b/docs-translations/jp/api/app.md index d0df22bb404..168f7a09399 100644 --- a/docs-translations/jp/api/app.md +++ b/docs-translations/jp/api/app.md @@ -378,6 +378,7 @@ if (browserOptions.transparent) { // No transparency, so we load a fallback that uses basic styles. win.loadURL('file://' + __dirname + '/fallback.html'); } +``` ### `app.commandLine.appendSwitch(switch[, value])` @@ -431,6 +432,12 @@ dock アイコンを表示します。 アプリケーションの[dock menu][dock-menu]を設定します。 +### `app.dock.setIcon(image)` _OS X_ + +* `image` [NativeImage](native-image.md) + +dock アイコンに紐づいた`image`を設定します。 + [dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 [tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs-translations/jp/api/chrome-command-line-switches.md b/docs-translations/jp/api/chrome-command-line-switches.md new file mode 100644 index 00000000000..b3359eb322c --- /dev/null +++ b/docs-translations/jp/api/chrome-command-line-switches.md @@ -0,0 +1,133 @@ +#サポートしているChromeコマンドラインスイッチ + +ElectronでサポートしているChromeブラウザーで使用できるコマンドラインスイッチをこのページで一覧にしています。[app][app]モジュールの[ready][ready]イベントが出力される前にアプリのメインスクリプトに追加するために[app.commandLine.appendSwitch][append-switch]を使えます。 + +```javascript +const app = require('electron').app; +app.commandLine.appendSwitch('remote-debugging-port', '8315'); +app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1'); + +app.on('ready', function() { + // Your code here +}); +``` + +## --client-certificate=`path` + +クライアントの証明書ファイルの`path`を設定します。 + +## --ignore-connections-limit=`domains` + +接続数の制限を無視する`,`で分割した`domains`リスト + +## --disable-http-cache + +HTTPリクエストのディスクキャッシュの無効化。 + +## --remote-debugging-port=`port` + +`port`で指定したHTTP越しのリモートデバッグの有効化。 + +## --js-flags=`flags` + +JSエンジンに渡されるフラグの指定。メインプロセスで`flags`を有効化したいのなら、Electron開始時に渡される必要があります。 + +```bash +$ electron --js-flags="--harmony_proxies --harmony_collections" your-app +``` + +## --proxy-server=`address:port` + +システム設定を上書きし、指定したプロキシサーバーを使用します。HTTPS、WebSocketリクエストを含むHTTPプロトコルのリクエストのみに影響します。全てのプロキシサーバーがHTTPSとWebSocketリクエストに対応しているわけではないことに注意してください。 + +## --proxy-bypass-list=`hosts` + +ホスト一覧をセミコロンで分割したプロキシサーバーをバイパスしてするためにElectronに指示します。このフラグは、`--proxy-server`と同時に使われるときのみに影響します。 + +例: + +```javascript +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678') +``` + +ロカールアドレス(`localhost`や`127.0.0.1`など)、`google.com`サブドメイン、`foo.com` サフィックスを含むホスト、`1.2.3.4:5678`を除いてすべてのホストでプロキシサーバーが使われます。 + +## --proxy-pac-url=`url` + +`url`で指定したPACスクリプトが使われます。 + +## --no-proxy-server + +プロキシサーバーを使わず、常に直接接続します。遠輝ほかのプロキシサーバーフラグを上書きします。 + +## --host-rules=`rules` + +ホスト名がどのようにマップされているかを制御するコンマで分割された`rules`一覧 + +例: + +* `MAP * 127.0.0.1` 全てのホスト名を127.0.0.1にマッピングするよう強制します。 +* `MAP *.google.com proxy` すべてのgoogle.comサブドメインを "proxy"で解決するよう強制します。 +* `MAP test.com [::1]:77` "test.com"をIPv6ループバックで解決するよう強制します。結果ポートのをソケットアドレス77番にするよう強制します。 +* `MAP * baz, EXCLUDE www.google.com` 全てを"baz"に再マッピングし、 "www.google.com"は除外します。 + +これらのマッピングは、ネットリクエスト(直接接続で、TCP接続とホスト解決とHTTPプロキシ接続での`CONNECT`、`SOCKS`プロキシ接続でのエンドポイントホスト)でエンドポイントホストに適用されます。 + +## --host-resolver-rules=`rules` + +`--host-rules`のようですが、`rules` はホスト解決のみに適用されます。 + +## --ignore-certificate-errors + +証明書関連エラーを無視します。 + +## --ppapi-flash-path=`path` + +pepper flash pluginの`path`を設定します。 + +## --ppapi-flash-version=`version` + +pepper flash pluginの`version`を設定します。 + +## --log-net-log=`path` + +ネットログイベントを保存し、`path`に書き込みを有効化します。 + +## --ssl-version-fallback-min=`version` + +TLSフォールバックを許可する最小のSSL/TLSバージョン ("tls1"や"tls1.1" 、 "tls1.2")を設定します。 + +## --cipher-suite-blacklist=`cipher_suites` + +無効にするために、SSL暗号スイートのカンマ区切りのリストを指定します。 + +## --disable-renderer-backgrounding + +不可視のページのレンダラープロセスの優先度を下げることからChromiumを防ぎます。 + +このフラグは、グローバルですべてのレンダラープロセスに有効で、一つのウィンドウだけで無効化したい場合、[playing silent audio][play-silent-audio]をハックして対応します。 + +## --enable-logging + +コンソールにChromiumのログを出力します。 + +このスイッチは`app.commandLine.appendSwitch` で使えず、アプリがロードされるよりもパースしますが、同じ効果を受けるために`ELECTRON_ENABLE_LOGGING`を環境変数に設定します。 + +## --v=`log_level` + +既定の最大アクティブなV-loggingレベルが付与されています。0が既定です。通常、正の値はV-loggingレベルに使用されます。 + +`--enable-logging` が渡された時だけ、このスイッチは動作します。 + +## --vmodule=`pattern` + +`--v`で付与された値を上書きするために、モジュール毎の最大V-loggingレベルを付与します。例えば、 `my_module=2,foo*=3` は、`my_module.*` と `foo*.*`のソースファイル全てのロギングレベルを変更します。 + +前方または後方スラッシュを含む任意のパターンは、全体のパス名だけでなく、モジュールに対してもテストとされます。例えば、`*/foo/bar/*=2`は`foo/bar`ディレクトリ下のソースファイルですべてのコードのロギングレベルを変更します。 + +このスイッチは、`--enable-logging`が渡された時のみ動作します。 + +[app]: app.md +[append-switch]: app.md#appcommandlineappendswitchswitch-value +[ready]: app.md#event-ready +[play-silent-audio]: https://github.com/atom/atom/pull/9485/files diff --git a/docs-translations/jp/api/content-tracing.md b/docs-translations/jp/api/content-tracing.md new file mode 100644 index 00000000000..f7142a5001b --- /dev/null +++ b/docs-translations/jp/api/content-tracing.md @@ -0,0 +1,129 @@ +# contentTracing + +`content-tracing`モジュールは、Chromiumコンテンツモジュールによって生成されるトーレスデータを収集するのに使われます。このモジュールはウェブインターフェイスを含んでいないので、Chromeブラウザーで `chrome://tracing/`を開いて、結果を表示するために生成されたファイルを読み込む必要があります。 + +```javascript +const contentTracing = require('electron').contentTracing; + +const options = { + categoryFilter: '*', + traceOptions: 'record-until-full,enable-sampling' +} + +contentTracing.startRecording(options, function() { + console.log('Tracing started'); + + setTimeout(function() { + contentTracing.stopRecording('', function(path) { + console.log('Tracing data recorded to ' + path); + }); + }, 5000); +}); +``` + +## メソッド + +`content-tracing`モジュールは次のメソッドを持っています。 + +### `contentTracing.getCategories(callback)` + +* `callback` Function + +カテゴリグループ一式を取得します。新しいコードパスに到達しているとしてカテゴリグループを変更できます。 + +一度、全ての子プロセスが`getCategories`リクエストを認識すると、カテゴリグループの配列で`callback`が呼び出されます。 + +### `contentTracing.startRecording(options, callback)` + +* `options` Object + * `categoryFilter` String + * `traceOptions` String +* `callback` Function + +全てのプロセスで記録を開始します。 + +EnableRecordingリクエストを受信するとすぐに、子プロセス上でただちに非同期にローカルに記録を始めます。全ての子プロセスが`startRecording`リクエストを認識すると、`callback`が呼び出されます。 + +`categoryFilter`はどのカテゴリグループをトレースすべきかをフィルタリングします。フィルターは、マッチしたカテゴリーを含むカテゴリグループを除外する`-`プレフィックスをオプションうぃ持っています。同じリストでの、対象カテゴリパターンと、除外カテゴリーパターンの両方を持つことはサポートしていません。 + +例: + +* `test_MyTest*`, +* `test_MyTest*,test_OtherStuff`, +* `"-excluded_category1,-excluded_category2` + +`traceOptions` は、どの種類のトレースを有効にするかを制御し、コンマ区切りのリストです。 + +取りうるオプション: + +* `record-until-full` +* `record-continuously` +* `trace-to-console` +* `enable-sampling` +* `enable-systrace` + +最初の3つのオプションは、トレースの記録モードで、そのため相互排他的です。`traceOptions`文字列に1つ以上のトレース記録モードが現れると、最後のモードが優先されます。トレース記録モードが指定されていない場合、記録モードは、`record-until-full`です。 + +適用される`traceOptions`からオプションをパースする前に、トレースオプションは最初に既定のオプションにリセットされます(`record_mode`は、`record-until-full`を設定し、 `enable_sampling`と `enable_systrace` は `false`に設定します)。 + +### `contentTracing.stopRecording(resultFilePath, callback)` + +* `resultFilePath` String +* `callback` Function + +全てのプロセスで記録を止めます。 + +子プロセスは基本的にトレースデータをキャッシュし、まれにフラッシュし、メインプロセスにトレースデータを送り返します。IPC越しにトレースデータを送信するのは高コストな操作なので、トレースのランタイムオーバーヘッドを最小限にするのに役立ちます。トレースが終了すると、保留されているトレースデータのフラッシュをするためにすべての子プロセスに非道に問い合わせすべきです。 + + +一度、すべての子プロセスが`stopRecording` リクエストを認識すると、トレースデータを含んだファイルで`callback`が呼び出されます。 + +トレースデータは`resultFilePath`が空でなければ、そこに書き込まれ、空の場合は一時ファイルに書き込まれます。実際のファイルパスは`null`でなければ `callback` に通します。 + +### `contentTracing.startMonitoring(options, callback)` + +* `options` Object + * `categoryFilter` String + * `traceOptions` String +* `callback` Function + +全てのプロセス上で監視を開始します。 + +`startMonitoring`リクエスト受信するとすぐに、子プロセス上でローカルに非同期にただちに監視を始めます。 + +全ての子プロセスが`startMonitoring`リクエストを認識すると、`callback`がコールされます。 + +### `contentTracing.stopMonitoring(callback)` + +* `callback` Function + +全てのプロセス上で監視を止めます。 + +全ての子プロセスが`stopMonitoring`リクエスト認識すると、`callback`がコールされます。 + +### `contentTracing.captureMonitoringSnapshot(resultFilePath, callback)` + +* `resultFilePath` String +* `callback` Function + +現在の監視トレースデータを取得します。子プロセスは基本的にトレースデータをキャッシュし、まれにフラッシュし、メインプロセスにトレースデータを送り返します。IPC越しにトレースデータを送信するのは高コストな操作なので、トレースによる不必要なランタイムオーバーヘッドを避けるます。トレースが終了するために、保留されているトレースデータのフラッシュをするためにすべての子プロセスに非道に問い合わせすべきです。 + +全ての子プロセスが`captureMonitoringSnapshot`リクエストを認識すると、トレースデータを含んだファイルで`callback`が呼び出されます。 + +### `contentTracing.getTraceBufferUsage(callback)` + +* `callback` Function + +プロセスのトレースバッファのプロセス間で最大使用量をフルの状態の何%かで取得します。TraceBufferUsage値が設定されていると、 `callback`がコールされます。 + +### `contentTracing.setWatchEvent(categoryName, eventName, callback)` + +* `categoryName` String +* `eventName` String +* `callback` Function + +プロセス上でイベント発生すると、その度に`callback`がコールされます。 + +### `contentTracing.cancelWatchEvent()` + +イベントウオッチをキャンセルします。トレースが有効になっていると、監視イベントのコールバックとの競合状態になる可能性があります。 diff --git a/docs-translations/jp/api/dialog.md b/docs-translations/jp/api/dialog.md new file mode 100644 index 00000000000..c01fef3fa33 --- /dev/null +++ b/docs-translations/jp/api/dialog.md @@ -0,0 +1,88 @@ +# dialog + +`dialog`モジュールは、ファイルやアラートを開くようなネイティブシステムダイアログを表示するためのAPIを提供します。そのため、ネイティブアプリケーションのようにウェブアプリケーションに同じユーザー体験を提供できます。 + +複数のファイルやディレクトリを選択するためのダイアログを表示する例です: + +```javascript +var win = ...; // BrowserWindow in which to show the dialog +const dialog = require('electron').dialog; +console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); +``` + +**Note for OS X**: シートとしてダイアログを表示したい場合、唯一しなければならないことは、`browserWindow`パラメーターを参照する`BrowserWindow`を提供することです。 + +## メソッド + +`dialog`モジュールは次のメソッドを持っています: + +### `dialog.showOpenDialog([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (オプション) +* `options` Object + * `title` String + * `defaultPath` String + * `filters` Array + * `properties` Array - ダイアログが使うべき機能を含め、`openFile`と`openDirectory`、`multiSelections`、`createDirectory`を含められます。 +* `callback` Function (オプション) + +成功したら、このメソッドはユーザーが選択したファイルパスの配列を返し、さうでなければ`undefined`を返します。 + +ユーザーが選択できる種類を制限したいときに、`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: ['*'] } + ] +} +``` + +`extensions`配列は、ワイルドカードやドットなしで拡張子を指定すべきです(例えば、`'png'`は良いですが、`'.png'` と `'*.png'`はダメです)。すべてのファイルを表示するために、`'*'`ワイルドカードを使用します(それいがいのワイルドカードはサポートしていません)。 + +`callback`を通すと、APIは非同期に読み出し、結果は`callback(filenames)`経由で通します。 + +**Note:** WindowsとLinuxでは、オープンダイアログがファイル選択とディレクトリ選択の両方を選択することはできません。プラットフォーム上で `properties`に`['openFile', 'openDirectory']`を設定すると、ディレクトリ選択が表示されます。 + +### `dialog.showSaveDialog([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (オプション) +* `options` Object + * `title` String + * `defaultPath` String + * `filters` Array +* `callback` Function (オプション) + +成功すると、このメソッドはユーザーが選択したファイルのパスが返され、そうでなければ`undefined`が返されます。 + +`filters`が表示できるファイル種別配列を指定します。例えば、`dialog.showOpenDialog`を参照してください。 + +`callback`を通すと、APIは非同期でコールされ、結果は`callback(filename)`経由で通します。 + +### `dialog.showMessageBox([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (オプション) +* `options` Object + * `type` String - `"none"`と `"info"`、 `"error"`、 `"question"`、`"warning"`を設定できます。Windowsでは、 "icon"オプションを使用してアイコンを設定しない限り、"question"は"info"として同じアイコンを表示します。 + * `buttons` Array - ボタン用のテキスト配列。 + * `defaultId` Integer - メッセージボックスを開くとき、既定で選択されるボタン配列でのボタンインデックスです + * `title` String - メッセージボックスのタイトルで、いくつかのプラットフォームでは表示されません。 + * `message` String - メッセージボックスのコンテンツ。 + * `detail` String - メッセージの外部情報 + * `icon` [NativeImage](native-image.md) + * `cancelId` Integer - ダイアログのボタンをクリックする代わりにユーザーがダイアログをキャンセルしたときに返す値です。既定では、ラベルの "cancel"や"no"を持つボタンのインデックスまたは、そのようなボタンが無ければ0を返します。OS XやWindowsでは、 すでに指定されているかどうかは関係なく、"Cancel"ボタンのインデックスはいつでも `cancelId`が使われます。 + * `noLink` Boolean - Windowsでは、Electronは、 ("Cancel" または "Yes"のような)共通ボタンである`buttons`の一つを見つけようとし、ダイアログ内のコマンドリンクとして表示します。この挙動が気に入らない場合は、 `noLink` を `true`に設定できます。 +* `callback` Function + +メッセージボックスを表示し、メッセージボックスが閉じるまでプロセスをブロックします。クリックされたボタンのインデックスを返します。 + +`callback`が通されると、APIは非同期にコールし、結果は`callback(response)`経由で通されます。 + +### `dialog.showErrorBox(title, content)` + +エラーメッセージを表示するモデルダイアログを表示します。 + + `app`モジュールが`ready`イベントを出力する前に、このAPIは安全にコールできます。スタートアップの早い段階でエラーを報告するのに通常は使われます。Linuxで、アプリの`ready`イベントの前にコールすると、メッセージは標準エラーに出力され、GUIダイアログは表示されません。 diff --git a/docs-translations/jp/api/environment-variables.md b/docs-translations/jp/api/environment-variables.md index 39fd8f80e0f..f5277f14659 100644 --- a/docs-translations/jp/api/environment-variables.md +++ b/docs-translations/jp/api/environment-variables.md @@ -24,6 +24,11 @@ Windows コンソール上: Chromeのインターナルログをコンソールに出力します。 + +## `ELECTRON_LOG_ASAR_READS` + +ASARファイルからElectronが読み込んだとき、システム`tmpdir`へ読み込みオフセットとファイルのパスを記録します。ファイルの順序を最適化するために、得られたファイルはASARモジュールに提供されます。 + ## `ELECTRON_ENABLE_STACK_DUMPING` Electronがクラッシュしたとき、コンソールにスタックとレースを出力します。 diff --git a/docs-translations/jp/api/frameless-window.md b/docs-translations/jp/api/frameless-window.md new file mode 100644 index 00000000000..0b1e055602f --- /dev/null +++ b/docs-translations/jp/api/frameless-window.md @@ -0,0 +1,73 @@ +# Frameless Window + +フレームの無いウィンドウは、ウェブページの一部ではなく、ツールバーのようなウィンドウのパーツで、[chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome)ではないウィンドウです。 オプションとして[`BrowserWindow`](browser-window.md)クラスがあります。 + +## フレームの無いウィンドウを作成する + +フレームの無いウィンドウを作成するために、[BrowserWindow](browser-window.md)の `options`で、`frame` を `false`に設定する必要があります。 + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +var win = new BrowserWindow({ width: 800, height: 600, frame: false }); +``` + +### OS Xでの別の方法 + +Mac OS X 10.10 Yosemite以降では、Chrome無しのウィンドウを指定する方法があります。`frame`を`false`に設定しタイトルバーとウィンドウコントロールの両方を無効にする代わりに、タイトルバーを隠しコンテンツをフルウィンドウサイズに広げたいけど、標準的なウィンドウ操作用にウィンドウコントロール("トラフィックライト")を維持したいかもしれません。新しい`titleBarStyle`オプションを指定することで、そうできます。 + +```javascript +var win = new BrowserWindow({ 'titleBarStyle': 'hidden' }); +``` + +## 透明なウィンドウ + + `transparent`オプションを`true`に設定すると、フレームの無い透明なウィンドウを作成できます。 + +```javascript +var win = new BrowserWindow({ transparent: true, frame: false }); +``` + +### 制限 + +* 透明領域をクリックすることはできません。この問題を解決するためにウィンドウの輪郭を設定するAPIを導入しようとしています。詳細は、[our issue](https://github.com/atom/electron/issues/1335) を参照してください。 +* 透明なウィンドウはサイズ変更できません。いくつかのプラットフォーム上では、`resizable`の`true`設定は、いくつかのプラットフォーム上で、動作を停止する透明ウィンドウを作成するかもしれません。 +* `blur`フィルターはウェブページのみに適用され、ウィンドウの下のコンテンツ(例えば、ユーザーのシステム上でほかのアプリケーションを開く)に、ぼやける効果を適用する方法はありません。 +* Windows オペレーティングシステム上では、DMMが無効のとき透明なウィンドウは動作しません。 +* Linuxユーザーは、GPUを無効化するためにコマンドラインで`--enable-transparent-visuals --disable-gpu`を設定でき、透明ウィンドウを作成するためにARGBを許可でき、これは、 [いくつかのNVidiaドライバー上でアルファチャンネルが動作しない](https://code.google.com/p/chromium/issues/detail?id=369209) という上流のバグ原因になります。 +* Macでは、透明なウィンドウでネイティブのウィンドウシャドーを表示できません。 + +## ドラッグ可能領域 + +既定では、フレーム無しウィンドウはドラッグできません。(OSの標準的なタイトルバーのような)ドラッグできる領域をElectronに指定するには、CSSで`-webkit-app-region: drag`を指定する必要があり、アプリはドラッグできる領域からドラッグできない領域を除外するために、`-webkit-app-region: no-drag`を使えます。現在のところ長方形領域のみサポートしています。 + +ウィンドウ全体をドラッグできるようにするには、`body`のスタイルに`-webkit-app-region: drag`を追加します。 + +```html + + +``` + +ウィンドウ全体度ドラッグできるようにするには、ボタンをドラッグできないように指定する必要があり、そうしなければユーザーがそれをクリックする可能性があります。 + +```css +button { + -webkit-app-region: no-drag; +} +``` + +カスタムタイトルバーをドラッグできるように設定すると、タイトルバーのすべてのボタンをドラッグできないようにする必要があります。 + +## テキスト選択 + +フレームの無いウィンドウでは、ドラッグを可能にする動作とテキスト選択がぶつかるかもしれません。例えば、タイトルバーをドラッグしたとき、うっかりタイトルバーのテキストを選択してしまうかもしれません。これを防ぐために、次の例のようにドラッグできる領域内のテキスト選択を無効にする必要があります。 + +```css +.titlebar { + -webkit-user-select: none; + -webkit-app-region: drag; +} +``` + +## コンテキストメニュー + +いくつかのプラットフォームでは、ドラッグ可能な領域は、クライアントフレーム無しとして扱われるので、その上で右クリックすると、システムメニューがポップアップします。コンテキストメニューをすべてのプラットフォームで正しく動作するようにするためには、ドラッグ可能領域でカスタムコンテキストメニューを使用しないでください。 diff --git a/docs-translations/jp/api/ipc-renderer.md b/docs-translations/jp/api/ipc-renderer.md index 5854157363f..edbd862ffaa 100644 --- a/docs-translations/jp/api/ipc-renderer.md +++ b/docs-translations/jp/api/ipc-renderer.md @@ -52,7 +52,7 @@ * `channel` String - イベント名 * `arg` (optional) -`channel`経由でメインプロセスに非同期にイベントを送信し、任意の引数を送信できます。 +`channel`経由でメインプロセスに同期的にイベントを送信し、任意の引数を送信できます。 メインプロセスは`ipcMain`で`channel`を受信することでハンドルし、 `event.returnValue`を設定してリプライします。 diff --git a/docs-translations/jp/api/menu-item.md b/docs-translations/jp/api/menu-item.md new file mode 100644 index 00000000000..9e28582e622 --- /dev/null +++ b/docs-translations/jp/api/menu-item.md @@ -0,0 +1,50 @@ +# MenuItem + +`menu-item`モジュールは、アプリケーションまたはコンテキスト[`メニュー`](menu.md)に項目を追加することができます。 + +具体例は、 [`menu`](menu.md) を見てください。 + +## クラス: MenuItem + +次のメソッドで、新しい`MenuItem`を作成します。 + +### new MenuItem(options) + +* `options` Object + * `click` Function - メニューアイテムがクリックされたとき、`click(menuItem, browserWindow)`がコールされます。 + * `role` String - 指定されたとき、メニューアイテムの動作が定義され、`click`プロパティは無視されます。 + * `type` String - `normal`と `separator`、`submenu`、`checkbox`、`radio`を指定できます。 + * `label` String + * `sublabel` String + * `accelerator` [Accelerator](accelerator.md) + * `icon` [NativeImage](native-image.md) + * `enabled` Boolean + * `visible` Boolean + * `checked` Boolean + * `submenu` Menu - メニューアイテムを省略できる`type: 'submenu'`を指定したとき、`submenu`種類のメニューアイテムを指定すべきです。値が`Menu`でないとき、`Menu.buildFromTemplate`を使用して自動的に変換されます。 + * `id` String - 1つのメニュー内で一意です。定義されていたら、position属性によってアイテムへの参照として使用できます。 + * `position` String - このフィールドは、指定されたメニュー内の特定の位置を細かく定義できます。 + +メニューアイテムを作成するとき、適切な動作がある場合は、メニューでベストな自然な体験を提供するために、手動で実装する代わりに`role`を指定することを推奨します。 + +`role` プロパティは次の値を持ちます: + +* `undo` +* `redo` +* `cut` +* `copy` +* `paste` +* `selectall` +* `minimize` - 現在のウィンドウの最小化 +* `close` - 現在のウィンドウを閉じます + +OS Xでは、`role`は次の追加の値を取れます: + +* `about` - `orderFrontStandardAboutPanel`動作に紐づけられます +* `hide` - `hide`動作に紐づけられます +* `hideothers` - `hideOtherApplications`動作に紐づけられます +* `unhide` - `unhideAllApplications`動作に紐づけられます +* `front` - `arrangeInFront`動作に紐づけられます +* `window` - サブメニューは "Window"メニューです。 +* `help` - サブメニューは "Help"メニューです。 +* `services` - サブメニューは "Services"メニューです。 diff --git a/docs-translations/jp/api/menu.md b/docs-translations/jp/api/menu.md new file mode 100644 index 00000000000..12039b1d6ad --- /dev/null +++ b/docs-translations/jp/api/menu.md @@ -0,0 +1,337 @@ +# Menu + +`menu`クラスは、アプリケーションのメニューと[コンテキストメニュー](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/ContextMenus)として使えるネイティブメニューを作成するのに使われます。このモジュールは、`remote`モジュール経由でレンダラープロセスで使用できるメインプロセスのモジュールです。 + +個々のメニューは複数の[menu items](menu-item.md)で成り立ち、個々のメニューアイテムはサブメニューを持てます。 + +以下は、ユーザーはページを右クリックした時、[remote](remote.md)モジュールを作成するために、(レンダラープロセス)ウェブページで動的にメニューを作成して、表示します。 + +```html + + +``` + +シンプルなテンプレートAPIでレンダラープロセスでアプリケーションメニューを作成する例です。 + +```javascript +var 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' + }, + ] + }, + { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + click: function(item, focusedWindow) { + if (focusedWindow) + focusedWindow.reload(); + } + }, + { + label: 'Toggle Full Screen', + accelerator: (function() { + if (process.platform == 'darwin') + return 'Ctrl+Command+F'; + else + return '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(); + } + }, + ] + }, + { + label: 'Window', + role: 'window', + submenu: [ + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize' + }, + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close' + }, + ] + }, + { + label: 'Help', + role: 'help', + submenu: [ + { + label: 'Learn More', + click: function() { require('electron').shell.openExternal('http://electron.atom.io') } + }, + ] + }, +]; + +if (process.platform == 'darwin') { + var name = require('electron').remote.app.getName(); + template.unshift({ + label: name, + 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(); } + }, + ] + }); + // Window menu. + template[3].submenu.push( + { + type: 'separator' + }, + { + label: 'Bring All to Front', + role: 'front' + } + ); +} + +var menu = Menu.buildFromTemplate(template); +Menu.setApplicationMenu(menu); +``` + +## クラス: Menu + +### `new Menu()` + +新しいメニューを作成します。 + +## メソッド + +`menu`クラスーは次のメソッドを持ちます。 + +### `Menu.setApplicationMenu(menu)` + +* `menu` Menu + +OS Xで、アプリケーションメニューとして`menu`を設定します。WindowsとLinuxでは、`menu`はそれぞれのウィンドウの上のメニューとして設定されます。 + +### `Menu.sendActionToFirstResponder(action)` _OS X_ + +* `action` String + +アプリケーションの最初のレスポンダーに`action`が送信されます。規定のCocoaメニュー動作をエミュレートするために使われ、通常は`MenuItem`の`role`プロパティーを使います。 + +### `Menu.buildFromTemplate(template)` + +* `template` Array + +一般的に、`template`は、[MenuItem](menu-item.md)を組み立てるための `options`配列です。使用方法は下のように参照します。 + +ほかのフィールドに`template`の項目を設定でき、メニューアイテムを構成するプロパティです。 + +### `Menu.popup([browserWindow, x, y, positioningItem])` + +* `browserWindow` BrowserWindow (オプション) - 既定では`null`です。 +* `x` Number (オプション) - 既定では -1です。 +* `y` Number (**必須** `x` が使われている場合) - 既定では -1です。 +* `positioningItem` Number (オプション) _OS X_ - 既定では -1です。 + +メニューアイテムのインデックスを指定した座標にマウスカーソルを配置します。 + +`browserWindow`でコンテキストメニューとしてメニューをポップアップします。メニューを表示する場所をオプションで`x, y`座標を指定でき、指定しなければ現在のマウスカーソル位置に表示します。 + +### `Menu.append(menuItem)` + +* `menuItem` MenuItem + +メニューに`menuItem`を追加します。 + +### `Menu.insert(pos, menuItem)` + +* `pos` Integer +* `menuItem` MenuItem + +メニューの`pos`位置に`menuItem`を追加します。 + +### `Menu.items()` + +メニューのアイテムを収容した配列を取得します。 + +## OS X アプリケーションメニューの注意事項 + +OS Xは、WindowsとLinuxのアプリケーションのメニューとは完全に異なるスタイルを持ち、よりネイティブのようにアプリメニューを作成するのに幾つかの注意事項があります。 + +### 標準的なメニュー + +OS Xでは、`Services`と`Windows`メニューのように定義された標準的な多くのメニューがあります。標準的なメニューを作成するために、メニューの`role`に次のどれかを設定する必要があり、Electronはそれを受けて標準的なメニューを作成します。 + +* `window` +* `help` +* `services` + +### 標準的なメニューアイテムの動作 + +`About xxx`と`Hide xxx`、`Hide Others`のようないくつかのメニューアイテム用にOS Xは標準的な動作を提供します。メニューアイテムの動作に標準的な動作を設定するために、メニューアイテムの`role`属性を設定すべきです。 + +### メインのメニュー名 + +OS Xでは、設定したラベルに関係なく、アプリケーションの最初のアイテムのラベルはいつもアプリの名前です。変更するために、アプリにバンドルされている`Info.plist`ファイルを修正してアプリの名前を変更する必要があります。詳細は、 [About Information Property List Files][AboutInformationPropertyListFiles] を見てください。 + +## メニューアイテムの位置 + +`position`を使用することができ、`Menu.buildFromTemplate`でメニューを構築するときに`id`がアイテムを配置する方法をコントロールします。 + +`MenuItem`の`position`属性は、`[placement]=[id]`をもち、 `placement`は`before`や `after`、 `endof`の一つが設定され、`id`はメニューの設定されているアイテムで一意のIDです。 + +* `before` - IDから参照したアイテムの前にアイテムを挿入します。参照するアイテムが存在しないのなら、メニューの最後にアイテムが挿入されます。 +* `after` - IDから参照したアイテムの後にアイテムを挿入します。参照するアイテムが存在しないのなら、メニューの最後にアイテムが挿入されます。 +* `endof` -IDから参照したアイテムを含む論理グループの最後にアイテムを挿入します(グループはアイテムを分けるために作成されます)。参照するアイテムが存在しないのなら、付与されたIDで新しい分離グループが作成され、そのグループのあとにアイテムが挿入されます。 + +アイテムが配置されたとき、新しいアイテムが配置されるまで、すべての配置されていないアイテムがその後に挿入されます。同じ場所にメニューアイテムのグループを配置したいのなら、最初にアイテムで場所を指定する必要があります。 + +### 具体例 + +テンプレート: + +```javascript +[ + {label: '4', id: '4'}, + {label: '5', id: '5'}, + {label: '1', id: '1', position: 'before=4'}, + {label: '2', id: '2'}, + {label: '3', id: '3'} +] +``` + +メニュー: + +``` +- 1 +- 2 +- 3 +- 4 +- 5 +``` + +テンプレート: + +```javascript +[ + {label: 'a', position: 'endof=letters'}, + {label: '1', position: 'endof=numbers'}, + {label: 'b', position: 'endof=letters'}, + {label: '2', position: 'endof=numbers'}, + {label: 'c', position: 'endof=letters'}, + {label: '3', position: 'endof=numbers'} +] +``` + +メニュー: + +``` +- --- +- a +- b +- c +- --- +- 1 +- 2 +- 3 +``` + +[AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html diff --git a/docs-translations/jp/api/protocol.md b/docs-translations/jp/api/protocol.md new file mode 100644 index 00000000000..9f341b2f974 --- /dev/null +++ b/docs-translations/jp/api/protocol.md @@ -0,0 +1,175 @@ +# protocol + +`protocol`モジュールはカスタムプロトコルを登録したり、または既存のプロトコルをインターセプタ―することができます。 + +`file://`プロトコルの同様の効果をもつプロトコルを実装した例です。 + +```javascript +const electron = require('electron'); +const app = electron.app; +const path = require('path'); + +app.on('ready', function() { + var protocol = electron.protocol; + protocol.registerFileProtocol('atom', function(request, callback) { + var url = request.url.substr(7); + callback({path: path.normalize(__dirname + '/' + url)}); + }, function (error) { + if (error) + console.error('Failed to register protocol') + }); +}); +``` + +**Note:** このモジュールは、`app`モジュールで`ready`イベントが出力された後のみ使うことができます。 + +## メソッド + +`protocol`モジュールは、次のメソッドを持ちます。 + +### `protocol.registerStandardSchemes(schemes)` + +* `schemes` Array - 標準的なスキーマーを登録するためのカスタムスキーマー + +標準的な`scheme`は、RFC 3986で策定している[generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3)に準拠しています。これには`file:` と `filesystem:`を含んでいます。 + +### `protocol.registerServiceWorkerSchemes(schemes)` + +* `schemes` Array - サービスワーカーをハンドルするために登録されたカスタムスキーマー + +### `protocol.registerFileProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +レスポンスとしてファイルを送信する`scheme`のプロトコルを登録します。`scheme`で`request`が生成された時、`handler`は`handler(request, callback)`で呼び出されます。`scheme` 登録が成功したり、`completion(error)`が失敗したときに、`completion` は`completion(null)`で呼び出されます。 + +* `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` Array (オプション) +* `callback` Function + +`uploadData` は `data` オブジェクトの配列です: + +* `data` Object + * `bytes` Buffer - 送信するコンテンツ + * `file` String - アップロードするファイルパス + +`request`をハンドルするために、`callback`はファイルパスまたは`path`プロパティを持つオブジェクトで呼び出すべきです。例えば、`callback(filePath)` または`callback({path: filePath})`です。 + +何もなし、数字、`error`プロパティを持つオブジェクトで、`callback`が呼び出された時、 `request`は指定した`error`番号で失敗します。使用できる提供されているエラー番号は、[net error list][net-error]を参照してください。 + +既定では、`scheme`は、`file:`のような一般的なURIの構文に続くプロトコルと違う解析がされ、`http:`のように扱われます。なので、恐らく標準的なスキーマーのように扱われるスキーマーを持つために、`protocol.registerStandardSchemes` を呼び出したくなります。 + +### `protocol.registerBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +レスポンスとして`Buffer`を送信する`scheme`プロトコルを登録します。 + + `callback`は、`Buffer`オブジェクトまたは、`data`と`mimeType`、 `charset`プロパティを持つオブジェクトのどちらかで呼ばれる必要があることを除いて、この使用方法は、`registerFileProtocol`と同じです。 + +例: + +```javascript +protocol.registerBufferProtocol('atom', function(request, callback) { + callback({mimeType: 'text/html', data: new Buffer('
Response
')}); +}, function (error) { + if (error) + console.error('Failed to register protocol') +}); +``` + +### `protocol.registerStringProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +レスポンスとして`String`を送信する`scheme`プロトコルを登録します。 + +`callback`は、`String`または`data`と `mimeType`、`chart`プロパティを持つオブジェクトを呼び出す必要があることを除いて、使用方法は`registerFileProtocol`と同じです。 + +### `protocol.registerHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +レスポンスとしてHTTPリクエストを送信する`scheme`プロトコルを登録します。 + +`callback`は、`url`と`method`、`referrer`、`uploadData`、`session`プロパティを持つオブジェクトを呼び出す必要があることを除いて、使用方法は`registerFileProtocol`と同じです。 + +* `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (オプション) + * `uploadData` Object (オプション) + +既定では、HTTPリクエストは現在のセッションを再利用します。別のセッションでリクエストをしたい場合、`session` に `null`を設定する必要があります。 + +POSTリクエストは`uploadData`オブジェクトを提供する必要があります。 +* `uploadData` object + * `contentType` String - コンテンツのMIMEタイプ + * `data` String - 送信されるコンテンツ + +### `protocol.unregisterProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function (optional) + +`scheme`のカスタムプロトコルを解除します。 + +### `protocol.isProtocolHandled(scheme, callback)` + +* `scheme` String +* `callback` Function + +`scheme`のハンドラーがあるかないかを示すブーリアン値で`callback`がコールされます。 + +### `protocol.interceptFileProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +`scheme`プロトコルをインターセプタ―し、レスポンスとしてファイルを送信するプロトコルの新しいハンドラーとして`handler`を使います。 + +### `protocol.interceptStringProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +`scheme`プロトコルをインターセプタ―し、レスポンスとして`String`を送信するプロトコルの新しいハンドラーとして`handler`を使います。 + +### `protocol.interceptBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +`scheme`プロトコルをインターセプタ―し、レスポンスとして`Buffer`を送信するプロトコルの新しいハンドラーとして`handler`を使います。 + +### `protocol.interceptHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +`scheme`プロトコルをインターセプタ―し、レスポンスとして新しいHTTPリクエストを送信するプロトコルの新しいハンドラーとして`handler`を使います。 + +### `protocol.uninterceptProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function + +インターセプタ―したインストールされた`scheme`を削除し、オリジナルハンドラーをリストアします。 + + +[net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h diff --git a/docs-translations/jp/api/remote.md b/docs-translations/jp/api/remote.md new file mode 100644 index 00000000000..0d4ec0d389e --- /dev/null +++ b/docs-translations/jp/api/remote.md @@ -0,0 +1,124 @@ +# remote + + `remote`モジュールは、レンダラープロセス(ウェブページ)とメインプロセス間でインタープロセスコミュニケーション(IPC)をする簡単な方法を提供します。 + +Electronでは、GUI関連モジュール(`dialog`や`menu`など)はメインプロセスのみに提供されており、レンダラープロセスには提供されていません。レンダラープロセスからそれらを使うために、`ipc`モジュールはメインプロセスにインタープロセスメッセージを送信するのに必要です。`remote`モジュールで、Javaの[RMI][rmi]と同じように、はっきりとインタープロセスメッセージを送信しなくてもメインプロセスオブジェクトのメソッドを呼び出せます。 + +レンダラープロセスからブラウザーウィンドウを作成する例: + +```javascript +const remote = require('electron').remote; +const BrowserWindow = remote.BrowserWindow; + +var win = new BrowserWindow({ width: 800, height: 600 }); +win.loadURL('https://github.com'); +``` + +**Note:** 逆には(メインプロセスからレンダラープロセスにアクセスする)、[webContents.executeJavascript](web-contents.md#webcontentsexecutejavascriptcode-usergesture)が使えます。 + +## Remote オブジェクト + +`remote`モジュールから返されるそれぞれのオブジェクト(関数含む)はメインプロセスでオブジェクトを示します(リモートオブジェクトまたはリモート関数と呼ばれます)。リモートプロジェクトのメソッドを実行したり、リモート関数をコールしたり、リモートコンストラクター(関数)で新しいオブジェクトを生成したりしたとき、実際に非同期にインタープロセスメッセージが送信されます。 + +上の例では、`BrowserWindow` と `win` はリモートオブジェクトで、`new BrowserWindow`はレンダラープロセスで `BrowserWindow`を作成しません。代わりに、メインプロセスで`BrowserWindow` オブジェクトが作成され、レンダラープロセスで対応するリモートオブジェクトを返し、すなわち`win`オブジェクトです。 + +リモート経由でのみアクセスできる [enumerable properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)に注意してください。 + +## Remote オブジェクトのライフタイム + +Electronは、レンダラープロセスのリモートオブジェクトが生きている限り(言い換えれば、ガベージコレクションされません)、対応するメインプロセスのオブジェクトは解放されないことを確認してください。リモートオブジェクトがガベージコレクションされたとき、対応するメインプロセスのオブジェクトが間接参照されます。 + +レンダラープロセスでリモートオブジェクトがリークした場合(マップに格納されているが解放されない)、メインプロセスで対応するオブジェクトもリークするので、リモートオブジェクトがリークしないように細心の注意を払うべきです。 + +文字列や数字のようなプライマリ値は、コピーして送信します。 + +## メインプロセスにコールバックを通す + +メインプロセスのコードは、レンダラーからコールバックを受け取ることができます。例えば、`remote`モジュールです。この機能をとても慎重に使うべきです。 + +最初に、デッドロックを避けるために、メインプロセスに渡されたコールバックは非同期に呼び出されます。メインプロセスは、渡されたコールバックの戻り値を取得することを期待すべきではありません。 + +例えば、メインプロセスでコールされた`Array.map`で、レンダラープロセスから関数を使用することはできません。: + +```javascript +// main process mapNumbers.js +exports.withRendererCallback = function(mapper) { + return [1,2,3].map(mapper); +} + +exports.withLocalCallback = function() { + return exports.mapNumbers(function(x) { + return x + 1; + }); +} +``` + +```javascript +// renderer process +var mapNumbers = require("remote").require("./mapNumbers"); + +var withRendererCb = mapNumbers.withRendererCallback(function(x) { + return x + 1; +}) + +var withLocalCb = mapNumbers.withLocalCallback() + +console.log(withRendererCb, withLocalCb) // [true, true, true], [2, 3, 4] +``` + +見ることができるように、レンダラーコールバックの同期戻り値は予想されなかったとして、メインプロセスで生きている同一のコールバックの戻り値と一致しません。 + +第二に、メインプロセスに渡されるコールバックはメインプロセスがガベージコレクトするまで存続します。 + +例えば、次のコードは一目で無害なコードのように思えます。リモートオブジェクト上で`close`イベントのコールバックをインストールしています。 + +```javascript +remote.getCurrentWindow().on('close', function() { + // blabla... +}); +``` + +りかし、明確にアンインストールするまでメインプロセスによってコールバックは参照されることを覚えておいてください。アンインストールしない場合、ウィンドウをリロードするたびに、コールバックは再度インストールされ、それぞれの再起動時にコールバックあリークします。 + +さらに悪いことに、前にインストールされたコールバックのコンテキストは解放されるので、`close`イベントを出力されると、メインプロセスで例外が発生します。 + +この問題を避けるために、メインプロセスに渡されたレンダラーコールバックへの参照をクリーンアップを確認します。これにはイベントハンドラーのクリンアップも含まれ、存在しているレンダラープロセスから来るコールバックを確実にメインプロセスが守るように確認します。 + +## メインプロセスで組み込みモジュールにアクセスする + +メインプロセスの組み込みモジュールは、`remote`モジュールでゲッターとして追加されるので、`electron`モジュールのように直接それらを使用できます。 + +```javascript +const app = remote.app; +``` + +## メソッド + +`remote`モジュールは次のメソッドを持ちます。 + +### `remote.require(module)` + +* `module` String + +メインプロセスで、`require(module)`で返されるオブジェクトを返します。 + +### `remote.getCurrentWindow()` + +このウェブページに属する[`BrowserWindow`](browser-window.md) オブジェクトを返します。 + +### `remote.getCurrentWebContents()` + +このウェブページの[`WebContents`](web-contents.md) オブジェクトを返します。 + +### `remote.getGlobal(name)` + +* `name` String + +メインプロセスで、`name`のグローバル変数(例えば、`global[name]`)を返します。 + + +### `remote.process` + +メインプロセスで`process`オブジェクトを返します。これは`remote.getGlobal('process')`と同様ですが、キャッシュされます。 + +[rmi]: http://en.wikipedia.org/wiki/Java_remote_method_invocation diff --git a/docs-translations/jp/api/session.md b/docs-translations/jp/api/session.md new file mode 100644 index 00000000000..a5d47d404a6 --- /dev/null +++ b/docs-translations/jp/api/session.md @@ -0,0 +1,445 @@ +# session + +`session`モジュールは、新しい`Session`オブジェクトを作成するのに使われます。 + +[`BrowserWindow`](browser-window.md)のプロパティである [`webContents`](web-contents.md)プロパティの`session`を使うことで既存ページの `session`にアクセスできます。 + +```javascript +const BrowserWindow = require('electron').BrowserWindow; + +var win = new BrowserWindow({ width: 800, height: 600 }); +win.loadURL("http://github.com"); + +var ses = win.webContents.session; +``` + +## メソッド + + `session`メソッドは次のメソッドを持ちます: + +### session.fromPartition(partition) + +* `partition` String + +`partition`文字列から新しい`Session`インスタンスを返します。 + +`partition`が`persist:`から始まっている場合、同じ`partition`のアプリ内のすべてのページに永続セッションを提供するのにページが使います。`persist:`プレフィックスが無い場合、ページはインメモリセッションを使います。`partition`が空の場合、アプリの既定のセッションを返します。 + +## プロパティ + +`session`モジュールは次のプロパティを持ちます: + +### session.defaultSession + +アプリの既定のセッションオブジェクトを返します。 + +## クラス: Session + +`session`モジュールで、`Session`オブジェクトを作成できます: + +```javascript +const session = require('electron').session; + +var ses = session.fromPartition('persist:name'); +``` + +### インスタンスイベント + +`Session`のインスタンス上で次のイベントが提供されます: + +#### イベント: 'will-download' + +* `event` Event +* `item` [DownloadItem](download-item.md) +* `webContents` [WebContents](web-contents.md) + +Electronが`webContents`で`item`をダウンロードしようとすると出力されます。 + +`event.preventDefault()` をコールするとダウンロードをキャンセルできます。 + +```javascript +session.defaultSession.on('will-download', function(event, item, webContents) { + event.preventDefault(); + require('request')(item.getURL(), function(data) { + require('fs').writeFileSync('/somewhere', data); + }); +}); +``` + +### インスタンスのメソッド + +`Session`のインスタンス上で次のメソッドが提供されています: + +#### `ses.cookies` + +`cookies`は、cookiesに問い合わせしたり、修正をできるようにします。例: + +```javascript +// Query all cookies. +session.defaultSession.cookies.get({}, function(error, cookies) { + console.log(cookies); +}); + +// Query all cookies associated with a specific url. +session.defaultSession.cookies.get({ url : "http://www.github.com" }, function(error, cookies) { + console.log(cookies); +}); + +// Set a cookie with the given cookie data; +// may overwrite equivalent cookies if they exist. +var cookie = { url : "http://www.github.com", name : "dummy_name", value : "dummy" }; +session.defaultSession.cookies.set(cookie, function(error) { + if (error) + console.error(error); +}); +``` + +#### `ses.cookies.get(filter, callback)` + +* `filter` Object + * `url` String (オプション) - `url`に関連付けられているcookiesを取得します。空の場合すべてのurlのcookiesを取得します + * `name` String (オプション) - `name`でcookiesをフィルタリングします + * `domain` String (オプション) - `domains`のドメインまたはサブドメインに一致するcookiesを取得します + * `path` String (オプション) - `path`に一致するパスのcookiesを取得します + * `secure` Boolean (オプション) - Secure プロパティでcookiesをフィルターします + * `session` Boolean (オプション) - Filters out `session`または永続cookiesを除外します +* `callback` Function + +`details`に一致するすべてのcookiesを取得するためにリクエストを送信し、完了時に`callback(error, cookies)`で`callback`がコールされます。 + +`cookies` は`cookie`オブジェクトの配列です。 + +* `cookie` Object + * `name` String - cookieの名前 + * `value` String - cookieの値 + * `domain` String - cookieのドメイン + * `hostOnly` String - cookieがホストのみのcookieかどうか + * `path` String - cookieのパス + * `secure` Boolean - cookieがセキュアとマークされているかどうか + * `httpOnly` Boolean - HTTPのみとしてcookieがマークされているかどうか + * `session` Boolean - cookieがセッションcookieまたは有効期限付きの永続cookieかどうか + * `expirationDate` Double (オプション) - + +cookieの有効期限をUNIX時間で何秒かを示します。セッションcookiesは提供されません。 + +#### `ses.cookies.set(details, callback)` + +* `details` Object + * `url` String - `url`に関連付けられているcookiesを取得します。 + * `name` String - cookieの名前。省略した場合、既定では空です。 + * `value` String - cookieの名前。省略した場合、既定では空です。 + * `domain` String - cookieのドメイン。省略した場合、既定では空です。 + * `path` String - cookieのパス。 省略した場合、既定では空です。 + * `secure` Boolean - cookieをセキュアとしてマークする必要があるかどうか。既定ではfalseです。 + * `session` Boolean - cookieをHTTPのみとしてマークする必要があるかどうか。既定ではfalseです。 + * `expirationDate` Double - cookieの有効期限をUNIX時間で何秒か。省略した場合、cookieはセッションcookieになります。 +* `callback` Function + +`details`でcookieを設定し、完了すると`callback(error)`で`callback`がコールされます。 + +#### `ses.cookies.remove(url, name, callback)` + +* `url` String - cookieに関連付けられているURL +* `name` String - 削除するcookieの名前 +* `callback` Function + +`url` と `name`と一致するcookiesを削除し、完了すると`callback`が、`callback()`でコールされます。 + +#### `ses.getCacheSize(callback)` + +* `callback` Function + * `size` Integer - 使用しているキャッシュサイズバイト数 + +現在のセッションのキャッシュサイズを返します。 + +#### `ses.clearCache(callback)` + +* `callback` Function - 操作が完了したら、コールされます。 + +セッションのHTTPキャッシュをクリアします。 + +#### `ses.clearStorageData([options, ]callback)` + +* `options` Object (オプション) + * `origin` String - `window.location.origin`の説明で、`scheme://host:port`に従う + * `storages` Array - クリアするストレージの種類で、次を含められます: + `appcache`、 `cookies`、 `filesystem`、 `indexdb`、 `local storage`、 + `shadercache`、 `websql`、 `serviceworkers` + * `quotas` Array - クリアするクォーターの種類で、次を含められます: + `temporary`, `persistent`, `syncable`. +* `callback` Function - 操作をするとコールされます。 + +ウェブストレージのデータをクリアします。 + +#### `ses.flushStorageData()` + +書き込まれていないDOMStorageデータをディスクに書き込みます。 + +#### `ses.setProxy(config, callback)` + +* `config` Object + * `pacScript` String - PACファイルに関連付けらえたURL + * `proxyRules` String - 使用するプロキシを指定するルール +* `callback` Function - 操作をするとコールされます。 + +プロキシ設定を設定します。 + +`pacScript` と `proxyRules`が一緒に渡されたら、`proxyRules`オプションは無視され、`pacScript`設定が適用されます。 + + `proxyRules`はつふぃのルールに従います。 + +``` +proxyRules = schemeProxies[";"] +schemeProxies = ["="] +urlScheme = "http" | "https" | "ftp" | "socks" +proxyURIList = [","] +proxyURL = ["://"][":"] +``` + +具体例: + +* `http=foopy:80;ftp=foopy2` - `http://`URLは`foopy:80`HTTPプロキシを使用し、`ftp://`URLは`foopy2:80` HTTPプロキシを使用します。 +* `foopy:80` - 全てのURLで`foopy:80`を使用します。 +* `foopy:80,bar,direct://` - 全てのURLで`foopy:80`HTTPプロキシを使用し、`foopy:80`が提供されていなければ`bar`を使用し、さらに使えない場合はプロキシを使いません。 +* `socks4://foopy` - 全てのURLでSOCKS `foopy:1080`プロキシを使います。 +* `http=foopy,socks5://bar.com` - http URLで`foopy`HTTPプロキシを使い、`foopy`が提供されていなければ、SOCKS5 proxy `bar.com`を使います。 +* `http=foopy,direct://` -  http URLで`foopy`HTTPプロキシを使い、`foopy`が提供されていなければ、プロキシを使いません。 +* `http=foopy;socks=foopy2` - http URLで`foopy`HTTPプロキシを使い、それ以外のすべてのURLで`socks4://foopy2`を使います。 + +### `ses.resolveProxy(url, callback)` + +* `url` URL +* `callback` Function + +`url`をプロキシ情報で解決します。リクエストが実行された時、`callback(proxy)`で `callback`がコールされます。 + +#### `ses.setDownloadPath(path)` + +* `path` String - ダウンロード場所 + +ダウンロードの保存ディレクトリを設定します。既定では、ダウンロードディレクトリは、個別のアプリフォルダー下の`Downloads`です。 + +#### `ses.enableNetworkEmulation(options)` + +* `options` Object + * `offline` Boolean - ネットワーク停止を再現するかどうか + * `latency` Double - RTT ms秒 + * `downloadThroughput` Double - Bpsでのダウンロード割合 + * `uploadThroughput` Double - Bpsでのアップロード割合 + +再現ネットワークは、`session`用の設定を付与します。 + +```javascript +// To emulate a GPRS connection with 50kbps throughput and 500 ms latency. +window.webContents.session.enableNetworkEmulation({ + latency: 500, + downloadThroughput: 6400, + uploadThroughput: 6400 +}); + +// To emulate a network outage. +window.webContents.session.enableNetworkEmulation({offline: true}); +``` + +#### `ses.disableNetworkEmulation()` + +`session`ですでに有効になっているネットワークエミュレーションを無効化します。オリジナルのネットワーク設定にリセットします。 + +#### `ses.setCertificateVerifyProc(proc)` + +* `proc` Function + +`session`の証明書検証ロジックを設定し、サーバー証明書確認がリクエストされた時、`proc(hostname, certificate, callback)`で`proc`がコールされます。`callback(true)`がコールされると証明書を受け入れ、`callback(false)`がコールされると拒否します。 + +Calling `setCertificateVerifyProc(null)`をコールして、既定の証明書検証ロジックに戻します。 + +```javascript +myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, callback) { + if (hostname == 'github.com') + callback(true); + else + callback(false); +}); +``` + +#### `ses.webRequest` + +`webRequest`APIセットをインターセプトし、そのライフタイムの様々な段階でリクエストの内容を変更できます。 + +APIのイベントが発生したとき、それぞれのAPIはオプションで`filter`と `listener`を受け入れ、`listener(details)` で`listener`がコールされ、`details`はリクエストを説明するオブジェクトです。`listener`に`null`が渡されるとイベントの購読をやめます。 + +`filter`は`urls`プロパティを持つオブジェクトで、URLパターンにマッチしないリクエストを除外するのに使われるURLパターンの配列です。`filter`を省略した場合、全てのリクエストにマッチします。 + +いくつかのイベントで`callback`で`listener`に渡され、`listener`が動作するとき、`response`オブジェクトでコールされる必要があります。 + +```javascript +// Modify the user agent for all requests to the following urls. +var filter = { + urls: ["https://*.github.com/*", "*://electron.github.io"] +}; + +session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, callback) { + details.requestHeaders['User-Agent'] = "MyAgent"; + callback({cancel: false, requestHeaders: details.requestHeaders}); +}); +``` + +#### `ses.webRequest.onBeforeRequest([filter, ]listener)` + +* `filter` Object +* `listener` Function + +リクエストが発生しようとしている時、`listener(details, callback)`で`listener` がコールされます。 + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `uploadData` Array (オプション) + * `callback` Function + +`uploadData`は `data`オブジェクトの配列です。 + +* `data` Object + * `bytes` Buffer - 送信されるコンテンツ + * `file` String - アップロードされるファイルパス + +`callback`は`response`オブジェクトでコールされる必要があります: + +* `response` Object + * `cancel` Boolean (オプション) + * `redirectURL` String (オプション) - オリジナルリクエストが送信もしくは完了するのを中断し、代わりに付与したURLにリダイレクトします。 + +#### `ses.webRequest.onBeforeSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +リクエストヘッダーが提供されれば、HTTPリクエストが送信される前に、`listener(details, callback)`で`listener`がコールされます。TCP接続がサーバーに対して行われた後に発生することがありますが、HTTPデータは送信前です。 + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object +* `callback` Function + +The `callback` has to be called with an `response` object: + +* `response` Object + * `cancel` Boolean (オプション) + * `requestHeaders` Object (オプション) - 付与されると、リクエストはそれらのヘッダーで作成されます。 + +#### `ses.webRequest.onSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +サーバーにリクエストを送信しようする直前に`listener(details)`で、`listener` がコールされます。前回の`onBeforeSendHeaders`レスポンスの変更箇所は、このリスナーが起動した時点で表示されます。 + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +#### `ses.webRequest.onHeadersReceived([filter,] listener)` + +* `filter` Object +* `listener` Function + +リクエストのHTTPレスポンスヘッダーを受信したとき、`listener`は`listener(details, callback)`でコールされます。 + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `statusLine` String + * `statusCode` Integer + * `responseHeaders` Object +* `callback` Function + +`callback`は`response`オブジェクトでコールされる必要があります: + +* `response` Object + * `cancel` Boolean + * `responseHeaders` Object (オプション) - 付与されていると、これらのヘッダーでサーバーはレスポンスしたと仮定します。 + +#### `ses.webRequest.onResponseStarted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +レスポンスボディの最初のバイトを受信したとき、`listener` は`listener(details)` でコールされます。HTTPリクエストでは、ステータス行とレスポンスヘッダーを意味します。 + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean -ディスクキャッシュから取得したレスポンスかどうかを示します + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onBeforeRedirect([filter, ]listener)` + +* `filter` Object +* `listener` Function + +サーバーがリダイレクトを開始しはじめたとき、`listener(details)`で`listener` がコールされます。 + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `redirectURL` String + * `statusCode` Integer + * `ip` String (オプション) - 実際にリクエストが送信されるサーバーIPアドレス + * `fromCache` Boolean + * `responseHeaders` Object + +#### `ses.webRequest.onCompleted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +リクエスト完了時、`listener`が`listener(details)`でコールされます。 + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onErrorOccurred([filter, ]listener)` + +* `filter` Object +* `listener` Function + +エラー発生時、 `listener(details)` で`listener`がコールされます。 + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `fromCache` Boolean + * `error` String - エラーの説明 diff --git a/docs-translations/jp/api/synopsis.md b/docs-translations/jp/api/synopsis.md index a2f701807ea..683f5611eb1 100644 --- a/docs-translations/jp/api/synopsis.md +++ b/docs-translations/jp/api/synopsis.md @@ -4,7 +4,7 @@ Electron では全ての [Node.js のビルトインモジュール](http://node Electron はネイティブのデスクトップアプリケーション開発のための幾つかの追加のビルトインモジュールも提供しています。メインプロセスでだけ使えるモジュールもあれば、レンダラプロセス(ウェブページ)でだけ使えるモジュール、あるいはメインプロセス、レンダラプロセスどちらでも使えるモジュールもあります。 -基本的なルールは:[GUI][gui]、または低レベルのシステムに関連するモジュールはメインモジュールでだけ利用できるべきです。これらのモジュールを使用できるようにするためには [メインプロセス対レンダラプロセス][main-process] スクリプトの概念を理解する必要があります。 +基本的なルールは:[GUI][gui]、または低レベルのシステムに関連するモジュールはメインモジュールでだけ利用できるべきです。これらのモジュールを使用できるようにするためには [メインプロセス対レンダラプロセス](../tutorial/quick-start.md#メインプロセス)スクリプトの概念を理解する必要があります。 メインプロセススクリプトは普通の Node.js スクリプトのようなものです: @@ -64,6 +64,5 @@ require('electron').hideInternalModules() ``` [gui]: https://en.wikipedia.org/wiki/Graphical_user_interface -[main-process]: ../tutorial/quick-start.md#メインプロセス [desctructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment [issue-387]: https://github.com/atom/electron/issues/387 diff --git a/docs-translations/jp/api/tray.md b/docs-translations/jp/api/tray.md new file mode 100644 index 00000000000..5c76e740530 --- /dev/null +++ b/docs-translations/jp/api/tray.md @@ -0,0 +1,199 @@ +# Tray + +`Tray`は、オペレーティングシステムの通知エリアでアイコンで表示され、通常コンテキストメニューが付随します。 + +```javascript +const electron = require('electron'); +const app = electron.app; +const Menu = electron.Menu; +const Tray = electron.Tray; + +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' } + ]); + appIcon.setToolTip('This is my application.'); + appIcon.setContextMenu(contextMenu); +}); + +``` + +__プラットフォームの制限:__ + +* Linuxでは、サポートしている場合アプリインディケーターが使われ、サポートされていなければ代わりに`GtkStatusIcon`が使われます。 +* アプリインディケーターを持っているLinuxディストリビューションでは、トレイアイコンを動作させるために`libappindicator1`をインストールする必要があります。 +* コンテキストメニューがあるときは、インディケーターのみが表示されます。 +* アプリインディケーターがLinuxで使われると、`click`イベントは無視されます。 + +すべてのプラットフォームで正確に同じ挙動を維持したい場合は、`click`イベントに依存せず、常にトレイアイコンにコンテキストメニューを付随させるようにします。 + +## クラス: Tray + +`Tray`は[EventEmitter][event-emitter]です。 + +### `new Tray(image)` + +* `image` [NativeImage](native-image.md) + +`image`で新しいトレイアイコンを作成します。 + +## イベント + +`Tray`モジュールは次のイベントを出力します。 + +**Note:** いくつかのイベントは、特定のオペレーティングシステム向けに提供され、そのようにラベルで表示します。 + +### イベント: 'click' + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - トレイアイコンのバウンド + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +トレイアイコンがクリックされたときに出力されます。 + +__Note:__ `バウンド` 再生はOS XとWindoesのみで実装されています。 + +### イベント: 'right-click' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - トレイアイコンのバウンド + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +トレイアイコンが右クリックされると出力されます。 + +### イベント: 'double-click' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - トレイアイコンのバウンド + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +トレイアイコンがダブルクリックされたら出力されます。 + +### イベント: 'balloon-show' _Windows_ + +トレイバルーンを表示したときに出力されます。 + +### イベント: 'balloon-click' _Windows_ + +トレイバルーンがクリックされたときに出力されます。 + +### イベント: 'balloon-closed' _Windows_ + +タイムアウトもしくはユーザーの操作で閉じて、トレイバルーンがクロースされたときに出力されます。 + +### イベント: 'drop' _OS X_ + +トレイアイコンでアイテムがドラグアンドドロップされたときに出力されます。 + +### イベント: 'drop-files' _OS X_ + +* `event` +* `files` Array - ドロップされたアイテムのフルパス + +トレイアイコンでファイルがドロップされたときに出力されます。 + +### イベント: 'drag-enter' _OS X_ + +トレイアイコンにドラッグ操作が入ったときに出力されます。 + +### イベント: 'drag-leave' _OS X_ + +トレイアイコンででドラッグ操作が行われたときに出力されます。 + +### イベント: 'drag-end' _OS X_ + +トレイ上でドラッグ操作が終了したか、ほかの場所で終了したときに出力されます。 + +## Methods + +`Tray`モジュールは次のメソッドを持ちます。 + +**Note:** いくつかのメソッドは、特定のオペレーティングシステム向けに提供され、そのようにラベルで表示します。 + +### `Tray.destroy()` + +ただちにトレイアイコンを終了します。 + +### `Tray.setImage(image)` + +* `image` [NativeImage](native-image.md) + +トレイアイコンの`image`を設定します。 + +### `Tray.setPressedImage(image)` _OS X_ + +* `image` [NativeImage](native-image.md) + +OS Xで押されたときにトレイアイコンの`image`を設定します。 + +### `Tray.setToolTip(toolTip)` + +* `toolTip` String + +トレイアイコン用のホバーテキストを設定します。 + +### `Tray.setTitle(title)` _OS X_ + +* `title` String + +ステータスバーで、トレイアイコンのわきに表示するタイトルを設定します。 + +### `Tray.setHighlightMode(highlight)` _OS X_ + +* `highlight` Boolean + +トレイアイコンがクリックされた時、トレイアイコンの背景をハイライト(青色)するかどうかを設定します。既定ではTrueです。 + +### `Tray.displayBalloon(options)` _Windows_ + +* `options` Object + * `icon` [NativeImage](native-image.md) + * `title` String + * `content` String + +トレイバルーンを表示します。 + +### `Tray.popUpContextMenu([menu, position])` _OS X_ _Windows_ + +* `menu` Menu (optional) +* `position` Object (optional) - ポップアップ位置 + * `x` Integer + * `y` Integer + +トレイアイコンのコンテキストメニューをポップアップします。`menu`が渡されたとき、`menu`はトレイコンテキストメニューの代わりに表示されます。 + +`position`はWindowsのみで提供され、既定では(0, 0) です。 + +### `Tray.setContextMenu(menu)` + +* `menu` Menu + +アイコン用のコンテキストメニューを設定します。 + +[event-emitter]: http://nodejs.org/api/events.html#events_class_events_eventemitter diff --git a/docs-translations/jp/faq/electron-faq.md b/docs-translations/jp/faq/electron-faq.md index b7383197477..eef30c013a3 100644 --- a/docs-translations/jp/faq/electron-faq.md +++ b/docs-translations/jp/faq/electron-faq.md @@ -12,6 +12,27 @@ Node.js の新しいバージョンがリリースされたとき、私たちは 通常、Node.js の新しい機能は V8 のアップグレードによってもたらされますが、Electron は Chrome ブラウザーに搭載されている V8 を使用しているので、新しい Node.js に入ったばかりのピカピカに新しい JavaScript 機能は Electron ではたいてい既に導入されています。 +## ウェブページ間のデータを共有する方法は? + +ウェブページ(レンダラープロセス)間のデータを共有するために最も単純な方法は、ブラウザで、すでに提供されているHTML5 APIを使用することです。もっとも良い方法は、[Storage API][storage]、[`localStorage`][local-storage]、[`sessionStorage`][session-storage]、[IndexedDB][indexed-db]です。 + +```javascript +// In the main process. +global.sharedObject = { + someProperty: 'default value' +}; +``` + +```javascript +// In page 1. +require('remote').getGlobal('sharedObject').someProperty = 'new value'; +``` + +```javascript +// In page 2. +console.log(require('remote').getGlobal('sharedObject').someProperty); +``` + ## 何分か経つと、アプリの Window/tray が消えてしまいます これは、Window/trayを格納するのに使用している変数がガベージコレクトされたときに発生します。 @@ -69,5 +90,42 @@ delete window.module; ``` +## `require('electron').xxx` は定義されていません。 + +Electronの組み込みモジュールを使うとに、次のようなエラーに遭遇するかもしれません。 + +``` +> require('electron').webFrame.setZoomFactor(1.0); +Uncaught TypeError: Cannot read property 'setZoomLevel' of undefined +``` + +これは、ローカルまたはグローバルのどちらかで [npm `electron` module][electron-module] をインストールしたことが原因で、Electronの組み込みモジュールを上書きしてしまいます。 + +正しい組み込みモジュールを使用しているかを確認するために、`electron`モジュールのパスを出力します。 + +```javascript +console.log(require.resolve('electron')); +``` + +そして、次の形式かどうかを確認します。 + +``` +"/path/to/Electron.app/Contents/Resources/atom.asar/renderer/api/lib/exports/electron.js" +``` + +If it is something like もし、`node_modules/electron/index.js` のような形式の場合は、npm `electron` モジュールを削除するか、それをリネームします。 + +```bash +npm uninstall electron +npm uninstall -g electron +``` + +しかし、組み込みモジュールを使用しているのに、まだこのエラーが出る場合、不適切なプロセスでモジュールを使用しようとしている可能性が高いです。例えば、`electron.app`はメインプロセスのみで使え、一方で`electron.webFrame`はレンダラープロセスのみに提供されています。 + [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/jp/tutorial/application-distribution.md b/docs-translations/jp/tutorial/application-distribution.md index 96dce25134a..f05b098514b 100644 --- a/docs-translations/jp/tutorial/application-distribution.md +++ b/docs-translations/jp/tutorial/application-distribution.md @@ -51,8 +51,8 @@ Electronにバンドルした後、ユーザーに配布する前に、 Electron ### Windows -`electron.exe`を任意の名前に変更でき、[rcedit](https://github.com/atom/rcedit) または -[ResEdit](http://www.resedit.net)のようなツールでアイコンやその他の情報を編集できます。 +`electron.exe`を任意の名前に変更でき、[rcedit](https://github.com/atom/rcedit) +のようなツールでアイコンやその他の情報を編集できます。 ### OS X diff --git a/docs-translations/jp/tutorial/debugging-main-process.md b/docs-translations/jp/tutorial/debugging-main-process.md index 0839d351598..ffffdeb83ac 100644 --- a/docs-translations/jp/tutorial/debugging-main-process.md +++ b/docs-translations/jp/tutorial/debugging-main-process.md @@ -55,13 +55,13 @@ $ electron --debug=5858 your/app $ electron --debug-brk=5858 your/app ``` -### 5. Electronを使用して、[node-inspector][node-inspector] サーバーを開始する +### 6. Electronを使用して、[node-inspector][node-inspector] サーバーを開始する ```bash $ ELECTRON_RUN_AS_NODE=true path/to/electron.exe node_modules/node-inspector/bin/inspector.js ``` -### 6. デバッグUIを読み込みます +### 7. デバッグUIを読み込みます Chromeブラウザで、 http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 を開きます。エントリーラインを見るために、debug-brkを始めるには、ポーズをクリックします。 diff --git a/docs-translations/jp/tutorial/desktop-environment-integration.md b/docs-translations/jp/tutorial/desktop-environment-integration.md index 24d3e9d2aaa..96bf76d7370 100644 --- a/docs-translations/jp/tutorial/desktop-environment-integration.md +++ b/docs-translations/jp/tutorial/desktop-environment-integration.md @@ -25,16 +25,7 @@ myNotification.onclick = function () { * Windows 10では、通知はすぐに動作します。 * Windows 8.1 と Windows 8では、[Application User Model ID][app-user-model-id]で、アプリへのショートカットはスタートメニューにインストールされます。しかし、スタートメニューにピン止めをする必要がありません。 -* Windows 7以前は、通知はサポートされていません。 しかし、[Tray API](tray-balloon)を使用してバルーンヒントを送信することができます。 - -通知にイメージを使うために、通知オプションの `icon` プロパティにローカルのイメージファイル(`png`が望ましい)を設定します。 正しくない、または`http/https`の URLを設定した場合でも、通知は表示されますが、イメージは表示されません。 - -```javascript -new Notification('Title', { - body: 'Notification with icon', - icon: 'file:///C:/Users/feriese/Desktop/icon.png' -}); -``` +* Windows 7以前は、通知はサポートされていません。 しかし、[Tray API][tray-balloon]を使用してバルーンヒントを送信することができます。 その上で、bodyの最大サイズは250文字であることに留意してください。Windowsチームは、通知は200文字にすることを推奨しています。 @@ -219,6 +210,23 @@ var window = new BrowserWindow({...}); window.setProgressBar(0.5); ``` +## タスクバーでアイコンをオーバーレイする (Windows) + +Windowsで、タスクバーボタンはアプリケーションステータスを表示するために小さなオーバーレイを使うことができます。MSDNから引用します。 + +> アイコン オーバーレイは、状況に応じた状態通知として機能し、通知領域に状態アイコンを個別に表示する必要性をなくして、情報をユーザーに伝えることを目的としています。たとえば、現在、通知領域に表示される Microsoft Office Outlook の新着メールの通知は、タスク バー ボタンのオーバーレイとして表示できるようになります。ここでも、開発サイクルの間に、アプリケーションに最適な方法を決定する必要があります。アイコン オーバーレイは、重要で長期にわたる状態や通知 (ネットワークの状態、メッセンジャーの状態、新着メールなど) を提供することを目的としています。ユーザーに対して、絶えず変化するオーバーレイやアニメーションを表示しないようにしてください。 + +__タスクバーボタンでのオーバーレイ:__ + +![Overlay on taskbar button](https://i-msdn.sec.s-msft.com/dynimg/IC420441.png) + +ウィンドウでオーバーレイアイコンを設定するために、[BrowserWindow.setOverlayIcon][setoverlayicon] APIを使用できます。 + +```javascript +var window = new BrowserWindow({...}); +window.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); +``` + ## Windowのファイル表示 (OS X) OS Xでは、ウィンドウがrepresented fileを設定でき、タイトルバー上にファイルのアイコンを表示でき、タイトル上でCommand-クリックまたはControl-クリックをすると、パスがポップアップ表示されます。 @@ -237,15 +245,16 @@ window.setRepresentedFilename('/etc/passwd'); window.setDocumentEdited(true); ``` -[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath -[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments -[setusertaskstasks]: ../api/app.md#appsetusertaskstasks -[setprogressbar]: ../api/browser-window.md#browserwindowsetprogressbarprogress -[setrepresentedfilename]: ../api/browser-window.md#browserwindowsetrepresentedfilenamefilename -[setdocumentedited]: ../api/browser-window.md#browserwindowsetdocumenteditededited +[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows +[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-os-x-windows +[setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows +[setprogressbar]: ../api/browser-window.md#winsetprogressbarprogress +[setoverlayicon]: ../api/browser-window.md#winsetoverlayiconoverlay-description-windows-7 +[setrepresentedfilename]: ../api/browser-window.md#winsetrepresentedfilenamefilename-os-x +[setdocumentedited]: ../api/browser-window.md#winsetdocumenteditededited-os-x [app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx [unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher -[setthumbarbuttons]: ../api/browser-window.md#browserwindowsetthumbarbuttonsbuttons +[setthumbarbuttons]: ../api/browser-window.md#winsetthumbarbuttonsbuttons-windows-7 [tray-balloon]: ../api/tray.md#traydisplayballoonoptions-windows [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx [notification-spec]: https://developer.gnome.org/notification-spec/ diff --git a/docs-translations/jp/tutorial/mac-app-store-submission-guide.md b/docs-translations/jp/tutorial/mac-app-store-submission-guide.md index b2cf9d26d30..47d7a76b925 100644 --- a/docs-translations/jp/tutorial/mac-app-store-submission-guide.md +++ b/docs-translations/jp/tutorial/mac-app-store-submission-guide.md @@ -2,6 +2,8 @@ v0.34.0から、ElectronはMac App Store (MAS)にパッケージ化したアプリを登録することができます。このガイドでは、MASビルド用の制限とアプリを登録する方法についての情報を提供します。 +__Note:__ v0.36.0から、アプリがサンドボックス化された後GPUプロセスを妨害するバグがあるので、このバグが修正されるまでは、v0.35.xを使用することを推奨します。[issue #3871][issue-3871]で、このことに関する追加情報を確認できます。 + __Note:__ Mac App Storeにアプリを登録するには、費用が発生する[Apple Developer Program][developer-program]に登録する必要があります。 ## アプリを登録する方法 @@ -63,13 +65,18 @@ INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Libraries/libnode.dylib" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Electron Framework" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A" codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" -codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +if [ -d "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" ]; then + # Signing a non-MAS build. + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Mantle.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/ReactiveCocoa.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" +fi +codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" + productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" ``` @@ -83,8 +90,8 @@ OS Xで、サンドボックスにアプリを新しく追加した場合、基 アプリのサンドボックスですべての要件を満たすために、MASビルドで次のモジュールを無効にしてください。 -* `crash-reporter` -* `auto-updater` +* `crashReporter` +* `autoUpdater` 次の挙動を変更してください。 @@ -92,7 +99,39 @@ OS Xで、サンドボックスにアプリを新しく追加した場合、基 * 一部のアクセシビリティ機能が動作しないことがあります。 * アプリはDNSの変更を認識しません。 -アプリのサンドボックスでの使用が原因で、アプリがアクセスできるリソースは厳密に制限されています。詳細は、 [App Sandboxing][app-sandboxing] を参照してください。 +アプリのサンドボックスでの使用では、アプリがアクセスできるリソースは厳密に制限されています。詳細は、 [App Sandboxing][app-sandboxing] を参照してください。 + +## Electronが使用する暗号化アルゴリズム + +あなたが住んでいる国や地域に依存して、Mac App Store がアプリで使用する暗号化アルゴリズムを文章化することを要求することがあり、暗号登録番号(U.S. Encryption Registration (ERN))の同意のコピーの提出を求められます。 + +Electron は次の暗号アルゴリズムを使用しています: + +* AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt) +* HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf) +* ECDSA - ANS X9.62–2005 +* ECDH - ANS X9.63–2001 +* HKDF - [NIST SP 800-56C](http://csrc.nist.gov/publications/nistpubs/800-56C/SP-800-56C.pdf) +* PBKDF2 - [RFC 2898](https://tools.ietf.org/html/rfc2898) +* RSA - [RFC 3447](http://www.ietf.org/rfc/rfc3447) +* SHA - [FIPS 180-4](http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf) +* Blowfish - https://www.schneier.com/cryptography/blowfish/ +* CAST - [RFC 2144](https://tools.ietf.org/html/rfc2144), [RFC 2612](https://tools.ietf.org/html/rfc2612) +* DES - [FIPS 46-3](http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf) +* DH - [RFC 2631](https://tools.ietf.org/html/rfc2631) +* DSA - [ANSI X9.30](http://webstore.ansi.org/RecordDetail.aspx?sku=ANSI+X9.30-1%3A1997) +* EC - [SEC 1](http://www.secg.org/sec1-v2.pdf) +* IDEA - "On the Design and Security of Block Ciphers" book by X. Lai +* MD2 - [RFC 1319](http://tools.ietf.org/html/rfc1319) +* MD4 - [RFC 6150](https://tools.ietf.org/html/rfc6150) +* MD5 - [RFC 1321](https://tools.ietf.org/html/rfc1321) +* MDC2 - [ISO/IEC 10118-2](https://www.openssl.org/docs/manmaster/crypto/mdc2.html) +* RC2 - [RFC 2268](https://tools.ietf.org/html/rfc2268) +* RC4 - [RFC 4345](https://tools.ietf.org/html/rfc4345) +* RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf +* RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) + +ERNの同意を取得するには、 [How to legally submit an app to Apple’s App Store when it uses encryption (or how to obtain an ERN)][ern-tutorial]を参照してくだsだい。 [developer-program]: https://developer.apple.com/support/compare-memberships/ [submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html @@ -101,3 +140,5 @@ OS Xで、サンドボックスにアプリを新しく追加した場合、基 [create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html [submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html [app-sandboxing]: https://developer.apple.com/app-sandboxing/ +[issue-3871]: https://github.com/atom/electron/issues/3871 +[ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ diff --git a/docs-translations/jp/tutorial/quick-start.md b/docs-translations/jp/tutorial/quick-start.md index bfb6e2a51f7..4f2e5c9a52e 100644 --- a/docs-translations/jp/tutorial/quick-start.md +++ b/docs-translations/jp/tutorial/quick-start.md @@ -24,6 +24,8 @@ Electron はウェブページを表示させるために Chromium を使用し Electron では、メインプロセスとレンダラープロセスとのコミュニケーションをするために [ipc](../api/ipc-renderer.md) モジュールを提供しています。またそれと、RPC 形式の通信を行う [remote](../api/remote.md) モジュールもあります。 +Electron では、メインプロセスとレンダラープロセスとのコミュニケーションをするには幾つかのほうほうがあります。メッセージを送信する[`ipcRenderer`](../api/ipc-renderer.md)モジュールと[`ipcMain`](../api/ipc-main.md)モジュールのように、RPC 形式の通信を行う[remote](../api/remote.md)モジュールです。[ウェブページ間のデータを共有する方法][share-data]にFAQエントリーがあります。 + ## Electronアプリを作成する 一般的に Electron アプリの構成は次のようになります: @@ -169,3 +171,5 @@ $ cd electron-quick-start # Install dependencies and run the app $ npm install && npm start ``` + +[share-data]: ../faq/electron-faq.md#how-to-share-data-between-web-pages diff --git a/docs-translations/jp/tutorial/supported-platforms.md b/docs-translations/jp/tutorial/supported-platforms.md index 8482931ed21..7d9f24b7ab6 100644 --- a/docs-translations/jp/tutorial/supported-platforms.md +++ b/docs-translations/jp/tutorial/supported-platforms.md @@ -4,7 +4,7 @@ Electronでは次のプラットフォームをサポートします。 ### OS X -OS X用に提供しているバイナリは64bitのみで、サポートするOS Xのバージョンは、OS X 10.8 以降です。 +OS X用に提供しているバイナリは64bitのみで、サポートするOS Xのバージョンは、OS X 10.9 以降です。 ### Windows diff --git a/docs-translations/jp/tutorial/using-selenium-and-webdriver.md b/docs-translations/jp/tutorial/using-selenium-and-webdriver.md index 3202028ebf5..a1fd0f21d7f 100644 --- a/docs-translations/jp/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/jp/tutorial/using-selenium-and-webdriver.md @@ -41,7 +41,7 @@ var driver = new webdriver.Builder() .withCapabilities({ chromeOptions: { // Here is the path to your Electron binary. - binary: '/Path-to-Your-App.app/Contents/MacOS/Atom', + binary: '/Path-to-Your-App.app/Contents/MacOS/Electron', } }) .forBrowser('electron') diff --git a/docs-translations/jp/tutorial/using-widevine-cdm-plugin.md b/docs-translations/jp/tutorial/using-widevine-cdm-plugin.md index 5cee7f26dc6..55f9d039f61 100644 --- a/docs-translations/jp/tutorial/using-widevine-cdm-plugin.md +++ b/docs-translations/jp/tutorial/using-widevine-cdm-plugin.md @@ -6,6 +6,8 @@ Electronで、Chromeブラウザーに同梱される Widevine CDMプラグイ Electronは、ライセンス的な理由でWidevine CDMプラグインは同梱されません。Widevine CDMプラグインを取得するために、最初に、使用するElectronビルドのChromバージョンとアーキテクチャを合わせた公式のChromeブラウザーをインストールする必要があります。 +__Note:__ Chromeブラウザの主要バージョンは、Electronが使用するChromeバージョンと同じでなければなりません。そうでなければ、プラグインは、`navigator.plugins`経由でロードされて表示されるにも関わらず動作しません。 + ### Windows & OS X Chromeブラウザーで、`chrome://components/`を開き、 `WidevineCdm` を探し、それが最新であることを確認し、`APP_DATA/Google/Chrome/WidevineCDM/VERSION/_platform_specific/PLATFORM_ARCH/`ディレクトリからすべてのプラグインバイナリを探します。 diff --git a/docs-translations/ko-KR/README.md b/docs-translations/ko-KR/README.md index 6f4a1c520f5..8c5912f604b 100644 --- a/docs-translations/ko-KR/README.md +++ b/docs-translations/ko-KR/README.md @@ -30,6 +30,7 @@ Electron에 대해 자주 묻는 질문이 있습니다. 이슈를 생성하기 * [개발자 도구 확장 기능](tutorial/devtools-extension.md) * [Pepper 플래시 플러그인 사용하기](tutorial/using-pepper-flash-plugin.md) * [Widevine CDM 플러그인 사용하기](tutorial/using-widevine-cdm-plugin.md) +* [Headless CI 시스템에서 테스팅하기 (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) ## 튜토리얼 diff --git a/docs-translations/ko-KR/api/accelerator.md b/docs-translations/ko-KR/api/accelerator.md index 99b549bbb70..90adf414dc3 100644 --- a/docs-translations/ko-KR/api/accelerator.md +++ b/docs-translations/ko-KR/api/accelerator.md @@ -22,6 +22,8 @@ Linux와 Windows에서는 `Command`키가 없으므로 작동하지 않습니다 * `Control` (단축어 `Ctrl`) * `CommandOrControl` (단축어 `CmdOrCtrl`) * `Alt` +* `Option` +* `AltGr` * `Shift` * `Super` @@ -43,5 +45,6 @@ Linux와 Windows에서는 `Command`키가 없으므로 작동하지 않습니다 * `Escape` (단축어 `Esc`) * `VolumeUp`, `VolumeDown` 그리고 `VolumeMute` * `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` 그리고 `MediaPlayPause` +* `PrintScreen` __키코드는 `단축어`로도 사용할 수 있습니다__ diff --git a/docs-translations/ko-KR/api/app.md b/docs-translations/ko-KR/api/app.md index 83921b3e39e..ff4269ea829 100644 --- a/docs-translations/ko-KR/api/app.md +++ b/docs-translations/ko-KR/api/app.md @@ -2,7 +2,7 @@ `app` 모듈은 어플리케이션의 생명주기 제어를 책임집니다. -밑의 예제는 마지막 윈도우 창이 종료되었을 때, 어플리케이션을 종료시키는 예제입니다: +밑의 예제는 마지막 윈도우가 종료되었을 때, 어플리케이션을 종료시키는 예제입니다: ```javascript const app = require('electron').app; @@ -32,15 +32,15 @@ Electron이 초기화를 끝냈을 때 발생하는 이벤트입니다. ### Event: 'window-all-closed' -모든 윈도우 창이 종료되었을 때 발생하는 이벤트입니다. +모든 윈도우가 종료되었을 때 발생하는 이벤트입니다. 이 이벤트는 어플리케이션이 완전히 종료되지 않았을 때만 발생합니다. 만약 사용자가 `Cmd + Q`를 입력했거나 개발자가 `app.quit()`를 호출했다면, -Electron은 먼저 모든 윈도우 창의 종료를 시도하고 `will-quit` 이벤트를 발생시킵니다. +Electron은 먼저 모든 윈도우의 종료를 시도하고 `will-quit` 이벤트를 발생시킵니다. 그리고 `will-quit` 이벤트가 발생했을 땐 `window-all-closed` 이벤트가 발생하지 않습니다. -**역주:** 이 이벤트는 말 그대로 현재 어플리케이션에서 윈도우 창만 완전히 종료됬을 때 +**역주:** 이 이벤트는 말 그대로 현재 어플리케이션에서 윈도우만 완전히 종료됬을 때 발생하는 이벤트 입니다. 따라서 어플리케이션을 완전히 종료하려면 이 이벤트에서 `app.quit()`를 호출해 주어야 합니다. @@ -50,7 +50,7 @@ Returns: * `event` Event -어플리케이션 윈도우 창들이 닫히기 시작할 때 발생하는 이벤트입니다. +어플리케이션 윈도우들이 닫히기 시작할 때 발생하는 이벤트입니다. `event.preventDefault()` 호출은 이벤트의 기본 동작을 방지하기 때문에 이를 통해 어플리케이션의 종료를 방지할 수 있습니다. @@ -60,7 +60,7 @@ Returns: * `event` Event -모든 윈도우 창들이 종료되고 어플리케이션이 종료되기 시작할 때 발생하는 이벤트 입니다. +모든 윈도우들이 종료되고 어플리케이션이 종료되기 시작할 때 발생하는 이벤트 입니다. `event.preventDefault()` 호출을 통해 어플리케이션의 종료를 방지할 수 있습니다. `will-quit` 와 `window-all-closed` 이벤트의 차이점을 확인하려면 `window-all-close` @@ -92,7 +92,7 @@ Returns: 이 이벤트를 처리할 땐 반드시 `event.preventDefault()`를 호출해야 합니다. -Windows에선 `process.argv`를 통해 파일 경로를 얻을 수 있습니다. +Windows에선 `process.argv` (메인 프로세스에서)를 통해 파일 경로를 얻을 수 있습니다. ### Event: 'open-url' _OS X_ @@ -164,7 +164,7 @@ Returns: 기본 동작을 방지하고 인증을 승인할 수 있습니다. ```javascript -session.on('certificate-error', function(event, webContents, url, error, certificate, callback) { +app.on('certificate-error', function(event, webContents, url, error, certificate, callback) { if (url == "https://github.com") { // Verification logic. event.preventDefault(); @@ -236,6 +236,10 @@ app.on('login', function(event, webContents, request, authInfo, callback) { GPU가 작동하던 중 크래시가 일어났을 때 발생하는 이벤트입니다. +### Event: 'platform-theme-changed' _OS X_ + +시스템의 다크 모드 테마가 토글되면 발생하는 이벤트입니다. + ## Methods `app` 객체는 다음과 같은 메서드를 가지고 있습니다: @@ -244,12 +248,12 @@ GPU가 작동하던 중 크래시가 일어났을 때 발생하는 이벤트입 ### `app.quit()` -모든 윈도우 창 종료를 시도합니다. `before-quit` 이벤트가 먼저 발생합니다. -모든 윈도우 창이 성공적으로 종료되면 `will-quit` 이벤트가 발생하고 기본 동작에 따라 +모든 윈도우 종료를 시도합니다. `before-quit` 이벤트가 먼저 발생합니다. +모든 윈도우가 성공적으로 종료되면 `will-quit` 이벤트가 발생하고 기본 동작에 따라 어플리케이션이 종료됩니다. 이 함수는 모든 `beforeunload`와 `unload` 이벤트 핸들러가 제대로 실행됨을 보장합니다. -`beforeunload` 이벤트 핸들러에서 `false`를 반환했을 때 윈도우 창 종료가 취소 될 수 +`beforeunload` 이벤트 핸들러에서 `false`를 반환했을 때 윈도우 종료가 취소 될 수 있습니다. ### `app.exit(exitCode)` @@ -258,9 +262,22 @@ GPU가 작동하던 중 크래시가 일어났을 때 발생하는 이벤트입 `exitCode`와 함께 어플리케이션을 즉시 종료합니다. -모든 윈도우 창은 사용자의 동의 여부에 상관없이 즉시 종료되며 `before-quit` 이벤트와 +모든 윈도우는 사용자의 동의 여부에 상관없이 즉시 종료되며 `before-quit` 이벤트와 `will-quit` 이벤트가 발생하지 않습니다. +### `app.focus()` + +Linux에선, 첫 번째로 보여지는 윈도우가 포커스됩니다. OS X에선, 어플리케이션을 활성화 +앱 상태로 만듭니다. Windows에선, 어플리케이션의 첫 윈도우에 포커스 됩니다. + +### `app.hide()` _OS X_ + +최소화를 하지 않고 어플리케이션의 모든 윈도우들을 숨깁니다. + +### `app.show()` _OS X_ + +숨긴 어플리케이션 윈도우들을 다시 보이게 만듭니다. 자동으로 포커스되지 않습니다. + ### `app.getAppPath()` 현재 어플리케이션의 디렉터리를 반환합니다. @@ -325,6 +342,12 @@ npm 모듈 규칙에 따라 대부분의 경우 `package.json`의 `name` 필드 반드시 이 필드도 같이 지정해야 합니다. 이 필드는 맨 앞글자가 대문자인 어플리케이션 전체 이름을 지정해야 합니다. +### `app.setName(name)` + +* `name` String + +현재 어플리케이션의 이름을 덮어씌웁니다. + ### `app.getLocale()` 현재 어플리케이션의 [로케일](https://ko.wikipedia.org/wiki/%EB%A1%9C%EC%BC%80%EC%9D%BC)을 @@ -343,6 +366,32 @@ npm 모듈 규칙에 따라 대부분의 경우 `package.json`의 `name` 필드 최근 문서 목록을 모두 비웁니다. +### `app.setAsDefaultProtocolClient(protocol)` _OS X_ _Windows_ + +* `protocol` String - 프로토콜의 이름, `://` 제외. 만약 앱을 통해 `electron://`과 + 같은 링크를 처리하고 싶다면, 이 메서드에 `electron` 인수를 담아 호출하면 됩니다. + +이 메서드는 지정한 프로토콜(URI scheme)에 대해 현재 실행파일을 기본 핸들러로 +등록합니다. 이를 통해 운영체제와 더 가깝게 통합할 수 있습니다. 한 번 등록되면, +`your-protocol://`과 같은 모든 링크에 대해 호출시 현재 실행 파일이 실행됩니다. +모든 링크, 프로토콜을 포함하여 어플리케이션의 인수로 전달됩니다. + +**참고:** OS X에선, 어플리케이션의 `info.plist`에 등록해둔 프로토콜만 사용할 수 +있습니다. 이는 런타임에서 변경될 수 없습니다. 이 파일은 간단히 텍스트 에디터를 +사용하거나, 어플리케이션을 빌드할 때 스크립트가 생성되도록 할 수 있습니다. 자세한 +내용은 [Apple의 참조 문서를][CFBundleURLTypes] 확인하세요. + +이 API는 내부적으로 Windows 레지스트리와 LSSetDefaultHandlerForURLScheme를 사용합니다. + +### `app.removeAsDefaultProtocolClient(protocol)` _Windows_ + +* `protocol` String - 프로토콜의 이름, `://` 제외. + +이 메서드는 현재 실행파일이 지정한 프로토콜(URI scheme)에 대해 기본 핸들러인지를 +확인합니다. 만약 그렇다면, 이 메서드는 앱을 기본 핸들러에서 제거합니다. + +**참고:** OS X에서는 앱을 제거하면 자동으로 기본 프로토콜 핸들러에서 제거됩니다. + ### `app.setUserTasks(tasks)` _Windows_ * `tasks` Array - `Task` 객체의 배열 @@ -386,7 +435,7 @@ Windows에서 사용할 수 있는 JumpList의 [Tasks][tasks] 카테고리에 `t `callback`은 주 인스턴스가 생성된 이후 또 다른 인스턴스가 생성됐을 때 `callback(argv, workingDirectory)` 형식으로 호출됩니다. `argv`는 두 번째 인스턴스의 명령줄 인수이며 `workingDirectory`는 현재 작업중인 디렉터리입니다. 보통 대부분의 -어플리케이션은 이러한 콜백이 호출될 때 주 윈도우 창을 포커스하고 최소화되어있으면 창 +어플리케이션은 이러한 콜백이 호출될 때 주 윈도우를 포커스하고 최소화되어있으면 창 복구를 실행합니다. `callback`은 `app`의 `ready` 이벤트가 발생한 후 실행됨을 보장합니다. @@ -403,7 +452,7 @@ OS X에선 사용자가 Finder에서 어플리케이션의 두 번째 인스턴 중복 실행을 방지하는 것이 좋습니다. 다음 예제는 두 번째 인스턴스가 생성되었을 때 중복된 인스턴스를 종료하고 주 어플리케이션 -인스턴스의 윈도우 창을 활성화 시키는 예제입니다: +인스턴스의 윈도우를 활성화 시키는 예제입니다: ```javascript var myWindow = null; @@ -422,7 +471,7 @@ if (shouldQuit) { return; } -// 윈도우 창을 생성하고 각종 리소스를 로드하고 작업합니다. +// 윈도우를 생성하고 각종 리소스를 로드하고 작업합니다. app.on('ready', function() { }); ``` @@ -463,6 +512,11 @@ if (browserOptions.transparent) { } ``` +### `app.isDarkMode()` _OS X_ + +이 메서드는 시스템이 다크 모드 상태인 경우 `true`를 반환하고 아닐 경우 `false`를 +반환합니다. + ### `app.commandLine.appendSwitch(switch[, value])` Chrominum의 명령줄에 스위치를 추가합니다. `value`는 추가적인 값을 뜻하며 옵션입니다. @@ -515,10 +569,17 @@ dock 아이콘을 표시합니다. ### `app.dock.setMenu(menu)` _OS X_ -* `menu` Menu +* `menu` [Menu](menu.md) 어플리케이션의 [dock menu][dock-menu]를 설정합니다. +### `app.dock.setIcon(image)` _OS X_ + +* `image` [NativeImage](native-image.md) + +dock 아이콘의 `image`를 설정합니다. + [dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 [tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115 diff --git a/docs-translations/ko-KR/api/auto-updater.md b/docs-translations/ko-KR/api/auto-updater.md index 04593ee5760..e5121d0f9b1 100644 --- a/docs-translations/ko-KR/api/auto-updater.md +++ b/docs-translations/ko-KR/api/auto-updater.md @@ -2,6 +2,9 @@ 이 모듈은 `Squirrel` 자동 업데이트 프레임워크의 인터페이스를 제공합니다. +[electron-release-server](https://github.com/ArekSredzki/electron-release-server)를 +포크하면 어플리케이션을 배포하기 위한 멀티 플랫폼 릴리즈 서버를 손쉽게 구축할 수 있습니다. + ## 플랫폼별 참고 사항 `autoUpdater`는 기본적으로 모든 플랫폼에 대해 같은 API를 제공하지만, 여전히 플랫폼별로 diff --git a/docs-translations/ko-KR/api/browser-window.md b/docs-translations/ko-KR/api/browser-window.md index e5d5fbafe43..06e31e9fba5 100644 --- a/docs-translations/ko-KR/api/browser-window.md +++ b/docs-translations/ko-KR/api/browser-window.md @@ -1,8 +1,8 @@ # BrowserWindow -`BrowserWindow` 클래스는 브라우저 창(윈도우 창)을 만드는 역할을 담당합니다. +`BrowserWindow` 클래스는 브라우저 창(윈도우)을 만드는 역할을 담당합니다. -다음 예제는 윈도우 창을 생성합니다: +다음 예제는 윈도우를 생성합니다: ```javascript // 메인 프로세스에서 @@ -28,37 +28,45 @@ win.show(); `BrowserWindow`는 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)를 상속받은 클래스 입니다. -`BrowserWindow`는 `options`를 통해 네이티브 속성을 포함한 새로운 윈도우 창을 -생성합니다. +`BrowserWindow`는 `options`를 통해 네이티브 속성을 포함한 새로운 윈도우를 생성합니다. ### `new BrowserWindow([options])` `options` 객체 (optional), 사용할 수 있는 속성들: -* `width` Integer - 윈도우 창의 가로 너비. 기본값은 `800`입니다. -* `height` Integer - 윈도우 창의 세로 높이. 기본값은 `600`입니다. +* `width` Integer - 윈도우의 가로 너비. 기본값은 `800`입니다. +* `height` Integer - 윈도우의 세로 높이. 기본값은 `600`입니다. * `x` Integer - 화면을 기준으로 창 좌측을 오프셋 한 위치. 기본값은 `화면중앙`입니다. * `y` Integer - 화면을 기준으로 창 상단을 오프셋 한 위치. 기본값은 `화면중앙`입니다. * `useContentSize` Boolean - `width`와 `height`를 웹 페이지의 크기로 사용합니다. 이 속성을 사용하면 웹 페이지의 크기에 윈도우 프레임 크기가 추가되므로 실제 창은 조금 더 커질 수 있습니다. 기본값은 `false`입니다. -* `center` Boolean - 윈도우 창을 화면 정 중앙에 위치시킵니다. -* `minWidth` Integer - 윈도우 창의 최소 가로 너비. 기본값은 `0`입니다. -* `minHeight` Integer - 윈도우 창의 최소 세로 높이. 기본값은 `0`입니다. -* `maxWidth` Integer - 윈도우 창의 최대 가로 너비. 기본값은 `제한없음`입니다. -* `maxHeight` Integer - 윈도우 창의 최대 세로 높이. 기본값은 `제한없음`입니다. -* `resizable` Boolean - 윈도우 창의 크기를 재조정 할 수 있는지 여부. 기본값은 `true` +* `center` Boolean - 윈도우를 화면 정 중앙에 위치시킵니다. +* `minWidth` Integer - 윈도우의 최소 가로 너비. 기본값은 `0`입니다. +* `minHeight` Integer - 윈도우의 최소 세로 높이. 기본값은 `0`입니다. +* `maxWidth` Integer - 윈도우의 최대 가로 너비. 기본값은 `제한없음`입니다. +* `maxHeight` Integer - 윈도우의 최대 세로 높이. 기본값은 `제한없음`입니다. +* `resizable` Boolean - 윈도우의 크기를 재조정 할 수 있는지 여부. 기본값은 `true` 입니다. -* `movable` Boolean - 윈도우를 이동시킬 수 있는지 여부. 이 기능은 현재 OSX에만 - 구현되어 있습니다. 기본값은 `true` -* `alwaysOnTop` Boolean - 윈도우 창이 언제나 다른 창들 위에 유지되는지 여부. +* `movable` Boolean - 윈도우를 이동시킬 수 있는지 여부. Linux에선 구현되어있지 + 않습니다. 기본값은 `true` 입니다. +* `minimizable` Boolean - 윈도우를 최소화시킬 수 있는지 여부. Linux에선 구현되어있지 + 않습니다. 기본값은 `true` 입니다. +* `maximizable` Boolean - 윈도우를 최대화시킬 수 있는지 여부. Linux에선 구현되어있지 + 않습니다. 기본값은 `true` 입니다. +* `closable` Boolean - 윈도우를 닫을 수 있는지 여부. Linux에선 구현되어있지 않습니다. + 기본값은 `true` 입니다. +* `alwaysOnTop` Boolean - 윈도우이 언제나 다른 창들 위에 유지되는지 여부. 기본값은 `false`입니다. -* `fullscreen` Boolean - 윈도우 창의 전체화면 활성화 여부. 기본값은 `false` 입니다. - `false`로 지정했을 경우 OS X에선 전체화면 버튼이 숨겨지거나 비활성화됩니다. +* `fullscreen` Boolean - 윈도우의 전체화면 활성화 여부. 이 속성을 명시적으로 + `false`로 지정했을 경우, OS X에선 전체화면 버튼이 숨겨지거나 비활성됩니다. 기본값은 + `false` 입니다. +* `fullscreenable` Boolean - OS X의 최대화/줌 버튼이 전체화면 모드 또는 윈도우 + 최대화를 토글할 수 있게 할지 여부입니다. 기본값은 `true` 입니다. * `skipTaskbar` Boolean - 작업표시줄 어플리케이션 아이콘 표시 스킵 여부. 기본값은 `false`입니다. * `kiosk` Boolean - Kiosk(키오스크) 모드. 기본값은 `false`입니다. -* `title` String - 기본 윈도우 창 제목. 기본값은 `"Electron"`입니다. +* `title` String - 기본 윈도우 제목. 기본값은 `"Electron"`입니다. * `icon` [NativeImage](native-image.md) - 윈도우 아이콘, 생략하면 실행 파일의 아이콘이 대신 사용됩니다. * `show` Boolean - 윈도우가 생성되면 보여줄지 여부. 기본값은 `true`입니다. @@ -70,16 +78,18 @@ win.show(); `false`입니다. * `autoHideMenuBar` Boolean - `Alt`를 누르지 않는 한 어플리케이션 메뉴바를 숨길지 여부. 기본값은 `false`입니다. -* `enableLargerThanScreen` Boolean - 윈도우 창 크기가 화면 크기보다 크게 재조정 될 +* `enableLargerThanScreen` Boolean - 윈도우 크기가 화면 크기보다 크게 재조정 될 수 있는지 여부. 기본값은 `false`입니다. -* `backgroundColor` String - 16진수로 표현된 윈도우의 배경 색. `#66CD00` 또는 - `#FFF`가 사용될 수 있습니다. 이 속성은 Linux와 Windows에만 구현되어 있습니다. - 기본값은 `#000`(검정)입니다. +* `backgroundColor` String - `#66CD00` 와 `#FFF`, `#80FFFFFF` (알파 지원됨) 같이 + 16진수로 표현된 윈도우의 배경 색. 기본값은 Linux와 Windows에선 `#000` (검정)이며, + Mac에선 `#FFF` (또는, transparent(투명)일 경우 clear(색 없음)로 설정) +* `hasShadow` Boolean - 윈도우가 그림자를 가질지 여부를 지정합니다. 이 속성은 + OS X에서만 구현되어 있습니다. 기본값은 `true`입니다. * `darkTheme` Boolean - 설정에 상관 없이 무조건 어두운 윈도우 테마를 사용합니다. 몇몇 GTK+3 데스크톱 환경에서만 작동합니다. 기본값은 `false`입니다. -* `transparent` Boolean - 윈도우 창을 [투명화](frameless-window.md)합니다. 기본값은 +* `transparent` Boolean - 윈도우를 [투명화](frameless-window.md)합니다. 기본값은 `false`입니다. -* `type` String - 특정 플랫폼에만 적용되는 윈도우 창의 종류를 지정합니다. 기본값은 +* `type` String - 특정 플랫폼에만 적용되는 윈도우의 종류를 지정합니다. 기본값은 일반 윈도우 입니다. 사용할 수 있는 창의 종류는 아래를 참고하세요. * `standardWindow` Boolean - OS X의 표준 윈도우를 텍스쳐 윈도우 대신 사용합니다. 기본 값은 `true`입니다. @@ -155,6 +165,15 @@ win.show(); * `blinkFeatures` String - `CSSVariables,KeyboardEventKey`같은 `,`로 구분된 기능 문자열들의 리스트입니다. 지원하는 전체 기능 문자열들은 [setFeatureEnabledFromString][blink-feature-string] 함수에서 찾을 수 있습니다. +* `defaultFontFamily` Object - font-family의 기본 폰트를 지정합니다. + * `standard` String - 기본값 `Times New Roman`. + * `serif` String - 기본값 `Times New Roman`. + * `sansSerif` String - 기본값 `Arial`. + * `monospace` String - 기본값 `Courier New`. +* `defaultFontSize` Integer - 기본값 `16`. +* `defaultMonospaceFontSize` Integer - 기본값 `13`. +* `minimumFontSize` Integer - 기본값 `0`. +* `defaultEncoding` String - 기본값 `ISO-8859-1`. ## Events @@ -203,7 +222,7 @@ window.onbeforeunload = function(e) { ### Event: 'closed' 윈도우 종료가 완료된 경우 발생하는 이벤트입니다. 이 이벤트가 발생했을 경우 반드시 -윈도우 창의 레퍼런스가 더 이상 사용되지 않도록 제거해야 합니다. +윈도우의 레퍼런스가 더 이상 사용되지 않도록 제거해야 합니다. ### Event: 'unresponsive' @@ -221,6 +240,14 @@ window.onbeforeunload = function(e) { 윈도우가 포커스를 가졌을 때 발생하는 이벤트입니다. +### Event: 'show' + +윈도우가 보여진 상태일 때 발생하는 이벤트입니다. + +### Event: 'hide' + +윈도우가 숨겨진 상태일 때 발생하는 이벤트입니다. + ### Event: 'maximize' 윈도우가 최대화됐을 때 발생하는 이벤트입니다. @@ -283,6 +310,24 @@ someWindow.on('app-command', function(e, cmd) { }); ``` +### Event: 'scroll-touch-begin' _OS X_ + +스크롤 휠 이벤트가 동작하기 시작했을 때 발생하는 이벤트입니다. + +### Event: 'scroll-touch-end' _OS X_ + +스크롤 휠 이벤트가 동작을 멈췄을 때 발생하는 이벤트입니다. + +### Event: 'swipe' _OS X_ + +Returns: + +* `event` Event +* `direction` String + +3-손가락 스와이프가 작동할 때 발생하는 이벤트입니다. 방향은 `up`, `right`, `down`, +`left`가 될 수 있습니다. + ## Methods `BrowserWindow` 객체는 다음과 같은 메서드를 가지고 있습니다: @@ -366,6 +411,10 @@ var win = new BrowserWindow({ width: 800, height: 600 }); 윈도우에 포커스를 맞춥니다. +### `win.blur()` + +윈도우의 포커스를 없앱니다. + ### `win.isFocused()` 윈도우가 포커스되었는지 여부를 반환합니다. @@ -424,7 +473,6 @@ var win = new BrowserWindow({ width: 800, height: 600 }); * `aspectRatio` 유지하려 하는 컨텐츠 뷰 일부의 종횡비 * `extraSize` Object (optional) - 종횡비를 유지하는 동안 포함되지 않을 엑스트라 크기. - 사용 가능한 속성: * `width` Integer * `height` Integer @@ -442,7 +490,7 @@ var win = new BrowserWindow({ width: 800, height: 600 }); ### `win.setBounds(options[, animate])` -* `options` Object, properties: +* `options` Object * `x` Integer * `y` Integer @@ -507,11 +555,71 @@ var win = new BrowserWindow({ width: 800, height: 600 }); * `resizable` Boolean -윈도우의 크기가 사용자에 의해 재조정될 수 있는지를 지정합니다. +사용자에 의해 윈도우의 크기가 재조정될 수 있는지를 지정합니다. ### `win.isResizable()` -윈도우의 크기가 사용자에 의해 재조정될 수 있는지 여부를 반환합니다. +사용자에 의해 윈도우의 크기가 재조정될 수 있는지 여부를 반환합니다. + +### `win.setMovable(movable)` _OS X_ _Windows_ + +* `movable` Boolean + +사용자에 의해 윈도우를 이동시킬 수 있는지 여부를 지정합니다. Linux에선 아무 일도 +일어나지 않습니다. + +### `win.isMovable()` _OS X_ _Windows_ + +사용자에 의해 윈도우를 이동시킬 수 있는지 여부를 반환합니다. Linux에선 항상 `true`를 +반환합니다. + +### `win.setMinimizable(minimizable)` _OS X_ _Windows_ + +* `minimizable` Boolean + +사용자에 의해 윈도우를 최소화시킬 수 있는지 여부를 지정합니다. Linux에선 아무 일도 +일어나지 않습니다. + +### `win.isMinimizable()` _OS X_ _Windows_ + +사용자에 의해 윈도우를 최소화시킬 수 있는지 여부를 반환합니다. Linux에선 항상 `true`를 +반환합니다. + +### `win.setMaximizable(maximizable)` _OS X_ _Windows_ + +* `maximizable` Boolean + +사용자에 의해 윈도우를 최대화시킬 수 있는지 여부를 지정합니다. Linux에선 아무 일도 +일어나지 않습니다. + +### `win.isMaximizable()` _OS X_ _Windows_ + +사용자에 의해 윈도우를 최대화시킬 수 있는지 여부를 반환합니다. Linux에선 항상 `true`를 +반환합니다. + +### `win.setFullScreenable(fullscreenable)` + +* `fullscreenable` Boolean + +최대화/줌 버튼이 전체화면 모드 또는 윈도우 최대화를 토글할 수 있게 할지 여부를 +지정합니다. + +### `win.isFullScreenable()` + +최대화/줌 버튼이 전체화면 모드 또는 윈도우 최대화를 토글할 수 있게 할지 여부를 +반환합니다. + +### `win.setClosable(closable)` _OS X_ _Windows_ + +* `closable` Boolean + +사용자에 의해 윈도우가 수동적으로 닫힐 수 있는지 여부를 지정합니다. Linux에선 아무 일도 +일어나지 않습니다. + +### `win.isClosable()` _OS X_ _Windows_ + +사용자에 의해 윈도우가 수동적으로 닫힐 수 있는지 여부를 반환합니다. Linux에선 항상 +`true`를 반환합니다. ### `win.setAlwaysOnTop(flag)` @@ -632,8 +740,7 @@ Windows 메시지 훅을 등록합니다. `callback`은 WndProc에서 메시지 ### `win.capturePage([rect, ]callback)` -* `rect` Object (optional) - 캡쳐할 페이지의 영역. - 사용할 수 있는 속성은 다음과 같습니다: +* `rect` Object (optional) - 캡쳐할 페이지의 영역 * `x` Integer * `y` Integer * `width` Integer @@ -686,39 +793,51 @@ Linux 플랫폼에선 Unity 데스크톱 환경만 지원합니다. 그리고 아이콘입니다. `null`로 지정하면 빈 오버레이가 사용됩니다 * `description` String - 접근성 설정에 의한 스크린 리더에 제공될 설명입니다 -현재 작업표시줄 아이콘에 16px 크기의 오버레이를 지정합니다. 보통 이 기능은 +현재 작업표시줄 아이콘에 16 x 16 픽셀 크기의 오버레이를 지정합니다. 보통 이 기능은 어플리케이션의 여러 상태를 사용자에게 소극적으로 알리기 위한 방법으로 사용됩니다. +### `win.setHasShadow(hasShadow)` _OS X_ + +* `hasShadow` (Boolean) + +윈도우가 그림자를 가질지 여부를 지정합니다. Windows와 Linux에선 아무 일도 일어나지 +않습니다. + +### `win.hasShadow()` _OS X_ + +윈도우가 그림자를 가지고 있는지 여부를 반환합니다. Windows와 Linux에선 항상 `true`를 +반환합니다. + ### `win.setThumbarButtons(buttons)` _Windows 7+_ -`buttons` - `button` 객체의 배열: - -`button` 객체는 다음과 같은 속성을 가지고 있습니다: - -* `icon` [NativeImage](native-image.md) - 미리보기 툴바에 보여질 아이콘. -* `tooltip` String (optional) - 버튼의 툴팁 텍스트. -* `flags` Array (optional) - 버튼의 특정 동작 및 상태 제어. 기본적으로 `enabled`이 - 사용됩니다. 이 속성은 다음 문자열들을 포함할 수 있습니다: - * `enabled` - 사용자가 사용할 수 있도록 버튼이 활성화 됩니다. - * `disabled` - 버튼이 비활성화 됩니다. 버튼은 표시되지만 시각적인 상태는 사용자의 - 동작에 응답하지 않는 비활성화 상태로 표시됩니다. - * `dismissonclick` - 버튼이 클릭되면 작업표시줄 버튼의 미리보기(flyout)가 즉시 - 종료됩니다. - * `nobackground` - 버튼의 테두리를 표시하지 않습니다. 이미지에만 사용할 수 있습니다. - * `hidden` - 버튼을 사용자에게 표시되지 않도록 숨깁니다. - * `noninteractive` - 버튼은 활성화되어 있지만 반응이 제거되며 버튼을 눌러도 - 눌려지지 않은 상태를 유지합니다. 이 값은 버튼을 알림의 용도로 사용하기 위해 - 만들어졌습니다. -* `click` - Function +* `buttons` - Array 윈도우 작업표시줄 버튼 레이아웃의 미리보기 이미지 영역에 미리보기 툴바와 버튼 세트를 -지정합니다. 반환되는 `Boolean` 값은 미리보기 툴바가 성공적으로 추가됬는지를 알려줍니다. +추가합니다. 반환되는 `Boolean` 값은 미리보기 툴바가 성공적으로 추가됬는지를 알려줍니다. 미리보기 이미지 영역의 제한된 크기로 인해 미리보기 툴바에 추가될 수 있는 최대 버튼의 개수는 7개이며 이 이상 추가될 수 없습니다. 플랫폼의 제약으로 인해 미리보기 툴바는 한 번 설정되면 삭제할 수 없습니다. 하지만 이 API에 빈 배열을 전달하여 버튼들을 제거할 수 있습니다. +`buttons`는 `Button` 객체의 배열입니다: + +* `Button` 객체 + * `icon` [NativeImage](native-image.md) - 미리보기 툴바에 보여질 아이콘. + * `tooltip` String (optional) - 버튼의 툴팁 텍스트. + * `flags` Array (optional) - 버튼의 특정 동작 및 상태 제어. 기본적으로 `enabled`이 + 사용됩니다. 이 속성은 다음 문자열들을 포함할 수 있습니다: + * `enabled` - 사용자가 사용할 수 있도록 버튼이 활성화 됩니다. + * `disabled` - 버튼이 비활성화 됩니다. 버튼은 표시되지만 시각적인 상태는 사용자의 + 동작에 응답하지 않는 비활성화 상태로 표시됩니다. + * `dismissonclick` - 버튼이 클릭되면 작업표시줄 버튼의 미리보기(flyout)가 즉시 + 종료됩니다. + * `nobackground` - 버튼의 테두리를 표시하지 않습니다. 이미지에만 사용할 수 있습니다. + * `hidden` - 버튼을 사용자에게 표시되지 않도록 숨깁니다. + * `noninteractive` - 버튼은 활성화되어 있지만 반응이 제거되며 버튼을 눌러도 + 눌려지지 않은 상태를 유지합니다. 이 값은 버튼을 알림의 용도로 사용하기 위해 + 만들어졌습니다. + ### `win.showDefinitionForSelection()` _OS X_ 페이지의 선택된 단어에 대한 사전 검색 결과 팝업을 표시합니다. diff --git a/docs-translations/ko-KR/api/clipboard.md b/docs-translations/ko-KR/api/clipboard.md index 2a0c98da2c0..df6475ad611 100644 --- a/docs-translations/ko-KR/api/clipboard.md +++ b/docs-translations/ko-KR/api/clipboard.md @@ -62,6 +62,19 @@ console.log(clipboard.readText('selection')); 클립보드에 `image`를 씁니다. +### `clipboard.readRtf([type])` + +* `type` String (optional) + +클립보드로부터 RTF 형식으로 컨텐츠를 읽어옵니다. + +### `clipboard.writeRtf(text[, type])` + +* `text` String +* `type` String (optional) + +클립보드에 `text`를 RTF 형식으로 씁니다. + ### `clipboard.clear([type])` * `type` String (optional) diff --git a/docs-translations/ko-KR/api/crash-reporter.md b/docs-translations/ko-KR/api/crash-reporter.md index 0b7daba14dd..c1437571d3f 100644 --- a/docs-translations/ko-KR/api/crash-reporter.md +++ b/docs-translations/ko-KR/api/crash-reporter.md @@ -16,25 +16,27 @@ crashReporter.start({ }); ``` +서버가 크래시 리포트를 받을 수 있도록 설정하기 위해 다음과 같은 프로젝트를 사용할 수도 +있습니다: + +* [socorro](https://github.com/mozilla/socorro) +* [mini-breakpad-server](https://github.com/atom/mini-breakpad-server) + ## Methods `crash-reporter` 모듈은 다음과 같은 메서드를 가지고 있습니다: ### `crashReporter.start(options)` -* `options` Object, properties: - -* `productName` String, 기본값: Electron -* `companyName` String (**필수항목**) -* `submitURL` String, (**필수항목**) - * 크래시 리포트는 POST 방식으로 이 URL로 전송됩니다. -* `autoSubmit` Boolean, 기본값: true - * true로 지정할 경우 유저의 승인 없이 자동으로 오류를 보고합니다. -* `ignoreSystemCrashHandler` Boolean, 기본값: false -* `extra` Object - * 크래시 리포트 시 같이 보낼 추가 정보를 지정하는 객체입니다. - * 문자열로 된 속성만 정상적으로 보내집니다. - * 중첩 객체는 지원되지 않습니다. (Nested objects are not supported) +* `options` Object + * `companyName` String + * `submitURL` String - 크래시 리포트는 POST 방식으로 이 URL로 전송됩니다. + * `productName` String (optional) - 기본값은 `Electron` 입니다. + * `autoSubmit` Boolean - 유저의 승인 없이 자동으로 오류를 보고합니다. 기본값은 + `true` 입니다. + * `ignoreSystemCrashHandler` Boolean - 기본값은 `false` 입니다. + * `extra` Object - 크래시 리포트 시 같이 보낼 추가 정보를 지정하는 객체입니다. + 문자열로 된 속성만 정상적으로 보내집니다. 중첩된 객체는 지원되지 않습니다. 다른 crashReporter API를 사용하기 전에 이 메서드를 먼저 호출해야 합니다. @@ -54,15 +56,15 @@ crashReporter.start({ ## crash-reporter 업로드 형식 -Crash Reporter는 다음과 같은 데이터를 `submitURL`에 `POST` 방식으로 전송합니다: +Crash Reporter는 다음과 같은 데이터를 `submitURL`에 `multipart/form-data` `POST` 방식으로 전송합니다: -* `ver` String - Electron의 버전 -* `platform` String - 예시 'win32' -* `process_type` String - 예시 'renderer' -* `guid` String - e.g. '5e1286fc-da97-479e-918b-6bfb0c3d1c72' -* `_version` String - `package.json`내의 `version` 필드 +* `ver` String - Electron의 버전. +* `platform` String - e.g. 'win32'. +* `process_type` String - e.g. 'renderer'. +* `guid` String - e.g. '5e1286fc-da97-479e-918b-6bfb0c3d1c72'. +* `_version` String - `package.json`내의 `version` 필드. * `_productName` String - Crash Reporter의 `options` 객체에서 정의한 제품명. * `prod` String - 기본 제품의 이름. 이 경우 Electron으로 표시됩니다. * `_companyName` String - Crash Reporter의 `options` 객체에서 정의한 회사명. -* `upload_file_minidump` File - 크래시 리포트 파일 +* `upload_file_minidump` File - `minidump` 형식의 크래시 리포트 파일. * Crash Reporter의 `options` 객체에서 정의한 `extra` 객체의 속성들. diff --git a/docs-translations/ko-KR/api/environment-variables.md b/docs-translations/ko-KR/api/environment-variables.md index 11d35069216..465548e003d 100644 --- a/docs-translations/ko-KR/api/environment-variables.md +++ b/docs-translations/ko-KR/api/environment-variables.md @@ -1,6 +1,7 @@ # 환경 변수 -Electron의 몇몇 동작은 명령 줄과 어플리케이션의 코드보다 먼저 초기화되어야 하므로 환경 변수에 의해 작동합니다. +Electron의 몇몇 동작은 명령 줄과 어플리케이션의 코드보다 먼저 초기화되어야 하므로 환경 +변수에 의해 작동합니다. POSIX 쉘의 예시입니다: @@ -24,6 +25,11 @@ Windows 콘솔의 예시입니다: Chrome의 내부 로그를 콘솔에 출력합니다. +## `ELECTRON_LOG_ASAR_READS` + +Electron이 ASAR 파일을 읽을 때, 읽기 오프셋의 로그를 남기고 시스템 `tmpdir`에 파일로 +저장합니다. 결과 파일은 ASAR 모듈의 파일 순서를 최적화 하는데 사용할 수 있습니다. + ## `ELECTRON_ENABLE_STACK_DUMPING` Electron이 크래시되면, 콘솔에 stack trace를 출력합니다. diff --git a/docs-translations/ko-KR/api/frameless-window.md b/docs-translations/ko-KR/api/frameless-window.md index 9dbac12d5fb..94c87ca9a64 100644 --- a/docs-translations/ko-KR/api/frameless-window.md +++ b/docs-translations/ko-KR/api/frameless-window.md @@ -1,7 +1,7 @@ # Frameless Window Frameless Window는 [창 테두리](https://developer.mozilla.org/en-US/docs/Glossary/Chrome)가 -없는 윈도우를 말합니다. 이 기능은 윈도우 창의 일부분인 툴바와 같이 웹 페이지의 일부분이 +없는 윈도우를 말합니다. 이 기능은 윈도우의 일부분인 툴바와 같이 웹 페이지의 일부분이 아닌 부분을 보이지 않도록 합니다. [`BrowserWindow`](browser-window.md) 클래스의 옵션에서 설정할 수 있습니다. diff --git a/docs-translations/ko-KR/api/ipc-main.md b/docs-translations/ko-KR/api/ipc-main.md index 3ae519d294b..30b10247f9d 100644 --- a/docs-translations/ko-KR/api/ipc-main.md +++ b/docs-translations/ko-KR/api/ipc-main.md @@ -8,8 +8,7 @@ ## 메시지 전송 물론 메시지를 받는 것 말고도 메인 프로세스에서 랜더러 프로세스로 보내는 것도 가능합니다. -자세한 내용은 [webContents.send](web-contents.md#webcontentssendchannel-arg1-arg2-)를 -참고하세요. +자세한 내용은 [webContents.send][web-contents-send]를 참고하세요. * 메시지를 전송할 때 이벤트 이름은 `channel`이 됩니다. * 메시지에 동기로 응답할 땐 반드시 `event.returnValue`를 설정해야 합니다. @@ -46,34 +45,40 @@ ipcRenderer.send('asynchronous-message', 'ping'); `ipcMain`은 다음과 같은 이벤트 리스닝 메서드를 가지고 있습니다: -### `ipcMain.on(channel, callback)` +### `ipcMain.on(channel, listener)` -* `channel` String - 이벤트 이름 -* `callback` Function +* `channel` String +* `listener` Function -이벤트가 발생하면 `event` 객체와 임의 메시지와 함께 `callback`이 호출됩니다. +`channel`에 대해 이벤트를 리스닝합니다. 새로운 메시지가 도착하면 `listener`가 +`listener(event, args...)` 형식으로 호출됩니다. -### `ipcMain.removeListener(channel, callback)` +### `ipcMain.once(channel, listener)` -* `channel` String - 이벤트의 이름 -* `callback` Function - `ipcMain.on(channel, callback)`에서 사용한 함수의 레퍼런스 +* `channel` String +* `listener` Function + +이벤트에 대해 한 번만 작동하는 `listener` 함수를 등록합니다. 이 `listener`는 등록된 +후 `channel`에 보내지는 메시지에 한해 호출됩니다. 호출된 이후엔 리스너가 삭제됩니다. + +### `ipcMain.removeListener(channel, listener)` + +* `channel` String +* `listener` Function 메시지 수신을 완료한 후, 더 이상의 콜백이 필요하지 않을 때 또는 몇 가지 이유로 채널의 메시지 전송을 멈출수 없을 때, 이 함수를 통해 지정한 채널에 대한 콜백을 삭제할 수 있습니다. +지정한 `channel`에 대한 리스너를 저장하는 배열에서 지정한 `listener`를 삭제합니다. + ### `ipcMain.removeAllListeners(channel)` -* `channel` String - 이벤트의 이름 +* `channel` String (optional) -이 ipc 채널에 등록된 *모든* 핸들러들을 삭제합니다. +이 ipc 채널에 등록된 모든 핸들러들을 삭제하거나 지정한 `channel`을 삭제합니다. -### `ipcMain.once(channel, callback)` - -`ipcMain.on()` 대신 이 함수를 사용할 경우 핸들러가 단 한 번만 호출됩니다. -`callback`이 한 번 호출된 이후 활성화되지 않습니다. - -## IPC Event +## Event 객체 `callback`에서 전달된 `event` 객체는 다음과 같은 메서드와 속성을 가지고 있습니다: @@ -85,5 +90,6 @@ ipcRenderer.send('asynchronous-message', 'ping'); 메시지를 보낸 `webContents` 객체를 반환합니다. `event.sender.send` 메서드를 통해 비동기로 메시지를 전달할 수 있습니다. 자세한 내용은 -[webContents.send](web-contents.md#webcontentssendchannel-arg1-arg2-)를 -참고하세요. +[webContents.send][web-contents-send]를 참고하세요. + +[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- diff --git a/docs-translations/ko-KR/api/ipc-renderer.md b/docs-translations/ko-KR/api/ipc-renderer.md index aba632430b5..0d43b157d16 100644 --- a/docs-translations/ko-KR/api/ipc-renderer.md +++ b/docs-translations/ko-KR/api/ipc-renderer.md @@ -10,32 +10,38 @@ `ipcRenderer`는 다음과 같은 이벤트 리스닝 메서드를 가지고 있습니다: -### `ipcRenderer.on(channel, callback)` +### `ipcMain.on(channel, listener)` -* `channel` String - 이벤트 이름 -* `callback` Function +* `channel` String +* `listener` Function -이벤트가 일어나면 `event` 객체와 임의의 인자와 함께 `callback` 함수가 호출됩니다. +`channel`에 대해 이벤트를 리스닝합니다. 새로운 메시지가 도착하면 `listener`가 +`listener(event, args...)` 형식으로 호출됩니다. -### `ipcMain.removeListener(channel, callback)` +### `ipcMain.once(channel, listener)` -* `channel` String - 이벤트의 이름 -* `callback` Function - `ipcMain.on(channel, callback)`에서 사용한 함수의 레퍼런스 +* `channel` String +* `listener` Function + +이벤트에 대해 한 번만 작동하는 `listener` 함수를 등록합니다. 이 `listener`는 등록된 +후 `channel`에 보내지는 메시지에 한해 호출됩니다. 호출된 이후엔 리스너가 삭제됩니다. + +### `ipcMain.removeListener(channel, listener)` + +* `channel` String +* `listener` Function 메시지 수신을 완료한 후, 더 이상의 콜백이 필요하지 않을 때 또는 몇 가지 이유로 채널의 메시지 전송을 멈출수 없을 때, 이 함수를 통해 지정한 채널에 대한 콜백을 삭제할 수 있습니다. +지정한 `channel`에 대한 리스너를 저장하는 배열에서 지정한 `listener`를 삭제합니다. + ### `ipcMain.removeAllListeners(channel)` -* `channel` String - 이벤트의 이름 +* `channel` String (optional) -이 ipc 채널에 등록된 *모든* 핸들러들을 삭제합니다. - -### `ipcMain.once(channel, callback)` - -`ipcMain.on()` 대신 이 함수를 사용할 경우 핸들러가 단 한 번만 호출됩니다. -`callback`이 한 번 호출된 이후 활성화되지 않습니다. +이 ipc 채널에 등록된 모든 핸들러들을 삭제하거나 지정한 `channel`을 삭제합니다. ## 메시지 보내기 @@ -43,23 +49,26 @@ ### `ipcRenderer.send(channel[, arg1][, arg2][, ...])` -* `channel` String - 이벤트 이름 +* `channel` String * `arg` (optional) `channel`을 통해 메인 프로세스에 비동기 메시지를 보냅니다. 그리고 필요에 따라 임의의 -인자를 사용할 수도 있습니다. 메인 프로세스는 `ipcMain` 모듈의 `channel` 이벤트를 통해 +인수를 사용할 수도 있습니다. 인수들은 내부적으로 JSON 포맷으로 직렬화 되며, 이후 함수와 +프로토타입 체인은 포함되지 않게 됩니다. + +메인 프로세스는 `ipcMain` 모듈의 `channel` 이벤트를 통해 이벤트를 리스닝 할 수 있습니다. ### `ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])` -* `channel` String - 이벤트 이름 +* `channel` String * `arg` (optional) `channel`을 통해 메인 프로세스에 동기 메시지를 보냅니다. 그리고 필요에 따라 임의의 -인자를 사용할 수도 있습니다. 메인 프로세스는 `ipc`를 통해 `channel` 이벤트를 리스닝 -할 수 있습니다. +인자를 사용할 수도 있습니다. 인수들은 내부적으로 JSON 포맷으로 직렬화 되며, 이후 함수와 +프로토타입 체인은 포함되지 않게 됩니다. -메인 프로세스에선 `ipcMain` 모듈의 `channel` 이벤트를 통해 받은 +메인 프로세스는 `ipcMain` 모듈을 통해 `channel` 이벤트를 리스닝 할 수 있고, `event.returnValue`로 회신 할 수 있습니다. __참고:__ 동기 메서드는 모든 랜더러 프로세스의 작업을 일시 중단시킵니다. 사용 목적이 @@ -67,7 +76,7 @@ __참고:__ 동기 메서드는 모든 랜더러 프로세스의 작업을 일 ### `ipcRenderer.sendToHost(channel[, arg1][, arg2][, ...])` -* `channel` String - 이벤트 이름 +* `channel` String * `arg` (optional) `ipcRenderer.send`와 비슷하지만 이벤트를 메인 프로세스 대신 호스트 페이지내의 diff --git a/docs-translations/ko-KR/api/menu.md b/docs-translations/ko-KR/api/menu.md index 61bf92bf76d..44fe461bcc2 100644 --- a/docs-translations/ko-KR/api/menu.md +++ b/docs-translations/ko-KR/api/menu.md @@ -140,7 +140,7 @@ var template = [ ]; if (process.platform == 'darwin') { - var name = require('electron').app.getName(); + var name = require('electron').remote.app.getName(); template.unshift({ label: name, submenu: [ @@ -166,7 +166,7 @@ if (process.platform == 'darwin') { }, { label: 'Hide Others', - accelerator: 'Command+Shift+H', + accelerator: 'Command+Alt+H', role: 'hideothers' }, { @@ -223,7 +223,9 @@ Linux에선 각 창의 상단에 표시됩니다. `action`을 어플리케이션의 first responder에 전달합니다. 이 메서드는 Cocoa 메뉴 동작을 에뮬레이트 하는데 사용되며 보통 `MenuItem`의 `role` 속성에 사용됩니다. -**참고:** 이 메서드는 OS X에서만 사용할 수 있습니다. +OS X의 네이티브 액션에 대해 자세히 알아보려면 +[OS X 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)` @@ -235,29 +237,41 @@ Linux에선 각 창의 상단에 표시됩니다. 또한 `template`에는 다른 속성도 추가할 수 있으며 메뉴가 만들어질 때 해당 메뉴 아이템의 프로퍼티로 변환됩니다. -### `Menu.popup([browserWindow, x, y])` +## Instance Methods -* `browserWindow` BrowserWindow (optional) -* `x` Number (optional) -* `y` Number (만약 `x`를 지정했을 경우 반드시 `y`도 지정해야 합니다) +`menu` 객체는 다음과 같은 인스턴스 메서드를 가지고 있습니다: + +### `menu.popup([browserWindow, x, y, positioningItem])` + +* `browserWindow` BrowserWindow (optional) - 기본값은 `null`입니다. +* `x` Number (optional) - 기본값은 -1입니다. +* `y` Number (만약 `x`를 지정한 경우 **필수 항목**) - 기본값은 -1입니다. +* `positioningItem` Number (optional) _OS X_ - 메뉴 팝업 시 마우스 커서에 바로 + 위치시킬 메뉴 아이템의 인덱스. 기본값은 -1입니다. 메뉴를 `browserWindow` 내부 팝업으로 표시합니다. 옵션으로 메뉴를 표시할 `(x,y)` 좌표를 지정할 수 있습니다. 따로 좌표를 지정하지 않은 경우 마우스 커서 위치에 표시됩니다. +`positioningItem` 속성은 메뉴 팝업 시 마우스 커서에 바로 위치시킬 메뉴 아이템의 +인덱스입니다. (OS X에서만 지원합니다) -### `Menu.append(menuItem)` +### `menu.append(menuItem)` * `menuItem` MenuItem 메뉴의 리스트 끝에 `menuItem`을 삽입합니다. -### `Menu.insert(pos, menuItem)` +### `menu.insert(pos, menuItem)` * `pos` Integer * `menuItem` MenuItem `pos` 위치에 `menuItem`을 삽입합니다. -### `Menu.items()` +## Instance Properties + +`menu` 객체는 또한 다음과 같은 속성을 가지고 있습니다: + +### `menu.items` 메뉴가 가지고 있는 메뉴 아이템들의 배열입니다. @@ -289,6 +303,11 @@ OS X에선 지정한 어플리케이션 메뉴에 상관없이 메뉴의 첫번 이름이 됩니다. 어플리케이션 이름을 변경하려면 앱 번들내의 `Info.plist` 파일을 수정해야 합니다. 자세한 내용은 [About Information Property List Files][AboutInformationPropertyListFiles] 문서를 참고하세요. +## 지정한 브라우저 윈도우에 메뉴 설정 (*Linux* *Windows*) + +브라우저 윈도우의 [`setMenu` 메서드][setMenu]는 어떤 브라우저 윈도우의 메뉴를 설정할 +수 있습니다. + ## 메뉴 아이템 위치 `Menu.buildFromTemplate`로 메뉴를 만들 때 `position`과 `id`를 사용해서 아이템의 @@ -363,3 +382,4 @@ OS X에선 지정한 어플리케이션 메뉴에 상관없이 메뉴의 첫번 ``` [AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html +[setMenu]: https://github.com/atom/electron/blob/master/docs-translations/ko-KR/api/browser-window.md#winsetmenumenu-linux-windows diff --git a/docs-translations/ko-KR/api/native-image.md b/docs-translations/ko-KR/api/native-image.md index 80be2beeafc..2ef35668cae 100644 --- a/docs-translations/ko-KR/api/native-image.md +++ b/docs-translations/ko-KR/api/native-image.md @@ -131,6 +131,15 @@ var image = nativeImage.createFromPath('/Users/somebody/images/icon.png'); 이미지를 data URL로 반환합니다. +### `image.getNativeHandle()` _OS X_ + +이미지의 네이티브 핸들 밑에 있는 C 포인터를 담은 [Buffer][buffer]을 반환합니다. +OS X에선, `NSImage` 인스턴스가 반환됩니다. + +참고로 반환된 포인터는 복사본이 아닌 네이티브 이미지의 밑에 있는 약한 포인터이며, +따라서 반드시 관련된 `nativeImage` 인스턴스가 확실하게 유지되고 있는지를 인지해야 +합니다. + ### `image.isEmpty()` 이미지가 비었는지 확인합니다. diff --git a/docs-translations/ko-KR/api/power-monitor.md b/docs-translations/ko-KR/api/power-monitor.md index 29a3aabc84a..63d5959fe20 100644 --- a/docs-translations/ko-KR/api/power-monitor.md +++ b/docs-translations/ko-KR/api/power-monitor.md @@ -29,10 +29,10 @@ app.on('ready', function() { 시스템의 절전모드가 해제될 때 발생하는 이벤트입니다. -## Event: `on-ac` +## Event: `on-ac` _Windows_ 시스템이 AC 어뎁터 충전기를 사용하기 시작할 때 발생하는 이벤트입니다. -## Event: `on-battery` +## Event: `on-battery` _Windows_ 시스템이 배터리를 사용하기 시작할 때 발생하는 이벤트입니다. diff --git a/docs-translations/ko-KR/api/protocol.md b/docs-translations/ko-KR/api/protocol.md index f6f2ac65c95..2b42226e2ff 100644 --- a/docs-translations/ko-KR/api/protocol.md +++ b/docs-translations/ko-KR/api/protocol.md @@ -50,14 +50,26 @@ app.on('ready', function() { `completion` 콜백은 `scheme`가 성공적으로 등록되었을 때 `completion(null)` 형식으로 호출되고, 등록에 실패했을 땐 `completion(error)` 형식으로 에러 내용을 담아 호출됩니다. +* `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` Array (optional) +* `callback` Function + +The `uploadData` is an array of `data` objects: + +* `data` Object + * `bytes` Buffer - Content being sent. + * `file` String - Path of file being uploaded. + `request`를 처리할 때 반드시 파일 경로 또는 `path` 속성을 포함하는 객체를 인자에 포함하여 `callback`을 호출해야 합니다. 예: `callback(filePath)` 또는 `callback({path: filePath})`. 만약 `callback`이 아무 인자도 없이 호출되거나 숫자나 `error` 프로퍼티를 가진 객체가 인자로 전달될 경우 `request`는 지정한 `error` 코드(숫자)를 출력합니다. 사용할 수 있는 -에러 코드는 [네트워크 에러 목록](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h)에서 -확인할 수 있습니다. +에러 코드는 [네트워크 에러 목록][net-error]에서 확인할 수 있습니다. 기본적으로 `scheme`은 `http:`와 같이 처리됩니다. `file:`과 같이 "일반적인 URI 문법" 과는 다르게 인식되는 프로토콜은 `protocol.registerStandardSchemes`을 사용하여 표준 @@ -69,9 +81,11 @@ app.on('ready', function() { * `handler` Function * `completion` Function (optional) -`scheme`에 `Buffer`를 응답으로 보내는 프로토콜을 등록합니다. 반드시 `Buffer` 또는 -`data`, `mimeType`, `chart` 속성을 포함한 객체 중 하나를 인자에 포함하여 -`callback`을 호출해야 합니다. +`Buffer`를 응답으로 전송하는 `scheme`의 프로토콜을 등록합니다. + +사용법은 `callback`이 반드시 `Buffer` 객체 또는 `data`, `mimeType`, `charset` +속성을 포함하는 객체와 함께 호출되어야 한다는 점을 제외하면 `registerFileProtocol`과 +사용법이 같습니다. 예제: @@ -90,9 +104,11 @@ protocol.registerBufferProtocol('atom', function(request, callback) { * `handler` Function * `completion` Function (optional) -`scheme`에 `문자열`을 응답으로 보내는 프로토콜을 등록합니다. 반드시 `문자열` 또는 -`data`, `mimeType`, `chart` 속성을 포함한 객체 중 하나를 인자에 포함하여 -`callback`을 호출해야 합니다. +`String`을 응답으로 전송할 `scheme`의 프로토콜을 등록합니다. + +사용법은 `callback`이 반드시 `String` 또는 `data`, `mimeType`, `charset` 속성을 +포함하는 객체와 함께 호출되어야 한다는 점을 제외하면 `registerFileProtocol`과 +사용법이 같습니다. ### `protocol.registerHttpProtocol(scheme, handler[, completion])` @@ -100,17 +116,26 @@ protocol.registerBufferProtocol('atom', function(request, callback) { * `handler` Function * `completion` Function (optional) -`scheme`에 HTTP 요청을 응답으로 보내는 프로토콜을 등록합니다. 반드시 `url`, -`method`, `referrer`, `uploadData` 그리고 `session` 속성을 포함하는 객체를 인자에 -포함하여 `callback`을 호출해야 합니다. +HTTP 요청을 응답으로 전송할 `scheme`의 프로토콜을 등록합니다. + +사용법은 `callback`이 반드시 `url`, `method`, `referrer`, `uploadData`와 +`session` 속성을 포함하는 `redirectRequest` 객체와 함께 호출되어야 한다는 점을 +제외하면 `registerFileProtocol`과 사용법이 같습니다. + +* `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (optional) + * `uploadData` Object (optional) 기본적으로 HTTP 요청은 현재 세션을 재사용합니다. 만약 서로 다른 세션에 요청을 보내고 싶으면 `session`을 `null`로 지정해야 합니다. -POST 요청은 반드시 `uploadData` 객체가 제공되어야 합니다. +POST 요청에는 반드시 `uploadData` 객체가 제공되어야 합니다. + * `uploadData` object * `contentType` String - 컨텐츠의 MIME 타입. - * `data` String - 전송할 컨텐츠. + * `data` String - 전송할 컨텐츠. ### `protocol.unregisterProtocol(scheme[, completion])` @@ -167,3 +192,5 @@ POST 요청은 반드시 `uploadData` 객체가 제공되어야 합니다. * `completion` Function (optional) 가로챈 `scheme`를 삭제하고 기본 핸들러로 복구합니다. + +[net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h diff --git a/docs-translations/ko-KR/api/session.md b/docs-translations/ko-KR/api/session.md index bf5c157ede6..5bb4867eda0 100644 --- a/docs-translations/ko-KR/api/session.md +++ b/docs-translations/ko-KR/api/session.md @@ -59,7 +59,8 @@ var ses = session.fromPartition('persist:name'); Electron의 `webContents`에서 `item`을 다운로드할 때 발생하는 이벤트입니다. -`event.preventDefault()` 메서드를 호출하면 다운로드를 취소합니다. +`event.preventDefault()` 메서드를 호출하면 다운로드를 취소하고, 프로세스의 다음 +틱부터 `item`을 사용할 수 없게 됩니다. ```javascript session.defaultSession.on('will-download', function(event, item, webContents) { @@ -212,6 +213,7 @@ proxyURL = ["://"][":"] ``` 예시: + * `http=foopy:80;ftp=foopy2` - http:// URL에 `foopy:80` HTTP 프록시를 사용합니다. `foopy2:80` 는 ftp:// URL에 사용됩니다. * `foopy:80` - 모든 URL에 `foopy:80` 프록시를 사용합니다. @@ -287,6 +289,36 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c callback(false); }); ``` +#### `ses.setPermissionRequestHandler(handler)` + +* `handler` Function + * `webContents` Object - [WebContents](web-contents.md) 권한을 요청. + * `permission` String - 'media', 'geolocation', 'notifications', + 'midiSysex', 'pointerLock', 'fullscreen'의 나열. + * `callback` Function - 권한 허용 및 거부. + +`session`의 권한 요청에 응답을 하는데 사용하는 핸들러를 설정합니다. +`callback(true)`를 호출하면 권한 제공을 허용하고 `callback(false)`를 +호출하면 권한 제공을 거부합니다. + +```javascript +session.fromPartition(partition).setPermissionRequestHandler(function(webContents, permission, callback) { + if (webContents.getURL() === host) { + if (permission == "notifications") { + callback(false); // 거부됨. + return; + } + } + + callback(true); +}); +``` + +#### `ses.clearHostResolverCache([callback])` + +* `callback` Function (optional) - 작업이 완료되면 호출됩니다. + +호스트 리소버(resolver) 캐시를 지웁니다. #### `ses.webRequest` @@ -329,6 +361,14 @@ session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, * `method` String * `resourceType` String * `timestamp` Double + * `uploadData` Array (optional) +* `callback` Function + +`uploadData`는 `data` 객체의 배열입니다: + +* `data` Object + * `bytes` Buffer - 전송될 컨텐츠. + * `file` String - 업로드될 파일의 경로. `callback`은 `response` 객체와 함께 호출되어야 합니다: @@ -353,6 +393,7 @@ HTTP 요청을 보내기 전 요청 헤더를 사용할 수 있을 때 `listener * `resourceType` String * `timestamp` Double * `requestHeaders` Object +* `callback` Function `callback`은 `response` 객체와 함께 호출되어야 합니다: @@ -395,6 +436,7 @@ HTTP 요청을 보내기 전 요청 헤더를 사용할 수 있을 때 `listener * `statusLine` String * `statusCode` Integer * `responseHeaders` Object +* `callback` Function `callback`은 `response` 객체와 함께 호출되어야 합니다: diff --git a/docs-translations/ko-KR/api/shell.md b/docs-translations/ko-KR/api/shell.md index 4183d3b2502..c4831092f3c 100644 --- a/docs-translations/ko-KR/api/shell.md +++ b/docs-translations/ko-KR/api/shell.md @@ -25,12 +25,16 @@ shell.openExternal('https://github.com'); 지정한 파일을 데스크톱 기본 프로그램으로 엽니다. -### `shell.openExternal(url)` +### `shell.openExternal(url[, options])` * `url` String +* `options` Object (optional) _OS X_ + * `activate` Boolean - `true`로 설정하면 어플리케이션을 바로 활성화 상태로 + 실행합니다. 기본값은 `true`입니다. 제공된 외부 프로토콜 URL을 기반으로 데스크톱의 기본 프로그램으로 엽니다. (예를 들어 -mailto: URL은 유저의 기본 이메일 에이전트로 URL을 엽니다.) +mailto: URL은 유저의 기본 이메일 에이전트로 URL을 엽니다.) 어플리케이션이 해당 URL을 +열 수 있을 때 `true`를 반환합니다. 아니라면 `false`를 반환합니다. 역주: 폴더는 'file:\\\\C:\\'와 같이 지정하여 열 수 있습니다. (Windows의 경우) diff --git a/docs-translations/ko-KR/api/synopsis.md b/docs-translations/ko-KR/api/synopsis.md index e70dfbb3349..8f59d12c1ce 100644 --- a/docs-translations/ko-KR/api/synopsis.md +++ b/docs-translations/ko-KR/api/synopsis.md @@ -49,7 +49,7 @@ app.on('ready', function() { ## 분리 할당 만약 CoffeeScript나 Babel을 사용하고 있다면, 빌트인 모듈을 사용할 때 -[분리 할당][desctructuring-assignment]을 통해 직관적으로 사용할 수 있습니다: +[분리 할당][destructuring-assignment]을 통해 직관적으로 사용할 수 있습니다: ```javascript const {app, BrowserWindow} = require('electron') @@ -78,5 +78,5 @@ require('electron').hideInternalModules() ``` [gui]: https://en.wikipedia.org/wiki/Graphical_user_interface -[desctructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +[destructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment [issue-387]: https://github.com/atom/electron/issues/387 diff --git a/docs-translations/ko-KR/api/tray.md b/docs-translations/ko-KR/api/tray.md index e182f6471e7..d82b0a9d7e4 100644 --- a/docs-translations/ko-KR/api/tray.md +++ b/docs-translations/ko-KR/api/tray.md @@ -32,6 +32,13 @@ __플랫폼별 한계:__ 트레이 아이콘이 작동하도록 만들 수 있습니다. * 앱 알림 표시기는 컨텍스트 메뉴를 가지고 있을 때만 보입니다. * Linux에서 앱 표시기가 사용될 경우, `click` 이벤트는 무시됩니다. +* Linux에서 각각 개별 `MenuItem`의 변경을 적용하려면 `setContextMenu`를 다시 + 호출해야 합니다. 예를 들면: + +```javascript +contextMenu.items[2].checked = false; +appIcon.setContextMenu(contextMenu); +``` 이러한 이유로 Tray API가 모든 플랫폼에서 똑같이 작동하게 하고 싶다면 `click` 이벤트에 의존해선 안되며 언제나 컨텍스트 메뉴를 포함해야 합니다. diff --git a/docs-translations/ko-KR/api/web-contents.md b/docs-translations/ko-KR/api/web-contents.md index 59b7d0c153e..1cda4a3af5e 100644 --- a/docs-translations/ko-KR/api/web-contents.md +++ b/docs-translations/ko-KR/api/web-contents.md @@ -255,8 +255,9 @@ Returns: * `result` Object * `requestId` Integer * `finalUpdate` Boolean - 더 많은 응답이 따르는 경우를 표시합니다. - * `matches` Integer (Optional) - 일치하는 개수. - * `selectionArea` Object (Optional) - 첫 일치 부위의 좌표. + * `activeMatchOrdinal` Integer (optional) - 활성화 일치의 위치. + * `matches` Integer (optional) - 일치하는 개수. + * `selectionArea` Object (optional) - 첫 일치 부위의 좌표. [`webContents.findInPage`](web-contents.md#webcontentsfindinpage) 요청의 결과를 사용할 수 있을 때 발생하는 이벤트입니다. @@ -278,6 +279,28 @@ Returns: ``` +### Event: 'cursor-changed' + +Returns: + +* `event` Event +* `type` String +* `image` NativeImage (optional) +* `scale` Float (optional) + +커서 타입이 변경될 때 발생하는 이벤트입니다. `type` 매개변수는 다음 값이 될 수 있습니다: +`default`, `crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, +`ne-resize`, `nw-resize`, `s-resize`, `se-resize`, `sw-resize`, `w-resize`, +`ns-resize`, `ew-resize`, `nesw-resize`, `nwse-resize`, `col-resize`, +`row-resize`, `m-panning`, `e-panning`, `n-panning`, `ne-panning`, `nw-panning`, +`s-panning`, `se-panning`, `sw-panning`, `w-panning`, `move`, `vertical-text`, +`cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, +`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. + +만약 `type` 매개변수가 `custom` 이고 `image` 매개변수가 `NativeImage`를 통한 커스텀 +커서를 지정했을 때, 해당 이미지로 커서가 변경됩니다. 또한 `scale` 매개변수는 이미지의 +크기를 조정합니다. + ## Instance Methods `webContents`객체는 다음과 같은 인스턴스 메서드들을 가지고 있습니다. @@ -285,12 +308,12 @@ Returns: ### `webContents.loadURL(url[, options])` * `url` URL -* `options` Object (optional), 속성들: - * `httpReferrer` String - HTTP Referrer url. +* `options` Object (optional) + * `httpReferrer` String - HTTP 레퍼러 url. * `userAgent` String - 요청을 시작한 유저 에이전트. * `extraHeaders` String - "\n"로 구분된 Extra 헤더들. -윈도우에 웹 페이지 `url`을 로드합니다. `url`은 `http://` or `file://`과 같은 +윈도우에 웹 페이지 `url`을 로드합니다. `url`은 `http://`, `file://`과 같은 프로토콜 접두사를 가지고 있어야 합니다. 만약 반드시 http 캐시를 사용하지 않고 로드해야 하는 경우 `pragma` 헤더를 사용할 수 있습니다. @@ -399,10 +422,12 @@ var currentURL = win.webContents.getURL(); CSS 코드를 현재 웹 페이지에 삽입합니다. -### `webContents.executeJavaScript(code[, userGesture])` +### `webContents.executeJavaScript(code[, userGesture, callback])` * `code` String * `userGesture` Boolean (optional) +* `callback` Function (optional) - 스크립트의 실행이 완료되면 호출됩니다. + * `result` 페이지에서 자바스크립트 코드를 실행합니다. @@ -471,7 +496,7 @@ CSS 코드를 현재 웹 페이지에 삽입합니다. ### `webContents.findInPage(text[, options])` * `text` String - 찾을 컨텐츠, 반드시 공백이 아니여야 합니다. -* `options` Object (Optional) +* `options` Object (optional) * `forward` Boolean - 앞에서부터 검색할지 뒤에서부터 검색할지 여부입니다. 기본값은 `true`입니다. * `findNext` Boolean - 작업을 계속 처리할지 첫 요청만 처리할지 여부입니다. 기본값은 @@ -524,11 +549,10 @@ ServiceWorker가 존재하면 모두 등록을 해제하고 JS Promise가 만족 ### `webContents.print([options])` -`options` Object (optional), properties: - -* `silent` Boolean - 사용자에게 프린트 설정을 묻지 않습니다. 기본값을 `false`입니다. -* `printBackground` Boolean - 웹 페이지의 배경 색과 이미지를 출력합니다. 기본값은 - `false`입니다. +`options` Object (optional) + * `silent` Boolean - 사용자에게 프린트 설정을 묻지 않습니다. 기본값을 `false`입니다. + * `printBackground` Boolean - 웹 페이지의 배경 색과 이미지를 출력합니다. 기본값은 + `false`입니다. 윈도우의 웹 페이지를 프린트합니다. `silent`가 `false`로 지정되어있을 땐, Electron이 시스템의 기본 프린터와 기본 프린터 설정을 가져옵니다. @@ -543,32 +567,23 @@ print기능을 사용하지 않는 경우 전체 바이너리 크기를 줄이 ### `webContents.printToPDF(options, callback)` -`options` Object, properties: - -* `marginsType` Integer - 사용할 마진의 종류를 지정합니다. - * 0 - default - * 1 - none - * 2 - minimum -* `pageSize` String - 생성되는 PDF의 페이지 크기를 지정합니다. - * `A5` - * `A4` - * `A3` - * `Legal` - * `Letter` - * `Tabloid` -* `printBackground` Boolean - CSS 배경을 프린트할지 여부를 정합니다. -* `printSelectionOnly` Boolean - 선택된 영역만 프린트할지 여부를 정합니다. -* `landscape` Boolean - landscape을 위해선 `true`를, portrait를 위해선 `false`를 - 사용합니다. - -`callback` Function - `function(error, data) {}` - -* `error` Error -* `data` Buffer - PDF 파일 내용. +* `options` Object + * `marginsType` Integer - 사용할 마진의 종류를 지정합니다. 0 부터 2 사이 값을 사용할 + 수 있고 각각 기본 마진, 마진 없음, 최소 마진입니다. + * `pageSize` String - 생성되는 PDF의 페이지 크기를 지정합니다. 값은 `A3`, `A4`, + `A5`, `Legal`, `Letter` 와 `Tabloid`가 사용될 수 있습니다. + * `printBackground` Boolean - CSS 배경을 프린트할지 여부를 정합니다. + * `printSelectionOnly` Boolean - 선택된 영역만 프린트할지 여부를 정합니다. + * `landscape` Boolean - landscape을 위해선 `true`를, portrait를 위해선 `false`를 + 사용합니다. +* `callback` Function - `function(error, data) {}` Chromium의 미리보기 프린팅 커스텀 설정을 이용하여 윈도우의 웹 페이지를 PDF로 프린트합니다. +`callback`은 작업이 완료되면 `callback(error, data)` 형식으로 호출됩니다. `data`는 +생성된 PDF 데이터를 담고있는 `Buffer`입니다. + 기본으로 비어있는 `options`은 다음과 같이 여겨지게 됩니다: ```javascript @@ -621,7 +636,7 @@ mainWindow.webContents.on('devtools-opened', function() { ### `webContents.openDevTools([options])` -* `options` Object (optional). Properties: +* `options` Object (optional) * `detach` Boolean - 새 창에서 개발자 도구를 엽니다. 개발자 도구를 엽니다. @@ -634,14 +649,14 @@ mainWindow.webContents.on('devtools-opened', function() { 개발자 도구가 열려있는지 여부를 반환합니다. -### `webContents.toggleDevTools()` - -개발자 도구를 토글합니다. - ### `webContents.isDevToolsFocused()` 개발자 도구에 포커스 되어있는지 여부를 반환합니다. +### `webContents.toggleDevTools()` + +개발자 도구를 토글합니다. + ### `webContents.inspectElement(x, y)` * `x` Integer @@ -659,8 +674,11 @@ mainWindow.webContents.on('devtools-opened', function() { * `arg` (optional) `channel`을 통하여 렌더러 프로세스에 비동기 메시지를 보냅니다. 임의의 인수를 보낼수도 -있습니다. 렌더러 프로세스는 `ipcRenderer` 모듈을 통하여 `channel`를 리슨하여 메시지를 -처리할 수 있습니다. +있습니다. 인수들은 내부적으로 JSON 포맷으로 직렬화 되며, 이후 함수와 프로토타입 체인은 +포함되지 않게 됩니다. + +렌더러 프로세스는 `ipcRenderer` 모듈을 통하여 `channel`를 리스닝하여 메시지를 처리할 +수 있습니다. 메인 프로세스에서 렌더러 프로세스로 메시지를 보내는 예시 입니다: @@ -741,11 +759,8 @@ Input `event`를 웹 페이지로 전송합니다. 키보드 이벤트들에 대해서는 `event` 객체는 다음 속성들을 사용할 수 있습니다: -* `keyCode` Char or String (**required**) - 키보드 이벤트로 보내지는 문자. 단일 - UTF-8 문자를 사용할 수 있고 이벤트를 발생시키는 다음 키 중 하나를 포함할 수 있습니다: - `enter`, `backspace`, `delete`, `tab`, `escape`, `control`, `alt`, `shift`, - `end`, `home`, `insert`, `left`, `up`, `right`, `down`, `pageUp`, `pageDown`, - `printScreen` +* `keyCode` String (**required**) - 키보드 이벤트가 발생할 때 보내질 문자. + [Accelerator](accelerator.md)의 올바른 키 코드만 사용해야 합니다. 마우스 이벤트들에 대해서는 `event` 객체는 다음 속성들을 사용할 수 있습니다: @@ -817,9 +832,83 @@ win.webContents.on('did-finish-load', function() { 이 webContents에서 사용하는 [session](session.md) 객체를 반환합니다. +### `webContents.hostWebContents` + +현재 `WebContents`를 소유하는 `WebContents`를 반환합니다. + ### `webContents.devToolsWebContents` 이 `WebContents`에 대한 개발자 도구의 `WebContents`를 가져옵니다. **참고:** 사용자가 절대로 이 객체를 저장해서는 안 됩니다. 개발자 도구가 닫혔을 때, `null`이 반환될 수 있습니다. + +### `webContents.debugger` + +디버거 API는 [원격 디버깅 프로토콜][rdp]에 대한 대체 수송자 역할을 합니다. + +```javascript +try { + win.webContents.debugger.attach("1.1"); +} catch(err) { + console.log("Debugger attach failed : ", err); +}; + +win.webContents.debugger.on('detach', function(event, reason) { + console.log("Debugger detached due to : ", reason); +}); + +win.webContents.debugger.on('message', function(event, method, params) { + if (method == "Network.requestWillBeSent") { + if (params.request.url == "https://www.github.com") + win.webContents.debugger.detach(); + } +}) + +win.webContents.debugger.sendCommand("Network.enable"); +``` + +#### `webContents.debugger.attach([protocolVersion])` + +* `protocolVersion` String (optional) - 요쳥할 디버깅 프로토콜의 버전. + +`webContents`에 디버거를 부착합니다. + +#### `webContents.debugger.isAttached()` + +디버거가 `webContents`에 부착되어 있는지 여부를 반환합니다. + +#### `webContents.debugger.detach()` + +`webContents`로부터 디버거를 분리시킵니다. + +#### `webContents.debugger.sendCommand(method[, commandParams, callback])` + +* `method` String - 메서드 이름, 반드시 원격 디버깅 프로토콜에 의해 정의된 메서드중 + 하나가 됩니다. +* `commandParams` Object (optional) - 요청 매개변수를 표현한 JSON 객체. +* `callback` Function (optional) - 응답 + * `error` Object - 커맨드의 실패를 표시하는 에러 메시지. + * `result` Object - 원격 디버깅 프로토콜에서 커맨드 설명의 'returns' 속성에 의해 + 정의된 응답 + +지정한 커맨드를 디버깅 대상에게 전송합니다. + +#### Event: 'detach' + +* `event` Event +* `reason` String - 디버거 분리 사유. + +디버깅 세션이 종료될 때 발생하는 이벤트입니다. `webContents`가 닫히거나 개발자 도구가 +부착된 `webContents`에 대해 호출될 때 발생합니다. + +#### Event: 'message' + +* `event` Event +* `method` String - 메서드 이름. +* `params` Object - 원격 디버깅 프로토콜의 'parameters' 속성에서 정의된 이벤트 + 매개변수 + +디버깅 타겟이 관련 이벤트를 발생시킬 때 마다 발생하는 이벤트입니다. + +[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol diff --git a/docs-translations/ko-KR/api/web-view-tag.md b/docs-translations/ko-KR/api/web-view-tag.md index a6a84a1ceee..69fa0c5a767 100644 --- a/docs-translations/ko-KR/api/web-view-tag.md +++ b/docs-translations/ko-KR/api/web-view-tag.md @@ -151,6 +151,16 @@ API를 사용할 수 있습니다. 이를 지정하면 내부에서 로우레벨 "on"으로 지정하면 페이지에서 새로운 창을 열 수 있도록 허용합니다. +### `blinkfeatures` + +```html + +``` + +활성화할 blink 기능을 지정한 `,`로 구분된 문자열의 리스트입니다. 지원하는 기능 문자열의 +전체 목록은 [setFeatureEnabledFromString][blink-feature-string] 함수에서 찾을 수 +있습니다. + ## Methods `webview` 태그는 다음과 같은 메서드를 가지고 있습니다: @@ -165,6 +175,17 @@ webview.addEventListener("dom-ready", function() { }); ``` +### `.loadURL(url[, options])` + +* `url` URL +* `options` Object (optional) + * `httpReferrer` String - HTTP 레퍼러 url. + * `userAgent` String - 요청을 시작한 유저 에이전트. + * `extraHeaders` String - "\n"로 구분된 Extra 헤더들. + +Webview에 웹 페이지 `url`을 로드합니다. `url`은 `http://`, `file://`과 같은 +프로토콜 접두사를 가지고 있어야 합니다. + ### `.getURL()` 페이지의 URL을 반환합니다. @@ -251,12 +272,14 @@ webview.addEventListener("dom-ready", function() { 페이지에 CSS를 삽입합니다. -### `.executeJavaScript(code[, userGesture])` +### `.executeJavaScript(code[, userGesture, callback])` * `code` String * `userGesture` Boolean +* `callback` Function (optional) - 스크립트의 실행이 완료되면 호출됩니다. + * `result` -페이지에서 자바스크립트 `code`를 실행합니다. +페이지에서 자바스크립트 코드를 실행합니다. 만약 `userGesture`가 `true`로 설정되어 있으면 페이지에 유저 제스쳐 컨텍스트를 만듭니다. 이 옵션을 활성화 시키면 `requestFullScreen`와 같은 HTML API에서 유저의 승인을 @@ -349,7 +372,7 @@ Service worker에 대한 개발자 도구를 엽니다. ### `webContents.findInPage(text[, options])` * `text` String - 찾을 컨텐츠, 반드시 공백이 아니여야 합니다. -* `options` Object (Optional) +* `options` Object (optional) * `forward` Boolean - 앞에서부터 검색할지 뒤에서부터 검색할지 여부입니다. 기본값은 `true`입니다. * `findNext` Boolean - 작업을 계속 처리할지 첫 요청만 처리할지 여부입니다. 기본값은 @@ -406,6 +429,10 @@ Webview 페이지를 PDF 형식으로 인쇄합니다. `event` 객체에 대해 자세히 알아보려면 [webContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent)를 참고하세요. +### `.getWebContents()` + +이 `webview`에 해당하는 [WebContents](web-contents.md)를 반환합니다. + ## DOM 이벤트 `webview` 태그는 다음과 같은 DOM 이벤트를 가지고 있습니다: @@ -536,8 +563,9 @@ Returns: * `result` Object * `requestId` Integer * `finalUpdate` Boolean - 더 많은 응답이 따르는 경우를 표시합니다. - * `matches` Integer (Optional) - 일치하는 개수. - * `selectionArea` Object (Optional) - 첫 일치 부위의 좌표. + * `activeMatchOrdinal` Integer (optional) - 활성화 일치의 위치. + * `matches` Integer (optional) - 일치하는 개수. + * `selectionArea` Object (optional) - 첫 일치 부위의 좌표. [`webContents.findInPage`](web-contents.md#webcontentsfindinpage) 요청의 결과를 사용할 수 있을 때 발생하는 이벤트입니다. @@ -701,3 +729,5 @@ WebContents가 파괴될 때 발생하는 이벤트입니다. ### Event: 'devtools-focused' 개발자 도구가 포커스되거나 열렸을 때 발생하는 이벤트입니다. + +[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 diff --git a/docs-translations/ko-KR/development/build-instructions-linux.md b/docs-translations/ko-KR/development/build-instructions-linux.md index c4f161fa56a..377a7977a85 100644 --- a/docs-translations/ko-KR/development/build-instructions-linux.md +++ b/docs-translations/ko-KR/development/build-instructions-linux.md @@ -21,7 +21,7 @@ Ubuntu를 사용하고 있다면 다음과 같이 라이브러리를 설치해 $ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ libnotify-dev libgnome-keyring-dev libgconf2-dev \ libasound2-dev libcap-dev libcups2-dev libxtst-dev \ - libxss1 libnss3-dev gcc-multilib g++-multilib + libxss1 libnss3-dev gcc-multilib g++-multilib curl ``` Fedora를 사용하고 있다면 다음과 같이 라이브러리를 설치해야 합니다: diff --git a/docs-translations/ko-KR/development/build-instructions-osx.md b/docs-translations/ko-KR/development/build-instructions-osx.md index 9e2a76d3104..142cdaca4df 100644 --- a/docs-translations/ko-KR/development/build-instructions-osx.md +++ b/docs-translations/ko-KR/development/build-instructions-osx.md @@ -21,8 +21,8 @@ $ git clone https://github.com/atom/electron.git ## 부트 스트랩 부트스트랩 스크립트는 필수적인 빌드 종속성 라이브러리들을 모두 다운로드하고 프로젝트 -파일을 생성합니다. 참고로 Electron은 `ninja`를 빌드 툴체인으로 사용하므로 Xcode -프로젝트는 생성되지 않습니다. +파일을 생성합니다. 참고로 Electron은 [ninja](https://ninja-build.org/)를 빌드 +툴체인으로 사용하므로 Xcode 프로젝트는 생성되지 않습니다. ```bash $ cd electron diff --git a/docs-translations/ko-KR/development/build-system-overview.md b/docs-translations/ko-KR/development/build-system-overview.md index 79ead32718d..044a2da0ae2 100644 --- a/docs-translations/ko-KR/development/build-system-overview.md +++ b/docs-translations/ko-KR/development/build-system-overview.md @@ -1,7 +1,8 @@ # 빌드 시스템 개요 -Electron은 프로젝트 생성을 위해 `gyp`를 사용하며 `ninja`를 이용하여 빌드합니다. -프로젝트 설정은 `.gyp` 와 `.gypi` 파일에서 볼 수 있습니다. +Electron은 프로젝트 생성을 위해 [gyp](https://gyp.gsrc.io/)를 사용하며 +[ninja](https://ninja-build.org/)를 이용하여 빌드합니다. 프로젝트 설정은 `.gyp` 와 +`.gypi` 파일에서 볼 수 있습니다. ## gyp 파일 diff --git a/docs-translations/ko-KR/development/coding-style.md b/docs-translations/ko-KR/development/coding-style.md index 21ee03fd0a1..dd64e724517 100644 --- a/docs-translations/ko-KR/development/coding-style.md +++ b/docs-translations/ko-KR/development/coding-style.md @@ -2,6 +2,9 @@ 이 가이드는 Electron의 코딩 스타일에 관해 설명합니다. +`npm run lint`를 실행하여 `cpplint`와 `eslint`를 통해 어떤 코딩 스타일 이슈를 확인할 +수 있습니다. + ## C++과 Python C++과 Python 스크립트는 Chromium의 @@ -18,17 +21,24 @@ C++ 코드는 많은 Chromium의 추상화와 타입을 사용합니다. 따라 자동으로 메모리에서 할당을 해제합니다. 스마트 포인터와 같습니다) 그리고 로깅 메커니즘 등을 언급하고 있습니다. -## CoffeeScript - -CoffeeScript의 경우 GitHub의 -[스타일 가이드](https://github.com/styleguide/javascript)를 기본으로 따릅니다. -그리고 추가로 다음 규칙을 따릅니다: +## JavaScript +* 하드 탭(hard tabs) 대신 소프트 탭(2 spaces) 들여쓰기를 사용합니다. +* 항상 구문의 끝은 `;`으로 마쳐야 합니다. * Google의 코딩 스타일에도 맞추기 위해 파일의 끝에는 **절대** 개행을 삽입해선 안됩니다. * 파일 이름의 공백은 `_`대신에 `-`을 사용하여야 합니다. 예를 들어 -`file_name.coffee`를 `file-name.coffee`로 고쳐야합니다. 왜냐하면 +`file_name.js`를 `file-name.js`로 고쳐야합니다. 왜냐하면 [github/atom](https://github.com/github/atom)에서 사용되는 모듈의 이름은 보통 -`module-name` 형식이기 때문입니다. 이 규칙은 '.coffee' 파일에만 적용됩니다. +`module-name` 형식이기 때문입니다. 이 규칙은 '.js' 파일에만 적용됩니다. +* 적절한 곳에 새로운 ES6/ES2015 문법을 사용해도 됩니다. + * [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) + 는 requires와 다른 상수에 사용합니다 + * [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) + 은 변수를 정의할 때 사용합니다 + * [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) + 는 `function () { }` 표현 대신에 사용합니다 + * [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) + 는 `+`로 문자열을 합치는 대신 사용합니다. ## API 이름 diff --git a/docs-translations/ko-KR/development/source-code-directory-structure.md b/docs-translations/ko-KR/development/source-code-directory-structure.md index e0e172d18b4..7450b3b3e9e 100644 --- a/docs-translations/ko-KR/development/source-code-directory-structure.md +++ b/docs-translations/ko-KR/development/source-code-directory-structure.md @@ -10,33 +10,34 @@ Electron의 소스 코드는 몇 개의 파트로 분리되어 있습니다. 그 ``` Electron -├──atom - Electron의 소스코드. -| ├── app - 시스템 엔트리 코드. -| ├── browser - 주 윈도우를 포함한 프론트엔드, UI, 그리고 메인 프로세스에 관련된 것과 -| | 랜더러와 웹 페이지 관리 관련 코드. -| | ├── lib - 메인 프로세스 초기화 코드의 자바스크립트 파트. -| | ├── ui - 크로스 플랫폼에 대응하는 UI 구현. -| | | ├── cocoa - 코코아 특정 소스 코드. -| | | ├── gtk - GTK+ 특정 소스 코드. -| | | └── win - Windows GUI 특정 소스 코드. -| | ├── default_app - 어플리케이션이 제공되지 않으면 기본으로 사용되는 페이지. -| | ├── api - 메인 프로세스 API의 구현. -| | | └── lib - API 구현의 자바스크립트 파트. -| | ├── net - 네트워크 관련 코드. -| | ├── mac - Mac 특정 Objective-C 소스 코드. -| | └── resources - 아이콘들, 플랫폼 종속성 파일들, 기타 등등. -| ├── renderer - 랜더러 프로세스에서 작동하는 코드들. -| | ├── lib - 랜더러 프로세스 초기화 코드의 자바스크립트 파트. -| | └── api - 랜더러 프로세스 API들의 구현. -| | └── lib - API 구현의 자바스크립트 파트. -| └── common - 메인 프로세스와 랜더러 프로세스에서 공유하는 코드. 유틸리티 함수와 -| node 메시지 루프를 Chromium의 메시지 루프에 통합시킨 코드도 포함. -| ├── lib - 공통 자바스크립트 초기화 코드. -| └── api - 공통 API들의 구현, Electron의 빌트인 모듈 기초 코드들. -| └── lib - API 구현의 자바스크립트 파트. -├── chromium_src - 복사된 Chromium 소스 코드. +├── atom - C++ 소스 코드. +| ├── app - 시스템 엔트리 코드. +| ├── browser - 주 윈도우를 포함한 프론트엔드, UI, 그리고 메인 프로세스에 관련된 +| | 코드와 랜더러 및 웹 페이지 관리 관련 코드. +| | ├── ui - 서로 다른 플랫폼에 대한 UI 관련 구현 코드. +| | | ├── cocoa - Cocoa 특정 소스 코드. +| | | ├── gtk - GTK+ 특정 소스 코드. +| | | └── win - Windows GUI 특정 소스 코드. +| | ├── api - 메인 프로세스 API의 구현. +| | ├── net - 네트워킹 관련 코드. +| | ├── mac - Mac 특정 Objective-C 소스 코드. +| | └── resources - 아이콘들, 플랫폼 종속성 파일들, 기타 등등.. +| ├── renderer - 랜더러 프로세스에서 작동하는 코드. +| | └── api - 랜더러 프로세스 API의 구현. +| └── common - 메인과 랜더러 프로세스에서 모두 사용하는 코드, 몇가지 유틸리티 +| 함수들이 포함되어 있고 node의 메시지 루프와 Chromium의 메시지 루프를 통합. +| └── api - 공통 API 구현들, 기초 Electron 빌트-인 모듈들. +├── chromium_src - Chromium에서 복사하여 가져온 소스코드. +├── default_app - Electron에 앱이 제공되지 않았을 때 보여지는 기본 페이지. ├── docs - 참조 문서. -├── spec - 자동화된 테스트. +├── lib - JavaScript 소스 코드. +| ├── browser - Javascript 메인 프로세스 초기화 코드. +| | └── api - Javascript API 구현 코드. +| ├── common - 메인과 랜더러 프로세스에서 모두 사용하는 JavaScript +| | └── api - Javascript API 구현 코드. +| └── renderer - Javascript 랜더러 프로세스 초기화 코드. +| └── api - Javascript API 구현 코드. +├── spec - 자동화 테스트. ├── atom.gyp - Electron의 빌드 규칙. └── common.gypi - 컴파일러 설정 및 `node` 와 `breakpad` 등의 구성요소를 위한 빌드 규칙. diff --git a/docs-translations/ko-KR/faq/electron-faq.md b/docs-translations/ko-KR/faq/electron-faq.md index 54ecf64e435..40d10f293f4 100644 --- a/docs-translations/ko-KR/faq/electron-faq.md +++ b/docs-translations/ko-KR/faq/electron-faq.md @@ -18,6 +18,34 @@ Node.js의 새로운 기능은 보통 V8 업그레이드에서 가져옵니다. 브라우저에 탑재된 V8을 사용하고 있습니다. 눈부신 새로운 Node.js 버전의 자바스크립트 기능은 보통 이미 Electron에 있습니다. +## 어떻게 웹 페이지 간에 데이터를 공유할 수 있나요? + +두 웹페이지 간에 (랜더러 프로세스) 데이터를 공유하려면 간단히 이미 모든 브라우저에서 +사용할 수 있는 HTML5 API들을 사용하면 됩니다. 가장 좋은 후보는 +[Storage API][storage], [`localStorage`][local-storage], +[`sessionStorage`][session-storage], 그리고 [IndexedDB][indexed-db]가 있습니다. + +또는 Electron에서만 사용할 수 있는 IPC 시스템을 사용하여 메인 프로세스의 global +변수에 데이터를 저장한 후 다음과 같이 랜더러 프로세스에서 `remote` 모듈을 사용하여 +접근할 수 있습니다: + +```javascript +// 메인 프로세스에서 +global.sharedObject = { + someProperty: 'default value' +}; +``` + +```javascript +// 첫 번째 페이지에서 +require('remote').getGlobal('sharedObject').someProperty = 'new value'; +``` + +```javascript +// 두 번째 페이지에서 +console.log(require('remote').getGlobal('sharedObject').someProperty); +``` + ## 제작한 어플리케이션의 윈도우/트레이가 몇 분 후에나 나타납니다. 이러한 문제가 발생하는 이유는 보통 윈도우/트레이를 담은 변수에 가비지 컬렉션이 작동해서 @@ -77,5 +105,62 @@ delete window.module; ``` +## `require('electron').xxx`가 undefined를 반환합니다. + +Electron의 빌트인 모듈을 사용할 때, 다음과 같은 오류가 발생할 수 있습니다: + +``` +> require('electron').webFrame.setZoomFactor(1.0); +Uncaught TypeError: Cannot read property 'setZoomLevel' of undefined +``` + +이러한 문제가 발생하는 이유는 [npm의 `electron` 모듈][electron-module]이 로컬 또는 +전역 중 한 곳에 설치되어, Electron의 빌트인 모듈을 덮어씌우는 바람에 빌트인 모듈을 +사용할 수 없기 때문입니다. + +올바른 빌트인 모듈을 사용하고 있는지 확인하고 싶다면, `electron` 모듈의 경로를 +출력하는 방법이 있습니다: + +```javascript +console.log(require.resolve('electron')); +``` + +그리고 다음과 같은 경로를 가지는지 점검하면 됩니다: + +``` +"/path/to/Electron.app/Contents/Resources/atom.asar/renderer/api/lib/exports/electron.js" +``` + +하지만 `node_modules/electron/index.js`와 같은 경로로 되어있을 경우, `electron` +모듈을 지우거나 이름을 바꿔야만 합니다. + +```bash +npm uninstall electron +npm uninstall -g electron +``` + +그런데 여전히 빌트인 모듈이 계속해서 문제를 발생시키는 경우, 아마 모듈을 잘못 사용하고 +있을 가능성이 큽니다. 예를 들면 `electron.app`은 메인 프로세스에서만 사용할 수 있는 +모듈이며, 반면 `electron.webFrame` 모듈은 랜더러 프로세스에서만 사용할 수 있는 +모듈입니다. + +## 왜 저의 앱 배경이 투명하죠? + +Electron `0.37.3` 부터, 기본 클라이언트 배경색이 `투명색`으로 변경되었습니다. +간단히 HTML에서 배경색을 지정합니다: + +```html + +``` + [memory-management]: https://developer.mozilla.org/ko/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/ko-KR/tutorial/application-distribution.md b/docs-translations/ko-KR/tutorial/application-distribution.md index 34eb8cee71f..7072d7c4d95 100644 --- a/docs-translations/ko-KR/tutorial/application-distribution.md +++ b/docs-translations/ko-KR/tutorial/application-distribution.md @@ -59,9 +59,8 @@ electron/resources/ ### Windows -`electron.exe`을 원하는 이름으로 변경할 수 있습니다. -그리고 [rcedit](https://github.com/atom/rcedit) 또는 -[ResEdit](http://www.resedit.net)를 사용하여 아이콘을 변경할 수 있습니다. +[rcedit](https://github.com/atom/rcedit)를 통해 `electron.exe`을 원하는 이름으로 +변경할 수 있고, 또한 아이콘과 기타 정보도 변경할 수 있습니다. ### OS X diff --git a/docs-translations/ko-KR/tutorial/debugging-main-process.md b/docs-translations/ko-KR/tutorial/debugging-main-process.md index 0b69c0a165a..43b24c79b3a 100644 --- a/docs-translations/ko-KR/tutorial/debugging-main-process.md +++ b/docs-translations/ko-KR/tutorial/debugging-main-process.md @@ -40,11 +40,11 @@ $ npm install git+https://git@github.com/enlight/node-pre-gyp.git#detect-electro ### 4. Electron용 `node-inspector` `v8` 모듈을 재 컴파일 (target을 사용하는 Electron의 버전에 맞춰 변경) ```bash -$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall -$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.11 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.11 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall ``` -또한 [네이티브 모듈을 사용하는 방법](how-to-install-native-modules) 문서도 참고해보세요. +또한 [네이티브 모듈을 사용하는 방법][how-to-install-native-modules] 문서도 참고해보세요. ### 5. Electron 디버그 모드 활성화 diff --git a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md index 91fc59a8f2e..ddeadc04e91 100644 --- a/docs-translations/ko-KR/tutorial/desktop-environment-integration.md +++ b/docs-translations/ko-KR/tutorial/desktop-environment-integration.md @@ -14,6 +14,8 @@ Windows, Linux, OS X 운영체제 모두 기본적으로 어플리케이션에 통해 개발자가 편리하게 데스크톱 알림을 사용할 수 있는 기능을 제공합니다. 데스크톱 알림은 운영체제의 네이티브 알림 API를 사용하여 표시합니다. +__참고:__ 이 API는 HTML5 API이기 때문에 랜더러 프로세스에서만 사용할 수 있습니다. + ```javascript var myNotification = new Notification('Title', { body: 'Lorem Ipsum Dolor Sit Amet' @@ -239,22 +241,20 @@ __Audacious의 런처 숏컷:__ ![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) -## Taskbar progress 기능 (Windows & Unity) +## 작업 표시줄 안의 프로그래스 바 (Windows, OS X, Unity) -Windows에선 태스크바의 어플리케이션 버튼에 progress bar를 추가할 수 있습니다. +Windows에선 작업 표시줄의 어플리케이션 버튼에 프로그래스 바를 추가할 수 있습니다. 이 기능은 사용자가 어플리케이션의 창을 열지 않고도 어플리케이션의 작업의 상태 정보를 시각적으로 보여줄 수 있도록 해줍니다. -또한 Unity DE도 런처에 progress bar를 부착할 수 있습니다. +OS X에선 프로그래스바가 dock 아이콘의 일부에 표시됩니다. -__태스크바 버튼의 progress bar:__ +또한 Unity DE도 런처에 프로그래스 바를 부착할 수 있습니다. + +__작업 표시줄 버튼의 프로그래스 바:__ ![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) -__Unity 런처의 progress bar:__ - -![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) - 이 기능은 [BrowserWindow.setProgressBar][setprogressbar] API를 사용하여 구현할 수 있습니다: @@ -263,6 +263,33 @@ var window = new BrowserWindow({...}); window.setProgressBar(0.5); ``` +## 작업 표시줄의 아이콘 오버레이 (Windows) + +Windows에선 작업 표시줄 버튼에 어플리케이션의 상태를 표시하는 작은 오버레이를 사용할 +수 있습니다. MSDN에서 인용하자면 (영문): + +> Icon overlays serve as a contextual notification of status, and are intended +> to negate the need for a separate notification area status icon to communicate +> that information to the user. For instance, the new mail status in Microsoft +> Outlook, currently shown in the notification area, can now be indicated +> through an overlay on the taskbar button. Again, you must decide during your +> development cycle which method is best for your application. Overlay icons are +> intended to supply important, long-standing status or notifications such as +> network status, messenger status, or new mail. The user should not be +> presented with constantly changing overlays or animations. + +__작업 표시줄 버튼 위의 오버레이:__ + +![작업 표시줄 버튼 위의 오버레이](https://i-msdn.sec.s-msft.com/dynimg/IC420441.png) + +윈도우에 오버레이 아이콘을 설정하려면 [BrowserWindow.setOverlayIcon][setoverlayicon] +API를 사용할 수 있습니다: + +```javascript +var window = new BrowserWindow({...}); +window.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); +``` + ## 대표 파일 제시 (OS X) OS X는 창에서 대표 파일을 설정할 수 있습니다. 타이틀바에서 파일 아이콘이 있고, 사용자가 @@ -283,13 +310,16 @@ window.setRepresentedFilename('/etc/passwd'); window.setDocumentEdited(true); ``` -[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath -[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments -[setusertaskstasks]: ../api/app.md#appsetusertaskstasks -[setprogressbar]: ../api/browser-window.md#browserwindowsetprogressbarprogress -[setrepresentedfilename]: ../api/browser-window.md#browserwindowsetrepresentedfilenamefilename -[setdocumentedited]: ../api/browser-window.md#browserwindowsetdocumenteditededited +[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows +[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-os-x-windows +[setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows +[setprogressbar]: ../api/browser-window.md#winsetprogressbarprogress +[setoverlayicon]: ../api/browser-window.md#winsetoverlayiconoverlay-description-windows-7 +[setrepresentedfilename]: ../api/browser-window.md#winsetrepresentedfilenamefilename-os-x +[setdocumentedited]: ../api/browser-window.md#winsetdocumenteditededited-os-x [app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx [unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher -[setthumbarbuttons]: ../api/browser-window.md#browserwindowsetthumbarbuttonsbuttons -[trayballoon]: ../api/tray.md#traydisplayballoonoptions-windows +[setthumbarbuttons]: ../api/browser-window.md#winsetthumbarbuttonsbuttons-windows-7 +[tray-balloon]: ../api/tray.md#traydisplayballoonoptions-windows +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[notification-spec]: https://developer.gnome.org/notification-spec/ diff --git a/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md b/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md index d316d16da83..c319f344b63 100644 --- a/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md +++ b/docs-translations/ko-KR/tutorial/mac-app-store-submission-guide.md @@ -4,6 +4,10 @@ Electron은 v0.34.0 버전부터 앱 패키지를 Mac App Store(MAS)에 제출 되었습니다. 이 가이드는 어플리케이션을 앱 스토어에 등록하는 방법과 빌드의 한계에 대한 설명을 제공합니다. +__참고:__ v0.36.0 버전부터 어플리케이션이 샌드박스화 된 상태로 실행되면 GPU 작동을 +방지하는 버그가 있었습니다. 따라서 이 버그가 고쳐지기 전까진 v0.35.x 버전을 사용하는 +것을 권장합니다. 이 버그에 관한 자세한 사항은 [issue #3871][issue-3871]를 참고하세요. + __참고:__ Mac App Store에 어플리케이션을 등록하려면 [Apple Developer Program][developer-program]에 등록되어 있어야 하며 비용이 발생할 수 있습니다. @@ -73,13 +77,18 @@ INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Libraries/libnode.dylib" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Electron Framework" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A" codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" -codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +if [ -d "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" ]; then + # non-MAS 빌드 서명 + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Mantle.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/ReactiveCocoa.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" +fi +codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" + productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" ``` @@ -98,8 +107,8 @@ productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RES 모든 어플리케이션 샌드박스에 대한 요구 사항을 충족시키기 위해, 다음 모듈들은 MAS 빌드에서 비활성화됩니다: -* `crash-reporter` -* `auto-updater` +* `crashReporter` +* `autoUpdater` 그리고 다음 동작으로 대체됩니다: @@ -111,7 +120,42 @@ productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RES 엄격하게 제한되어 있습니다. 자세한 내용은 [App Sandboxing][app-sandboxing] 문서를 참고하세요. -**역주:** [Mac 앱 배포 가이드 공식 문서](https://developer.apple.com/osx/distribution/kr/) +## Electron에서 사용하는 암호화 알고리즘 + +국가와 살고 있는 지역에 따라, 맥 앱스토어는 제출한 어플리케이션에서 사용하는 암호화 +알고리즘의 문서를 요구할 수 있습니다. 심지어 U.S. Encryption Registration (ERN)의 +승인 사본을 제출하라고 할 수도 있습니다. + +Electron은 다음과 같은 암호화 알고리즘을 사용합니다: + +* AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt) +* HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf) +* ECDSA - ANS X9.62–2005 +* ECDH - ANS X9.63–2001 +* HKDF - [NIST SP 800-56C](http://csrc.nist.gov/publications/nistpubs/800-56C/SP-800-56C.pdf) +* PBKDF2 - [RFC 2898](https://tools.ietf.org/html/rfc2898) +* RSA - [RFC 3447](http://www.ietf.org/rfc/rfc3447) +* SHA - [FIPS 180-4](http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf) +* Blowfish - https://www.schneier.com/cryptography/blowfish/ +* CAST - [RFC 2144](https://tools.ietf.org/html/rfc2144), [RFC 2612](https://tools.ietf.org/html/rfc2612) +* DES - [FIPS 46-3](http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf) +* DH - [RFC 2631](https://tools.ietf.org/html/rfc2631) +* DSA - [ANSI X9.30](http://webstore.ansi.org/RecordDetail.aspx?sku=ANSI+X9.30-1%3A1997) +* EC - [SEC 1](http://www.secg.org/sec1-v2.pdf) +* IDEA - "On the Design and Security of Block Ciphers" book by X. Lai +* MD2 - [RFC 1319](http://tools.ietf.org/html/rfc1319) +* MD4 - [RFC 6150](https://tools.ietf.org/html/rfc6150) +* MD5 - [RFC 1321](https://tools.ietf.org/html/rfc1321) +* MDC2 - [ISO/IEC 10118-2](https://www.openssl.org/docs/manmaster/crypto/mdc2.html) +* RC2 - [RFC 2268](https://tools.ietf.org/html/rfc2268) +* RC4 - [RFC 4345](https://tools.ietf.org/html/rfc4345) +* RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf +* RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) + +ERN의 승인을 얻는 방법은, 다음 글을 참고하는 것이 좋습니다: +[어플리케이션이 암호화를 사용할 때, 합법적으로 Apple의 앱 스토어에 제출하는 방법 (또는 ERN의 승인을 얻는 방법)][ern-tutorial]. + +**역주:** [Mac 앱 배포 가이드 공식 한국어 문서](https://developer.apple.com/osx/distribution/kr/) [developer-program]: https://developer.apple.com/support/compare-memberships/ [submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html @@ -120,3 +164,5 @@ productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RES [create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html [submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html [app-sandboxing]: https://developer.apple.com/app-sandboxing/ +[issue-3871]: https://github.com/atom/electron/issues/3871 +[ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ diff --git a/docs-translations/ko-KR/tutorial/quick-start.md b/docs-translations/ko-KR/tutorial/quick-start.md index 708a48080ff..82d359e0de5 100644 --- a/docs-translations/ko-KR/tutorial/quick-start.md +++ b/docs-translations/ko-KR/tutorial/quick-start.md @@ -12,7 +12,7 @@ Electron은 웹 페이지의 GUI를 사용합니다. 쉽게 말해 Electron은 ### 메인 프로세스 -Electron은 실행될 때 __메인 프로세스__로 불리는 `package.json`의 `main` 스크립트를 +Electron은 실행될 때 __메인 프로세스__ 로 불리는 `package.json`의 `main` 스크립트를 호출합니다. 이 스크립트는 메인 프로세스에서 작동합니다. GUI 컴포넌트를 조작하거나 웹 페이지 창을 생성할 수 있습니다. @@ -43,6 +43,7 @@ API를 사용하여 low-level 수준으로 운영체제와 상호작용할 수 Electron에는 메인 프로세스와 랜더러 프로세스 사이에 통신을 할 수 있도록 [ipc](../api/ipc-renderer.md) 모듈을 제공하고 있습니다. 또는 [remote](../api/remote.md) 모듈을 사용하여 RPC 스타일로 통신할 수도 있습니다. +또한 FAQ에서 [다양한 객체를 공유하는 방법](share-data)도 소개하고 있습니다. ## 첫번째 Electron 앱 만들기 @@ -211,3 +212,5 @@ $ cd electron-quick-start # 어플리케이션의 종속성 모듈을 설치한 후 실행합니다 $ npm install && npm start ``` + +[share-data]: ../faq/electron-faq.md#어떻게-웹-페이지-간에-데이터를-공유할-수-있나요 diff --git a/docs-translations/ko-KR/tutorial/supported-platforms.md b/docs-translations/ko-KR/tutorial/supported-platforms.md index 3b3a00d7215..656d31308e0 100644 --- a/docs-translations/ko-KR/tutorial/supported-platforms.md +++ b/docs-translations/ko-KR/tutorial/supported-platforms.md @@ -4,7 +4,7 @@ Electron에선 다음과 같은 플랫폼을 지원합니다: ### OS X -OS X는 64비트 바이너리만 제공됩니다. 그리고 최소 OS X 지원 버전은 10.8입니다. +OS X는 64비트 바이너리만 제공됩니다. 그리고 최소 OS X 지원 버전은 10.9입니다. ### Windows diff --git a/docs-translations/ko-KR/tutorial/testing-on-headless-ci.md b/docs-translations/ko-KR/tutorial/testing-on-headless-ci.md new file mode 100644 index 00000000000..be88be1cec6 --- /dev/null +++ b/docs-translations/ko-KR/tutorial/testing-on-headless-ci.md @@ -0,0 +1,58 @@ +# Headless CI 시스템에서 테스팅하기 (Travis, Jenkins) (Travis CI, Jenkins) + +Chromium을 기반으로 한 Electron은 작업을 위해 디스플레이 드라이버가 필요합니다. +만약 Chromium이 디스플레이 드라이버를 찾기 못한다면, Electron은 그대로 실행에 +실패할 것입니다. 따라서 실행하는 방법에 관계없이 모든 테스트를 실행하지 못하게 됩니다. +Electron 기반 어플리케이션을 Travis, Circle, Jenkins 또는 유사한 시스템에서 테스팅을 +진행하려면 약간의 설정이 필요합니다. 요점만 말하자면, 가상 디스플레이 드라이버가 +필요합니다. + +## 가상 디스플레이 드라이버 설정 + +먼저, [Xvfb](https://en.wikipedia.org/wiki/Xvfb)를 설치합니다. 이것은 X11 +디스플레이 서버 프로토콜의 구현이며 모든 그래픽 작업을 스크린 출력없이 인-메모리에서 +수행하는 가상 프레임버퍼입니다. 정확히 우리가 필요로 하는 것입니다. + +그리고, 가상 xvfb 스크린을 생성하고 DISPLAY라고 불리우는 환경 변수를 지정합니다. +Electron의 Chromium은 자동적으로 `$DISPLAY` 변수를 찾습니다. 따라서 앱의 추가적인 +다른 설정이 필요하지 않습니다. 이러한 작업은 Paul Betts의 +[xfvb-maybe](https://github.com/paulcbetts/xvfb-maybe)를 통해 자동화 할 수 +있습니다: `xfvb-maybe`를 테스트 커맨드 앞에 추가하고 현재 시스템에서 요구하면 +이 작은 툴이 자동적으로 xfvb를 설정합니다. Windows와 Mac OS X에선 간단히 아무 작업도 +하지 않습니다. + +``` +## Windows와 OS X에선, 그저 electron-mocha를 호출합니다 +## Linux에선, 현재 headless 환경에 있는 경우 +## xvfb-run electron-mocha ./test/*.js와 같습니다 +xvfb-maybe electron-mocha ./test/*.js +``` + +### Travis CI + +Travis에선, `.travis.yml`이 대충 다음과 같이 되어야 합니다: + +``` +addons: + apt: + packages: + - xvfb + +install: + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & +``` + +### Jenkins + +Jenkins는 [Xfvb 플러그인이 존재합니다](https://wiki.jenkins-ci.org/display/JENKINS/Xvfb+Plugin). + +### Circle CI + +Circle CI는 멋지게도 이미 xvfb와 `$DISPLY` 변수가 준비되어 있습니다. 따라서 +[추가적인 설정이 필요하지](https://circleci.com/docs/environment#browsers) 않습니다. + +### AppVeyor + +AppVeyor는 Windows에서 작동하기 때문에 Selenium, Chromium, Electron과 그 비슷한 +툴들을 복잡한 과정 없이 모두 지원합니다. - 설정이 필요하지 않습니다. 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 d0684e0cfe1..6cc5472bb12 100644 --- a/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/ko-KR/tutorial/using-selenium-and-webdriver.md @@ -48,7 +48,7 @@ var driver = new webdriver.Builder() .withCapabilities({ chromeOptions: { // 여기에 사용중인 Electron 바이너리의 경로를 지정하세요. - binary: '/Path-to-Your-App.app/Contents/MacOS/Atom', + binary: '/Path-to-Your-App.app/Contents/MacOS/Electron', } }) .forBrowser('electron') diff --git a/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md b/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md index d729c0efc08..b9fec886769 100644 --- a/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md +++ b/docs-translations/ko-KR/tutorial/using-widevine-cdm-plugin.md @@ -8,6 +8,10 @@ Electron은 라이센스상의 문제로 Widevine CDM 플러그인을 직접 제 따라서 플러그인을 얻으려면 먼저 사용할 Electron 빌드의 아키텍쳐와 버전에 맞춰 공식 Chrome 브라우저를 설치해야 합니다. +__참고:__ Chrome 브라우저의 메이저 버전은 Electron에서 사용하는 Chrome 버전과 +같습니다, 만약 그렇지 않다면 `navigator.plugins`가 로드됐더라도 정상적으로 작동하지 +않습니다. + ### Windows & OS X Chrome 브라우저에서 `chrome://components/`를 열고 `WidevineCdm`을 찾은 후 확실히 diff --git a/docs-translations/pt-BR/README.md b/docs-translations/pt-BR/README.md index f458e51da8f..05fedfeb438 100644 --- a/docs-translations/pt-BR/README.md +++ b/docs-translations/pt-BR/README.md @@ -6,9 +6,14 @@ documentação na lista de [versões disponíveis](http://electron.atom.io/docs/ ou se você estiver usando a interface do GitHub, abra o *dropdown* "Switch branches/tags" e selecione a *tag* que corresponde à sua versão. +## FAQ + +Existem muitas perguntas comuns que são feitas, verifique antes de criar uma issue. +* [Electron FAQ](../../docs/faq/electron-faq.md) + ## Guias -* [Platformas Suportadas](../../tutorial/supported-platforms.md) +* [Plataformas Suportadas](tutorial/supported-platforms.md) * [Distribuição de Aplicações](tutorial/application-distribution.md) * [Guia de Submissão da Mac App Store](../../tutorial/mac-app-store-submission-guide.md) * [Empacotamento da Aplicação](tutorial/application-packaging.md) @@ -17,6 +22,7 @@ selecione a *tag* que corresponde à sua versão. * [Usando Selenium e WebDriver](../../docs/tutorial/using-selenium-and-webdriver.md) * [Extensão DevTools](../../docs/tutorial/devtools-extension.md) * [Usando o Plugin Pepper Flash](tutorial/using-pepper-flash-plugin.md) +* [Usando o Plugin Widevine CDM](../../tutorial/using-widevine-cdm-plugin.md) ## Tutoriais @@ -58,6 +64,7 @@ selecione a *tag* que corresponde à sua versão. ### Módulos para o Processo Renderizador: +* [DesktopCapturer](../../docs/api/desktop-capturer) * [ipcRenderer](../../docs/api/ipc-renderer.md) * [remote](../../docs/api/remote.md) * [webFrame](../../docs/api/web-frame.md) diff --git a/docs-translations/pt-BR/api/window-open.md b/docs-translations/pt-BR/api/window-open.md new file mode 100644 index 00000000000..eb2b31cd0e4 --- /dev/null +++ b/docs-translations/pt-BR/api/window-open.md @@ -0,0 +1,67 @@ +# The `window.open` function + +Qunado `window.open` é chamado para criar uma nova janela de uma pagina web uma nova instância de `BrowserWindow` será criado para a `url` e um proxy será devolvido para o `windows.open`, para permitir que a página tenha limitado controle sobre ele. + +O proxy tem funcionalidade limitada padrão implementada para ser compatível com as páginas web tradicionais. +Para controle total da nova janela você deveria criar um `BrowserWindow` diretamente + + +The newly created `BrowserWindow` will inherit parent window's options by +default, to override inherited options you can set them in the `features` +string. + +O recém-criado `BrowserWindow` herdará as opções da janela pai por padrão, para substituir as opções herdadas você pode definilos no `features`(string). +### `window.open(url[, frameName][, features])` + +* `url` String +* `frameName` String (opcional) +* `features` String (opcional) + +Cria uma nova janela e retorna uma instância da classe `BrowserWindowProxy'. + +A string `features` segue o formato padrão do browser, mas cada recurso (feature) tem que ser um campo de opções do `BrowserWindow`. + +### `window.opener.postMessage(message, targetOrigin)` + +* `message` String +* `targetOrigin` String + +Envia uma mensagem para a janela pai com a origem especificada ou `*` preferência de origem não especificada. +Sends a message to the parent window with the specified origin or `*` +origin preference. + +## Class: BrowserWindowProxy + +O objeto `BrowserWindowProxy` é retornado de `window.open` e fornece uma funcionalidade limitada para a janela filha. + +### `BrowserWindowProxy.blur()` + +Remove o foco da janela filha. + +### `BrowserWindowProxy.close()` + +Forçadamente fecha a janela filha sem chamar o evento de descarregamento. + +### `BrowserWindowProxy.closed` + +Define como true após a janela filha ficar fechada. + +### `BrowserWindowProxy.eval(code)` + +* `code` String + +Avalia o código na jánela filha. + +### `BrowserWindowProxy.focus()` + +Concentra-se a janela filha (traz a janela para frente) +### `BrowserWindowProxy.postMessage(message, targetOrigin)` + +* `message` String +* `targetOrigin` String + +Sends a message to the child window with the specified origin or `*` for no +origin preference. + +In addition to these methods, the child window implements `window.opener` object +with no properties and a single method. diff --git a/docs-translations/pt-BR/development/source-code-directory-structure.md b/docs-translations/pt-BR/development/source-code-directory-structure.md new file mode 100644 index 00000000000..77c01d79113 --- /dev/null +++ b/docs-translations/pt-BR/development/source-code-directory-structure.md @@ -0,0 +1,53 @@ +# Estrutura de Diretórios do Código-Fonte + +O código-fonte do Electron é separado em algumas partes, seguindo principalmente as convenções de separação do chromium. + +Você pode se familiarizar com a [arquitetura de multiprocessamento ](http://dev.chromium.org/developers/design-documents/multi-process-architecture) do Chromium para entender melhor o código-fonte. + + +## Estrutura do Código-Fonte + +``` +Electron +├──atom - Código fonte do Electron. +| ├── app - Código de inicialização. +| ├── browser - A interface incluíndo a janela principal, UI, e todas as coisas do processo principal. Ele se comunica com o renderizador para gerenciar as páginas web. +| |   ├── lib - Código Javascript para inicializar o processo principal. +| | ├── ui - Implementação da UI para plataformas distintas. +| | | ├── cocoa - Código-fonte específico do cocoa . +| | | ├── gtk - Código-font específico do GTK+. +| | | └── win - Código-fonte específico do Windows GUI. +| | ├── default_app - A página padrão é mostrada quando +| | | Electron inicializa sem fornecer um app. +| | ├── api - Implementação do processo principal das APIs +| | | └── lib - Código Javascript, parte da implementação da API. +| | ├── net - Código relacionado a rede. +| | ├── mac - Código fonte em Object-c, específico para Mac. +| | └── resources - Icones, arquivos dependentes da plataforma, etc. +| ├── renderer - Código que é executado no processo de renderização. +| | ├── lib - Parte do código Javascript de inicialização do renderizador. +| | └── api - Implementação das APIs para o processo de renderizaçãp. +| | └── lib - Código Javascript, parte da implementação da API. +| └── common - Código que utiliza ambos os processos, o principal e o de rendezição, +| ele inclui algumas funções utilitárias e códigos para integrar com ciclo de mensagens do node no ciclo de mensagens do Chromium. +| ├── lib - Código Javascript comum para a inicialização. +| └── api - A implementação de APIs comuns e fundamentação dos +| módulos integrados com Electron's. +| └── lib - Código Javascript, parte da implementação da API. +├── chromium_src - Código-fonte copiado do Chromium. +├── docs - Documentação. +├── spec - Testes Automáticos. +├── atom.gyp - Regras de compilação do Electron. +└── common.gypi - Configuração específica do compilador e regras de construção para outros componentes + como `node` e `breakpad`. +``` + +## Estrutura de Outros Diretórios. + +* **script** - Scripts utilizado para fins de desenvolvimento como building, packaging, testes, etc. +* **tools** - Scripts auxiliares, utilizados pelos arquivos gyp, ao contrário do`script`, os scripts colocados aqui nunca devem ser invocados diretamente pelos usuários. +* **vendor** - Dependências de código-fonte de terceiros, nós não utilizamos `third_party` como nome porque ele poderia ser confundido com o diretório homônimo existente no código-fonte do Chromium. +* **node_modules** - Módulos de terceiros em node usados para compilação +* **out** - Diretório temporário saída do `ninja`. +* **dist** - Diretório temporário do `script/create-dist.py` ao criar uma distribuição +* **external_binaries** - Binários baixados de Frameworks de terceiros que não suportam a compilação com `gyp`. diff --git a/docs-translations/pt-BR/tutorial/application-distribution.md b/docs-translations/pt-BR/tutorial/application-distribution.md index 41f2457c408..f5a87d532c5 100644 --- a/docs-translations/pt-BR/tutorial/application-distribution.md +++ b/docs-translations/pt-BR/tutorial/application-distribution.md @@ -1,7 +1,7 @@ # Distribuição de aplicações -Para distribuir sua aplicação com o Electron, você deve nomear o diretório que contém sua aplicação como -`app` e dentro deste diretório colocar os recursos que você está utilizando (no OSX +Para distribuir sua aplicação com o Electron, você deve nomear o diretório que contém sua aplicação como +`app` e dentro deste diretório colocar os recursos que você está utilizando (no OSX `Electron.app/Contents/Resources/`, no Linux e no Windows é em `resources/`): @@ -24,7 +24,7 @@ electron/resources/app ``` Logo após execute `Electron.app` (ou `electron` no Linux e `electron.exe` no Windows), -e o Electron iniciaria a aplicação. O diretório `electron` será utilizado para criar a distribuição para +e o Electron iniciaria a aplicação. O diretório `electron` será utilizado para criar a distribuição para usuários finais. ## Empacotando sua aplicação em um arquivo. @@ -58,11 +58,10 @@ Mais detalhes podem ser encontrados em [Empacotamento da aplicação](../../../d Depois de empacotar seu aplicativo Electron, você vai querer renomear a marca Electron antes de distribuí-lo aos usuários. -### Janelas +### Windows Você pode renomear `electron.exe` para o nome que desejar e editar o seu ícone e outras -informações com ferramentas como [rcedit](https://github.com/atom/rcedit) ou -[ResEdit](http://www.resedit.net). +informações com ferramentas como [rcedit](https://github.com/atom/rcedit). ### OS X @@ -115,4 +114,4 @@ uma tarefa para o Grunt foi criado e irá cuidar desta tarefa automaticamente pa [grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). Esta tarefa irá automaticamente editar o arquivo `.gyp`, compilar o código -e reconstruir os módulos nativos da aplicação para utilizar o novo nome. \ No newline at end of file +e reconstruir os módulos nativos da aplicação para utilizar o novo nome. diff --git a/docs-translations/pt-BR/tutorial/supported-platforms.md b/docs-translations/pt-BR/tutorial/supported-platforms.md new file mode 100644 index 00000000000..0fc42179bb9 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/supported-platforms.md @@ -0,0 +1,22 @@ +# Plataformas Suportadas + +As plataformas suportadas por Electron são: + +### OS X + +Somente binarios em 64bit são construidos para OS X e a versão mínima suportada é OS X 10.9. + +### Windows +Suporte para Windows 7 ou superior, versões anteriores não são suportados (e não ira funcionar) + + Binarios em `x86` e `amd64` (x64) são construidos para Windows. Atencão: Versão `ARM` do Windows não é suportada agora. + +### Linux +Binario pré-construido `ia32`(`i686`) e binario `x64`(`amd64`) são construidas no Ubuntu 12.04, binario `arm` está construido contra ARM v7 com hard-float ABI e NEION para Debian Wheezy. + +Se o pré-compilador poderá ser executado por uma distribuição, depende se a distruibuicão inclui as blibliotecas que o Eletron está vinculando na plataforma de construcão, por este motivo apenas Ubuntu 12.04 é garantido para funcionar corretamente, mas as seguintes distribuições foram verificados com o pre-compilador: + + +* Ubuntu 12.04 ou superior +* Fedora 21 +* Debian 8 diff --git a/docs-translations/ru-RU/README.md b/docs-translations/ru-RU/README.md index 07e50028df9..4e7ed443468 100644 --- a/docs-translations/ru-RU/README.md +++ b/docs-translations/ru-RU/README.md @@ -1,6 +1,6 @@ Пожалуйста, убедитесь, что вы используете документацию, которые соответствует вашей версии Electron. Номер версии должен быть частью адреса страницы. Если это не так, вы -возможно,используете документицию ветки разработки, которая может содержать изменения api, +возможно, используете документацию ветки разработки, которая может содержать изменения api, которые не совместимы с вашей версией Electron. Если это так, Вы можете переключиться на другую версию документации в списке [доступные версии](http://electron.atom.io/docs/) на atom.io, или diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md index 5c4303a0a6f..58293e38470 100644 --- a/docs-translations/zh-CN/README.md +++ b/docs-translations/zh-CN/README.md @@ -1,13 +1,19 @@ +## 常见问题 + ++ [Electron 常见问题](faq/electron-faq.md) + ## 向导 * [支持平台](tutorial/supported-platforms.md) -* [应用部署](tutorial/application-distribution.md) -* [应用打包](tutorial/application-packaging.md) -* [使用原生模块](tutorial/using-native-node-modules.md) +* [分发应用](tutorial/application-distribution.md) +* [提交应用到 Mac App 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) -* [调试工具扩展](tutorial/devtools-extension.md) -* [使用 PepperFlash 插件](tutorial/using-pepper-flash-plugin.md) +* [使用开发人员工具扩展](tutorial/devtools-extension.md) +* [使用 Pepper Flash 插件](tutorial/using-pepper-flash-plugin.md) +* [使用 Widevine CDM 插件](tutorial/using-widevine-cdm-plugin.md) ## 教程 @@ -19,53 +25,55 @@ * [简介](api/synopsis.md) * [进程对象](api/process.md) -* [支持的Chrome命令行开关](api/chrome-command-line-switches.md) +* [支持的 Chrome 命令行开关](api/chrome-command-line-switches.md) +* [环境变量](api/environment-variables.md) -定制的DOM元素: +自定义的 DOM 元素: -* [`File`对象](api/file-object.md) -* [``标签](api/web-view-tag.md) -* [`window.open`函数](api/window-open.md) +* [`File` 对象](api/file-object.md) +* [`` 标签](api/web-view-tag.md) +* [`window.open` 函数](api/window-open.md) -主进程可用的模块: +在主进程内可用的模块: * [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) * [webContents](api/web-contents.md) -* [tray](api/tray.md) +* [Tray](api/tray.md) -渲染进程(网页)可用的模块: +在渲染进程(网页)内可用的模块: -* [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) -两种进程都可用的模块: +在两种进程中都可用的模块: * [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) +* [代码规范](development/coding-style.md) * [源码目录结构](development/source-code-directory-structure.md) -* [与 NW.js (原名 node-webkit) 在技术上的差异](development/atom-shell-vs-node-webkit.md) -* [构建系统概况](development/build-system-overview.md) -* [构建步骤 (Mac)](development/build-instructions-mac.md) -* [构建步骤 (Windows)](development/build-instructions-windows.md) -* [构建步骤 (Linux)](development/build-instructions-linux.md) -* [在调试中使用 SymbolServer](development/setting-up-symbol-server.md) +* [与 NW.js(原 node-webkit)在技术上的差异](development/atom-shell-vs-node-webkit.md) +* [构建系统概览](development/build-system-overview.md) +* [构建步骤(OS X)](development/build-instructions-osx.md) +* [构建步骤(Windows)](development/build-instructions-windows.md) +* [构建步骤(Linux)](development/build-instructions-linux.md) +* [在调试中使用 Symbol Server](development/setting-up-symbol-server.md) diff --git a/docs-translations/zh-CN/api/accelerator.md b/docs-translations/zh-CN/api/accelerator.md index 8858d18e856..06e7397c849 100644 --- a/docs-translations/zh-CN/api/accelerator.md +++ b/docs-translations/zh-CN/api/accelerator.md @@ -1,46 +1,43 @@ # Accelerator -An accelerator is a string that represents a keyboard shortcut. It can contain -multiple modifiers and key codes, combined by the `+` character. +一个 `Accelerator` 是一个表示某个快捷键组合的字符串。它包含了用 `+` 连接的若干个按键。 -Examples: +例如: * `Command+A` * `Ctrl+Shift+Z` -## Platform notice +## 运行平台相关的提示 -On Linux and Windows, the `Command` key does not have any effect so -use `CommandOrControl` which represents `Command` on OS X and `Control` on -Linux and Windows to define some accelerators. +在 Linux 和 Windows 上,`Command` 键并不存在,因此我们通常用 `CommandOrControl` 来表示“在 OS X 下为 `Command` 键,但在 +Linux 和 Windows 下为 `Control` 键。 -The `Super` key is mapped to the `Windows` key on Windows and Linux and -`Cmd` on OS X. +`Super` 键是指 Linux 和 Windows 上的 `Windows` 键,但是在 OS X 下为 `Command` 键。 -## Available modifiers +## 可用的功能按键 -* `Command` (or `Cmd` for short) -* `Control` (or `Ctrl` for short) -* `CommandOrControl` (or `CmdOrCtrl` for short) +* `Command`(缩写为 `Cmd`) +* `Control`(缩写为 `Ctrl`) +* `CommandOrControl`(缩写为 `CmdOrCtrl`) * `Alt` * `Shift` * `Super` -## Available key codes +## 可用的普通按键 -* `0` to `9` -* `A` to `Z` -* `F1` to `F24` -* Punctuations like `~`, `!`, `@`, `#`, `$`, etc. +* `0` 到 `9` +* `A` 到 `Z` +* `F1` 到 `F24` +* 类似与 `~`、`!`、`@`、`#`、`$` 的标点符号。 * `Plus` * `Space` * `Backspace` * `Delete` * `Insert` -* `Return` (or `Enter` as alias) -* `Up`, `Down`, `Left` and `Right` -* `Home` and `End` -* `PageUp` and `PageDown` -* `Escape` (or `Esc` for short) -* `VolumeUp`, `VolumeDown` and `VolumeMute` -* `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` and `MediaPlayPause` +* `Return`(和 `Enter` 等同) +* `Up`、`Down`、`Left` 和 `Right` +* `Home` 和 `End` +* `PageUp` 和 `PageDown` +* `Escape`(缩写为 `Esc`) +* `VolumeUp`、`VolumeDown` 和 `VolumeMute` +* `MediaNextTrack`、`MediaPreviousTrack`、`MediaStop` 和 `MediaPlayPause` diff --git a/docs-translations/zh-CN/api/app.md b/docs-translations/zh-CN/api/app.md index 03eb083dfa6..21c73cf836a 100644 --- a/docs-translations/zh-CN/api/app.md +++ b/docs-translations/zh-CN/api/app.md @@ -11,11 +11,11 @@ app.on('window-all-closed', function() { }); ``` -## 事件 +## 事件列表 `app` 对象会触发以下的事件: -### 事件: 'will-finish-launching' +### 事件:'will-finish-launching' 当应用程序完成基础的启动的时候被触发。在 Windows 和 Linux 中, `will-finish-launching` 事件与 `ready` 事件是相同的; 在 OS X 中, @@ -24,11 +24,11 @@ app.on('window-all-closed', function() { 在大多数的情况下,你应该只在 `ready` 事件处理器中完成所有的业务。 -### 事件: 'ready' +### 事件:'ready' 当 Electron 完成初始化时被触发。 -### 事件: 'window-all-closed' +### 事件:'window-all-closed' 当所有的窗口都被关闭时触发。 @@ -36,20 +36,20 @@ app.on('window-all-closed', function() { 或者开发者调用了 `app.quit()` ,Electron 将会先尝试关闭所有的窗口再触发 `will-quit` 事件, 在这种情况下 `window-all-closed` 不会被触发。 -### 事件: 'before-quit' +### 事件:'before-quit' 返回: -* `event` 事件 +* `event` Event 在应用程序开始关闭它的窗口的时候被触发。 调用 `event.preventDefault()` 将会阻止终止应用程序的默认行为。 -### 事件: 'will-quit' +### 事件:'will-quit' 返回: -* `event` 事件 +* `event` Event 当所有的窗口已经被关闭,应用即将退出时被触发。 调用 `event.preventDefault()` 将会阻止终止应用程序的默认行为。 @@ -57,83 +57,121 @@ app.on('window-all-closed', function() { 你可以在 `window-all-closed` 事件的描述中看到 `will-quit` 事件 和 `window-all-closed` 事件的区别。 -### 事件: 'quit' +### 事件:'quit' +返回: + +* `event` Event +* `exitCode` Integer 当应用程序正在退出时触发。 -### 事件: 'open-file' +### 事件:'open-file' _OS X_ 返回: -* `event` 事件 -* `path` 字符串 +* `event` Event +* `path` String 当用户想要在应用中打开一个文件时触发。`open-file` 事件常常在应用已经打开并且系统想要再次使用应用打开文件时被触发。 `open-file` 也会在一个文件被拖入 dock 且应用还没有运行的时候被触发。 请确认在应用启动的时候(甚至在 `ready` 事件被触发前)就对 `open-file` 事件进行监听,以处理这种情况。 如果你想处理这个事件,你应该调用 `event.preventDefault()` 。 +在 Windows系统中, 你需要通过解析 process.argv 来获取文件路径。 -### 事件: 'open-url' +### 事件:'open-url' _OS X_ 返回: -* `event` 事件 -* `url` 字符串 +* `event` Event +* `url` String 当用户想要在应用中打开一个url的时候被触发。URL格式必须要提前标识才能被你的应用打开。 如果你想处理这个事件,你应该调用 `event.preventDefault()` 。 -### 事件: 'activate' _OS X_ +### 事件:'activate' _OS X_ 返回: -* `event` 事件 -* `hasVisibleWindows` 布尔值 +* `event` Event +* `hasVisibleWindows` Boolean 当应用被激活时触发,常用于点击应用的 dock 图标的时候。 -### 事件: 'browser-window-blur' +### 事件:'browser-window-blur' 返回: -* `event` 事件 -* `window` 浏览器窗口 +* `event` Event +* `window` BrowserWindow -当一个 [浏览器窗口](browser-window.md) 失去焦点的时候触发。 +当一个 [BrowserWindow](browser-window.md) 失去焦点的时候触发。 -### 事件: 'browser-window-focus' +### 事件:'browser-window-focus' 返回: -* `event` 事件 -* `window` 浏览器窗口 +* `event` Event +* `window` BrowserWindow -当一个 [浏览器窗口](browser-window.md) 获得焦点的时候触发。 +当一个 [BrowserWindow](browser-window.md) 获得焦点的时候触发。 -### 事件: 'browser-window-created' +### 事件:'browser-window-created' 返回: -* `event` 事件 -* `window` 浏览器窗口 +* `event` Event +* `window` BrowserWindow -当一个 [浏览器窗口](browser-window.md) 被创建的时候触发。 +当一个 [BrowserWindow](browser-window.md) 被创建的时候触发。 -### 事件: 'select-certificate' +### 事件:'certificate-error' + +返回: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` String - URL 地址 +* `error` String - 错误码 +* `certificate` Object + * `data` Buffer - PEM 编码数据 + * `issuerName` String - 发行者的公有名称 +* `callback` Function + +当对 `url` 验证 `certificate` 证书失败的时候触发,如果需要信任这个证书,你需要阻止默认行为 `event.preventDefault()` 并且 +调用 `callback(true)`。 + +```javascript +session.on('certificate-error', function(event, webContents, url, error, certificate, callback) { + if (url == "https://github.com") { + // 验证逻辑。 + event.preventDefault(); + callback(true); + } else { + callback(false); + } +}); +``` + +### 事件:'select-client-certificate' + + +返回: + +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `url` String - URL 地址 +* `certificateList` [Object] + * `data` Buffer - PEM 编码数据 + * `issuerName` String - 发行者的公有名称 +* `callback` Function 当一个客户端认证被请求的时候被触发。 -返回: +`url` 指的是请求客户端认证的网页地址,调用 `callback` 时需要传入一个证书列表中的证书。 -* `event` 事件 -* `webContents` [web组件](browser-window.md#class-webcontents) -* `url` 字符串 -* `certificateList` 对象 - * `data` PEM 编码数据 - * `issuerName` 发行者的公有名称 -* `callback` 函数 +需要通过调用 `event.preventDefault()` 来防止应用自动使用第一个证书进行验证。如下所示: ```javascript app.on('select-certificate', function(event, host, url, list, callback) { @@ -141,28 +179,67 @@ app.on('select-certificate', function(event, host, url, list, callback) { callback(list[0]); }) ``` +### 事件: 'login' -The `url` corresponds to the navigation entry requesting the client certificate -and `callback` needs to be called with an entry filtered from the list. -Using `event.preventDefault()` prevents the application from using the first -certificate from the store. +返回: -### 事件: 'gpu-process-crashed' +* `event` Event +* `webContents` [WebContents](web-contents.md) +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function -当GPU进程崩溃时触发。 +当 `webContents` 要做进行一次 HTTP 登陆验证时被触发。 -## 方法 +默认情况下,Electron 会取消所有的验证行为,如果需要重写这个行为,你需要用 `event.preventDefault()` 来阻止默认行为,并且 +用 `callback(username, password)` 来进行验证。 + +```javascript +app.on('login', function(event, webContents, request, authInfo, callback) { + event.preventDefault(); + callback('username', 'secret'); +}) +``` +### 事件:'gpu-process-crashed' + +当 GPU 进程崩溃时触发。 + +## 方法列表 `app` 对象拥有以下的方法: -**提示:** 有的方法只能用于特定的操作系统。 +**请注意** 有的方法只能用于特定的操作系统。 ### `app.quit()` -试图关掉所有的窗口。`before-quit` 事件将会被最先触发。如果所有的窗口都被成功关闭了, +试图关掉所有的窗口。`before-quit` 事件将会最先被触发。如果所有的窗口都被成功关闭了, `will-quit` 事件将会被触发,默认下应用将会被关闭。 -这个方法保证了所有的 `beforeunload` 和 `unload` 事件处理器被正确执行。会存在一个窗口被 `beforeunload` 事件处理器返回 `false` 取消退出的可能性。 +这个方法保证了所有的 `beforeunload` 和 `unload` 事件处理器被正确执行。假如一个窗口的 `beforeunload` 事件处理器返回 `false`,那么整个应用可能会取消退出。 + +### `app.hide()` _OS X_ + +隐藏所有的应用窗口,不是最小化. + +### `app.show()` _OS X_ + +隐藏后重新显示所有的窗口,不会自动选中他们。 + +### `app.exit(exitCode)` + +* `exitCode` 整数 + +带着`exitCode`退出应用. + +所有的窗口会被立刻关闭,不会询问用户。`before-quit` 和 `will-quit` 这2个事件不会被触发 ### `app.getAppPath()` @@ -170,38 +247,38 @@ certificate from the store. ### `app.getPath(name)` -* `name` 字符串 +* `name` String 返回一个与 `name` 参数相关的特殊文件夹或文件路径。当失败时抛出一个 `Error` 。 你可以通过名称请求以下的路径: -* `home` 用户的 home 文件夹。 -* `appData` 所有用户的应用数据文件夹,默认对应: +* `home` 用户的 home 文件夹(主目录) +* `appData` 当前用户的应用数据文件夹,默认对应: * `%APPDATA%` Windows 中 * `$XDG_CONFIG_HOME` or `~/.config` Linux 中 * `~/Library/Application Support` OS X 中 -* `userData` 储存你应用程序设置文件的文件夹,默认是 `appData` 文件夹附加应用的名称。 -* `cache` 所有用户应用程序缓存的文件夹,默认对应: - * `%APPDATA%` Windows 中 (没有一个通用的缓存位置) - * `$XDG_CACHE_HOME` 或 `~/.cache` Linux 中 - * `~/Library/Caches` OS X 中 -* `userCache` 用于存放应用程序缓存的文件夹,默认是 `cache` 文件夹附加应用的名称。 -* `temp` 临时文件夹。 -* `userDesktop` 当前用户的桌面文件夹。 -* `exe` 当前的可执行文件。 -* `module` `libchromiumcontent` 库。 +* `userData` 储存你应用程序设置文件的文件夹,默认是 `appData` 文件夹附加应用的名称 +* `temp` 临时文件夹 +* `exe` 当前的可执行文件 +* `module` `libchromiumcontent` 库 +* `desktop` 当前用户的桌面文件夹 +* `documents` 用户文档目录的路径 +* `downloads` 用户下载目录的路径. +* `music` 用户音乐目录的路径. +* `pictures` 用户图片目录的路径. +* `videos` 用户视频目录的路径. ### `app.setPath(name, path)` -* `name` 字符串 -* `path` 字符串 +* `name` String +* `path` String -重写 `path` 参数到一个特别的文件夹或者是一个和 `name` 参数有关系的文件。 +重写某个 `name` 的路径为 `path`,`path` 可以是一个文件夹或者一个文件,这个和 `name` 的类型有关。 如果这个路径指向的文件夹不存在,这个文件夹将会被这个方法创建。 -如果错误则抛出 `Error` 。 +如果错误则会抛出 `Error`。 -你只可以指向 `app.getPath` 中定义过 `name` 的路径。You can only override paths of a `name` defined in `app.getPath`. +`name` 参数只能使用 `app.getPath` 中定义过 `name`。 默认情况下,网页的 cookie 和缓存都会储存在 `userData` 文件夹。 如果你想要改变这个位置,你需要在 `app` 模块中的 `ready` 事件被触发之前重写 `userData` 的路径。 @@ -215,101 +292,192 @@ certificate from the store. 返回当前应用程序的 `package.json` 文件中的名称。 -通常 `name` 字段是一个短的小写字符串,其命名规则按照 npm 中的模块命名规则。你应该单独列举一个 -`productName` 字段,用于表示你的应用程序的完整名称,这个名称将会被 Electron 优先采用。 +由于 npm 的命名规则,通常 `name` 字段是一个短的小写字符串。但是应用名的完整名称通常是首字母大写的,你应该单独使用一个 +`productName` 字段,用于表示你的应用程序的完整名称。Electron 会优先使用这个字段作为应用名。 + +### `app.setName(name)` + +* `name` String + +重写当前应用的名字 ### `app.getLocale()` -返回当前应用程序的位置。 +返回当前应用程序的语言。 -### `app.resolveProxy(url, callback)` +### `app.addRecentDocument(path)` _OS X_ _Windows_ -* `url` URL -* `callback` 函数 +* `path` String -为 `url` 解析代理信息。 `callback` 在请求被执行之后将会被 `callback(proxy)` 调用。 +在最近访问的文档列表中添加 `path`。 -### `app.addRecentDocument(path)` +这个列表由操作系统进行管理。在 Windows 中您可以通过任务条进行访问,在 OS X 中你可以通过 dock 菜单进行访问。 -* `path` 字符串 - -为最近访问的文档列表中添加 `path` 。 - -这个列表由操作系统进行管理。在 Windows 中您可以通过任务条进行访问,在 OS X 中你可以通过dock 菜单进行访问。 - -### `app.clearRecentDocuments()` +### `app.clearRecentDocuments()` _OS X_ _Windows_ 清除最近访问的文档列表。 ### `app.setUserTasks(tasks)` _Windows_ -* `tasks` 由 `Task` 对象构成的数组 +* `tasks` [Task] - 一个由 Task 对象构成的数组 将 `tasks` 添加到 Windows 中 JumpList 功能的 [Tasks][tasks] 分类中。 `tasks` 中的 `Task` 对象格式如下: -`Task` 对象 -* `program` 字符串 - 执行程序的路径,通常你应该说明当前程序的路径为 `process.execPath` 字段。 -* `arguments` 字符串 - 当 `program` 执行时的命令行参数。 -* `title` 字符串 - JumpList 中显示的标题。 -* `description` 字符串 - 对这个任务的描述。 -* `iconPath` 字符串 - JumpList 中显示的 icon 的绝对路径,可以是一个任意包含一个icon的资源文件。你通常可以通过指明 `process.execPath` 来显示程序中的icon。 -* `iconIndex` 整数 - icon文件中的icon目录。如果一个icon文件包括了两个或多个icon,就需要设置这个值以确定icon。如果一个文件仅包含一个icon,那么这个值为0。 +`Task` Object +* `program` String - 执行程序的路径,通常你应该说明当前程序的路径为 `process.execPath` 字段。 +* `arguments` String - 当 `program` 执行时的命令行参数。 +* `title` String - JumpList 中显示的标题。 +* `description` String - 对这个任务的描述。 +* `iconPath` String - JumpList 中显示的图标的绝对路径,可以是一个任意包含一个图标的资源文件。通常来说,你可以通过指明 `process.execPath` 来显示程序中的图标。 +* `iconIndex` Integer - 图标文件中的采用的图标位置。如果一个图标文件包括了多个图标,就需要设置这个值以确定使用的是哪一个图标。 +如果这个图标文件中只包含一个图标,那么这个值为 0。 +### `app.allowNTLMCredentialsForAllDomains(allow)` + +* `allow` Boolean + +动态设置是否总是为 HTTP NTLM 或 Negotiate 认证发送证书。通常来说,Electron 只会对本地网络(比如和你处在一个域中的计算机)发 +送 NTLM / Kerberos 证书。但是假如网络设置得不太好,可能这个自动探测会失效,所以你可以通过这个接口自定义 Electron 对所有 URL +的行为。 + +### `app.makeSingleInstance(callback)` + +* `callback` Function + +这个方法可以让你的应用在同一时刻最多只会有一个实例,否则你的应用可以被运行多次并产生多个实例。你可以利用这个接口保证只有一个实例正 +常运行,其余的实例全部会被终止并退出。 + +如果多个实例同时运行,那么第一个被运行的实例中 `callback` 会以 `callback(argv, workingDirectory)` 的形式被调用。其余的实例 +会被终止。 +`argv` 是一个包含了这个实例的命令行参数列表的数组,`workingDirectory` 是这个实例目前的运行目录。通常来说,我们会用通过将应用在 +主屏幕上激活,并且取消最小化,来提醒用户这个应用已经被打开了。 + +在 `app` 的 `ready` 事件后,`callback` 才有可能被调用。 + +如果当前实例为第一个实例,那么在这个方法将会返回 `false` 来保证它继续运行。否则将会返回 `true` 来让它立刻退出。 + +在 OS X 中,如果用户通过 Finder、`open-file` 或者 `open-url` 打开应用,系统会强制确保只有一个实例在运行。但是如果用户是通过 +命令行打开,这个系统机制会被忽略,所以你仍然需要靠这个方法来保证应用为单实例运行的。 + +下面是一个简单的例子。我们可以通过这个例子了解如何确保应用为单实例运行状态。 + +```js +var myWindow = null; + +var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { + // 当另一个实例运行的时候,这里将会被调用,我们需要激活应用的窗口 + if (myWindow) { + if (myWindow.isMinimized()) myWindow.restore(); + myWindow.focus(); + } + return true; +}); + +// 这个实例是多余的实例,需要退出 +if (shouldQuit) { + app.quit(); + return; +} + +// 创建窗口、继续加载应用、应用逻辑等…… +app.on('ready', function() { +}); + +``` +### `app.setAppUserModelId(id)` _Windows_ + +* `id` String + +改变当前应用的 [Application User Model ID][app-user-model-id] 为 `id`. + +### `app.isAeroGlassEnabled()` _Windows_ + +如果 [DWM composition](https://msdn.microsoft.com/en-us/library/windows/desktop/aa969540.aspx)(Aero Glass) 启用 +了,那么这个方法会返回 `true`,否则是 `false`。你可以用这个方法来决定是否要开启透明窗口特效,因为如果用户没开启 DWM,那么透明窗 +口特效是无效的。 + +举个例子: + +```js +let browserOptions = {width: 1000, height: 800}; + +// 只有平台支持的时候才使用透明窗口 +if (process.platform !== 'win32' || app.isAeroGlassEnabled()) { + browserOptions.transparent = true; + browserOptions.frame = false; +} + +// 创建窗口 +win = new BrowserWindow(browserOptions); + +// 转到某个网页 +if (browserOptions.transparent) { + win.loadURL('file://' + __dirname + '/index.html'); +} else { + // 没有透明特效,我们应该用某个只包含基本样式的替代解决方案。 + win.loadURL('file://' + __dirname + '/fallback.html'); +} +``` ### `app.commandLine.appendSwitch(switch[, value])` -通过可选的参数 `value` 给 Chromium 命令行中添加一个开关。 -Append a switch (with optional `value`) to Chromium's command line. +通过可选的参数 `value` 给 Chromium 中添加一个命令行开关。 -**贴士:** 这不会影响 `process.argv` ,这个方法主要被开发者用于控制一些低层级的 Chromium 行为。 +**注意** 这个方法不会影响 `process.argv`,我们通常用这个方法控制一些底层 Chromium 行为。 ### `app.commandLine.appendArgument(value)` -给 Chromium 命令行中加入一个参数。这个参数是当前正在被引用的。 +给 Chromium 中直接添加一个命令行参数,这个参数 `value` 的引号和格式必须正确。 -**贴士:** 这不会影响 `process.argv`。 +**注意** 这个方法不会影响 `process.argv`。 ### `app.dock.bounce([type])` _OS X_ -* `type` 字符串 (可选的) - 可以是 `critical` 或 `informational`。默认下是 `informational` +* `type` String - 可选参数,可以是 `critical` 或 `informational`。默认为 `informational`。 -当输入 `critical` 时,dock 中的 icon 将会开始弹跳直到应用被激活或者这个请求被取消。 +当传入的是 `critical` 时,dock 中的应用将会开始弹跳,直到这个应用被激活或者这个请求被取消。 -当输入 `informational` 时,dock 中的 icon 只会弹跳一秒钟。 -然而,这个请求仍然会激活,直到应用被激活或者请求被取消。 +当传入的是 `informational` 时,dock 中的图标只会弹跳一秒钟。但是,这个请求仍然会激活,直到应用被激活或者请求被取消。 -返回一个表示这个请求的 ID。 +这个方法返回的返回值表示这个请求的 ID。 ### `app.dock.cancelBounce(id)` _OS X_ -* `id` 整数 +* `id` Integer 取消这个 `id` 对应的请求。 ### `app.dock.setBadge(text)` _OS X_ -* `text` 字符串 +* `text` String -设置 dock 中显示的字符。 +设置应用在 dock 中显示的字符串。 ### `app.dock.getBadge()` _OS X_ -返回 dock 中显示的字符。 +返回应用在 dock 中显示的字符串。 ### `app.dock.hide()` _OS X_ -隐藏 dock 中的 icon。 +隐藏应用在 dock 中的图标。 ### `app.dock.show()` _OS X_ -显示 dock 中的 icon。 +显示应用在 dock 中的图标。 ### `app.dock.setMenu(menu)` _OS X_ -* `menu` 菜单 +* `menu` [Menu](menu.md) 设置应用的 [dock 菜单][dock-menu]. +### `app.dock.setIcon(image)` _OS X_ + +* `image` [NativeImage](native-image.md) + +设置应用在 dock 中显示的图标。 + [dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 -[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks \ No newline at end of file +[tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs-translations/zh-CN/api/auto-updater.md b/docs-translations/zh-CN/api/auto-updater.md new file mode 100644 index 00000000000..f027fbc6e50 --- /dev/null +++ b/docs-translations/zh-CN/api/auto-updater.md @@ -0,0 +1,88 @@ +# autoUpdater + +这个模块提供了一个到 `Squirrel` 自动更新框架的接口。 + +## 平台相关的提示 + +虽然 `autoUpdater` 模块提供了一套各平台通用的接口,但是在每个平台间依然会有一些微小的差异。 + +### OS X + +在 OS X 上,`autoUpdater` 模块依靠的是内置的 [Squirrel.Mac][squirrel-mac],这意味着你不需要依靠其他的设置就能使用。关于 +更新服务器的配置,你可以通过阅读 [Server Support][server-support] 这篇文章来了解。 + +### Windows + +在 Windows 上,你必须使用安装程序将你的应用装到用户的计算机上,所以比较推荐的方法是用 [grunt-electron-installer][installer] 这个模块来自动生成一个 Windows 安装向导。 + +Squirrel 自动生成的安装向导会生成一个带 [Application User Model ID][app-user-model-id] 的快捷方式。 +Application User Model ID 的格式是 `com.squirrel.PACKAGE_ID.YOUR_EXE_WITHOUT_DOT_EXE`, 比如 +像 `com.squirrel.slack.Slack` 和 `com.squirrel.code.Code` 这样的。你应该在自己的应用中使用 `app.setAppUserModelId` 方法设置相同的 API,不然 Windows 将不能正确地把你的应用固定在任务栏上。 + +服务器端的配置和 OS X 也是不一样的,你可以阅读 [Squirrel.Windows][squirrel-windows] 这个文档来获得详细信息。 + +### Linux + +Linux 下没有任何的自动更新支持,所以我们推荐用各个 Linux 发行版的包管理器来分发你的应用。 + +## 事件列表 + +`autoUpdater` 对象会触发以下的事件: + +### 事件:'error' + +返回: + +* `error` Error + +当更新发生错误的时候触发。 + +### 事件:'checking-for-update' + +当开始检查更新的时候触发。 + +### 事件:'update-available' + +当发现一个可用更新的时候触发,更新包下载会自动开始。 + +### 事件:'update-not-available' + +当没有可用更新的时候触发。 + +### 事件:'update-downloaded' + +返回: + +* `event` Event +* `releaseNotes` String - 新版本更新公告 +* `releaseName` String - 新的版本号 +* `releaseDate` Date - 新版本发布的日期 +* `updateURL` String - 更新地址 + +在更新下载完成的时候触发。 + +在 Windows 上只有 `releaseName` 是有效的。 + +## 方法列表 + +`autoUpdater` 对象有以下的方法: + +### `autoUpdater.setFeedURL(url)` + +* `url` String + +设置检查更新的 `url`,并且初始化自动更新。这个 `url` 一旦设置就无法更改。 + +### `autoUpdater.checkForUpdates()` + +向服务端查询现在是否有可用的更新。在调用这个方法之前,必须要先调用 `setFeedURL`。 + +### `autoUpdater.quitAndInstall()` + +在下载完成后,重启当前的应用并且安装更新。这个方法应该仅在 `update-downloaded` 事件触发后被调用。 + +[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 +[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx diff --git a/docs-translations/zh-CN/api/browser-window.md b/docs-translations/zh-CN/api/browser-window.md new file mode 100644 index 00000000000..6e486406d63 --- /dev/null +++ b/docs-translations/zh-CN/api/browser-window.md @@ -0,0 +1,761 @@ +# BrowserWindow + + `BrowserWindow` 类让你有创建一个浏览器窗口的权力。例如: + +```javascript +// In the main process. +const BrowserWindow = require('electron').BrowserWindow; + +// Or in the renderer process. +const BrowserWindow = require('electron').remote.BrowserWindow; + +var win = new BrowserWindow({ width: 800, height: 600, show: false }); +win.on('closed', function() { + win = null; +}); + +win.loadURL('https://github.com'); +win.show(); +``` + +你也可以不通过chrome创建窗口,使用 +[Frameless Window](frameless-window.md) API. + +## Class: BrowserWindow + +`BrowserWindow` 是一个 +[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +通过 `options` 可以创建一个具有本质属性的 `BrowserWindow` . + +### `new BrowserWindow([options])` + +* `options` Object + * `width` Integer - 窗口宽度,单位像素. 默认是 `800`. + * `height` Integer - 窗口高度,单位像素. 默认是 `600`. + * `x` Integer - 窗口相对于屏幕的左偏移位置.默认居中. + * `y` Integer - 窗口相对于屏幕的顶部偏移位置.默认居中. + * `useContentSize` Boolean - `width` 和 `height` 使用web网页size, 这意味着实际窗口的size应该包括窗口框架的size,稍微会大一点,默认为 `false`. + * `center` Boolean - 窗口屏幕居中. + * `minWidth` Integer - 窗口最小宽度,默认为 `0`. + * `minHeight` Integer - 窗口最小高度,默认为 `0`. + * `maxWidth` Integer - 窗口最大宽度,默认无限制. + * `maxHeight` Integer - 窗口最大高度,默认无限制. + * `resizable` Boolean - 是否可以改变窗口size,默认为 `true`. + * `movable` Boolean - 窗口是否可以拖动. 在 Linux 上无效. 默认为 `true`. + * `minimizable` Boolean - 窗口是否可以最小化. 在 Linux 上无效. 默认为 `true`. + * `maximizable` Boolean - 窗口是否可以最大化. 在 Linux 上无效. 默认为 `true`. + * `closable` Boolean - 窗口是否可以关闭. 在 Linux 上无效. 默认为 `true`. + * `alwaysOnTop` Boolean - 窗口是否总是显示在其他窗口之前. 在 Linux 上无效. 默认为 `false`. + * `fullscreen` Boolean - 窗口是否可以全屏幕. 当明确设置值为When `false` ,全屏化按钮将会隐藏,在 OS X 将禁用. 默认 `false`. + * `fullscreenable` Boolean - 在 OS X 上,全屏化按钮是否可用,默认为 `true`. + * `skipTaskbar` Boolean - 是否在人物栏中显示窗口. 默认是`false`. + * `kiosk` Boolean - kiosk 方式. 默认为 `false`. + * `title` String - 窗口默认title. 默认 `"Electron"`. + * `icon` [NativeImage](native-image.md) - 窗口图标, 如果不设置,窗口将使用可用的默认图标. + * `show` Boolean - 窗口创建的时候是否显示. 默认为 `true`. + * `frame` Boolean - 指定 `false` 来创建一个 + [Frameless Window](frameless-window.md). 默认为 `true`. + * `acceptFirstMouse` Boolean - 是否允许单击web view来激活窗口 . 默认为 `false`. + * `disableAutoHideCursor` Boolean - 当 typing 时是否隐藏鼠标.默认 `false`. + * `autoHideMenuBar` Boolean - 除非点击 `Alt`,否则隐藏菜单栏.默认为 `false`. + * `enableLargerThanScreen` Boolean - 是否允许允许改变窗口大小大于屏幕. 默认是 `false`. + * `backgroundColor` String -窗口的 background color 值为十六进制,如 `#66CD00` 或 `#FFF` 或 `#80FFFFFF` (支持透明度). 默认为在 Linux 和 Windows 上为 + `#000` (黑色) , Mac上为 `#FFF`(或透明). + * `hasShadow` Boolean - 窗口是否有阴影. 只在 OS X 上有效. 默认为 `true`. + * `darkTheme` Boolean - 为窗口使用 dark 主题, 只在一些拥有 GTK+3 桌面环境上有效. 默认为 `false`. + * `transparent` Boolean - 窗口 [透明](frameless-window.md). + 默认为 `false`. + * `type` String - 窗口type, 默认普通窗口. 下面查看更多. + * `titleBarStyle` String - 窗口标题栏样式. 下面查看更多. + * `webPreferences` Object - 设置界面特性. 下面查看更多. + +`type` 的值和效果不同平台展示效果不同,具体: + +* Linux, 可用值为 `desktop`, `dock`, `toolbar`, `splash`, + `notification`. +* OS X, 可用值为 `desktop`, `textured`. + * `textured` type 添加金属梯度效果 + (`NSTexturedBackgroundWindowMask`). + * `desktop` 设置窗口在桌面背景窗口水平 + (`kCGDesktopWindowLevel - 1`). 注意桌面窗口不可聚焦, 不可不支持键盘和鼠标事件, 但是可以使用 `globalShortcut` 来解决输入问题. + +`titleBarStyle` 只在 OS X 10.10 Yosemite 或更新版本上支持. +可用值: + +* `default` 以及无值, 显示在 Mac 标题栏上为不透明的标准灰色. +* `hidden` 隐藏标题栏,内容充满整个窗口, 然后它依然在左上角,仍然受标准窗口控制. +* `hidden-inset`主体隐藏,显示小的控制按钮在窗口边缘. + +`webPreferences` 参数是个对象,它的属性: + +* `nodeIntegration` Boolean - 是否完整支持node. 默认为 `true`. +* `preload` String - 界面的其它脚本运行之前预先加载一个指定脚本. 这个脚本将一直可以使用 node APIs 无论 node integration 是否开启. 脚本路径为绝对路径. + 当 node integration 关闭, 预加载的脚本将从全局范围重新引入node的全局引用标志. 查看例子 + [here](process.md#event-loaded). +* `session` [Session](session.md#class-session) - 设置界面session. 而不是直接忽略session对象 , 也可用 `partition` 来代替, 它接受一个 partition 字符串. 当同时使用 `session` 和 `partition`, `session` 优先级更高. + 默认使用默认 session. +* `partition` String - 通过session的partition字符串来设置界面session. 如果 `partition` 以 `persist:` 开头, 这个界面将会为所有界面使用相同的 `partition`. 如果没有 `persist:` 前缀, 界面使用历史session. 通过分享同一个 `partition`, 所有界面使用相同的session. 默认使用默认 session. +* `zoomFactor` Number - 界面默认缩放值, `3.0` 表示 + `300%`. 默认 `1.0`. +* `javascript` Boolean - 开启javascript支持. 默认为`true`. +* `webSecurity` Boolean - 当设置为 `false`, 它将禁用相同地方的规则 (通常测试服), 并且如果有2个非用户设置的参数,就设置 + `allowDisplayingInsecureContent` 和 `allowRunningInsecureContent` 的值为 + `true`. 默认为 `true`. +* `allowDisplayingInsecureContent` Boolean -允许一个使用 https的界面来展示由 http URLs 传过来的资源. 默认`false`. +* `allowRunningInsecureContent` Boolean - Boolean -允许一个使用 https的界面来渲染由 http URLs 提交的html,css,javascript. 默认为 `false`. +* `images` Boolean - 开启图片使用支持. 默认 `true`. +* `textAreasAreResizable` Boolean - textArea 可以编辑. 默认为 `true`. +* `webgl` Boolean - 开启 WebGL 支持. 默认为 `true`. +* `webaudio` Boolean - 开启 WebAudio 支持. 默认为 `true`. +* `plugins` Boolean - 是否开启插件支持. 默认为 `false`. +* `experimentalFeatures` Boolean - 开启 Chromium 的 可测试 特性. + 默认为 `false`. +* `experimentalCanvasFeatures` Boolean - 开启 Chromium 的 canvas 可测试特性. 默认为 `false`. +* `directWrite` Boolean - 开启窗口的 DirectWrite font 渲染系统. 默认为 `true`. +* `blinkFeatures` String - 以 `,` 分隔的特性列表, 如 + `CSSVariables,KeyboardEventKey`. 被支持的所有特性可在 [setFeatureEnabledFromString][blink-feature-string] + 中找到. +* `defaultFontFamily` Object - 设置 font-family 默认字体. + * `standard` String - 默认为 `Times New Roman`. + * `serif` String - 默认为 `Times New Roman`. + * `sansSerif` String - 默认为 `Arial`. + * `monospace` String - 默认为 `Courier New`. +* `defaultFontSize` Integer - 默认为 `16`. +* `defaultMonospaceFontSize` Integer - 默认为 `13`. +* `minimumFontSize` Integer - 默认为 `0`. +* `defaultEncoding` String - 默认为 `ISO-8859-1`. + +## 事件 + + `BrowserWindow` 对象可触发下列事件: + +**注意:** 一些事件只能在特定os环境中触发,已经尽可能地标出. + +### Event: 'page-title-updated' + +返回: + +* `event` Event + +当文档改变标题时触发,使用 `event.preventDefault()` 可以阻止原窗口的标题改变. + +### Event: 'close' + +返回: + +* `event` Event + +在窗口要关闭的时候触发. 它在DOM的 `beforeunload` and `unload` 事件之前触发.使用 `event.preventDefault()` 可以取消这个操作 + +通常你想通过 `beforeunload` 处理器来决定是否关闭窗口,但是它也会在窗口重载的时候被触发. 在 Electron 中,返回一个空的字符串或 `false` 可以取消关闭.例如: + +```javascript +window.onbeforeunload = function(e) { + console.log('I do not want to be closed'); + + // Unlike usual browsers, in which a string should be returned and the user is + // prompted to confirm the page unload, Electron gives developers more options. + // Returning empty string or false would prevent the unloading now. + // You can also use the dialog API to let the user confirm closing the application. + e.returnValue = false; +}; +``` + +### Event: 'closed' + +当窗口已经关闭的时候触发.当你接收到这个事件的时候,你应当删除对已经关闭的窗口的引用对象和避免再次使用它. + +### Event: 'unresponsive' + +在界面卡死的时候触发事件. + +### Event: 'responsive' + +在界面恢复卡死的时候触发. + +### Event: 'blur' + +在窗口失去焦点的时候触发. + +### Event: 'focus' + +在窗口获得焦点的时候触发. + +### Event: 'maximize' + +在窗口最大化的时候触发. + +### Event: 'unmaximize' + +在窗口退出最大化的时候触发. + +### Event: 'minimize' + +在窗口最小化的时候触发. + +### Event: 'restore' + +在窗口从最小化恢复的时候触发. + +### Event: 'resize' + +在窗口size改变的时候触发. + +### Event: 'move' + +在窗口移动的时候触发. + +注意:在 OS X 中别名为 `moved`. + +### Event: 'moved' _OS X_ + +在窗口移动的时候触发. + +### Event: 'enter-full-screen' + +在的窗口进入全屏状态时候触发. + +### Event: 'leave-full-screen' + +在的窗口退出全屏状态时候触发. + +### Event: 'enter-html-full-screen' + +在的窗口通过 html api 进入全屏状态时候触发. + +### Event: 'leave-html-full-screen' + +在的窗口通过 html api 退出全屏状态时候触发. + +### Event: 'app-command' _Windows_ + +在请求一个[App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx)的时候触发. +典型的是键盘媒体或浏览器命令, Windows上的 "Back" 按钮用作鼠标也会触发. + +```js +someWindow.on('app-command', function(e, cmd) { + // Navigate the window back when the user hits their mouse back button + if (cmd === 'browser-backward' && someWindow.webContents.canGoBack()) { + someWindow.webContents.goBack(); + } +}); +``` + +### Event: 'scroll-touch-begin' _OS X_ + +在滚动条事件开始的时候触发. + +### Event: 'scroll-touch-end' _OS X_ + +在滚动条事件结束的时候触发. + +## 方法 + +`BrowserWindow` 对象有如下方法: + +### `BrowserWindow.getAllWindows()` + +返回一个所有已经打开了窗口的对象数组. + +### `BrowserWindow.getFocusedWindow()` + +返回应用当前获得焦点窗口,如果没有就返回 `null`. + +### `BrowserWindow.fromWebContents(webContents)` + +* `webContents` [WebContents](web-contents.md) + +根据 `webContents` 查找窗口. + +### `BrowserWindow.fromId(id)` + +* `id` Integer + +根据 id 查找窗口. + +### `BrowserWindow.addDevToolsExtension(path)` + +* `path` String + +添加位于 `path` 的开发者工具栏扩展,并且返回扩展项的名字. + +这个扩展会被添加到历史,所以只需要使用这个API一次,这个api不可用作编程使用. + +### `BrowserWindow.removeDevToolsExtension(name)` + +* `name` String + +删除开发者工具栏名为 `name` 的扩展. + +## 实例属性 + +使用 `new BrowserWindow` 创建的实例对象,有如下属性: + +```javascript +// In this example `win` is our instance +var win = new BrowserWindow({ width: 800, height: 600 }); +``` + +### `win.webContents` + +这个窗口的 `WebContents` 对象,所有与界面相关的事件和方法都通过它完成的. + +查看 [`webContents` documentation](web-contents.md) 的方法和事件. + +### `win.id` + +窗口的唯一id. + +## 实例方法 + +使用 `new BrowserWindow` 创建的实例对象,有如下方法: + +**注意:** 一些方法只能在特定os环境中调用,已经尽可能地标出. + +### `win.destroy()` + +强制关闭窗口, `unload` and `beforeunload` 不会触发,并且 `close` 也不会触发, 但是它保证了 `closed` 触发. + +### `win.close()` + +尝试关闭窗口,这与用户点击关闭按钮的效果一样. 虽然网页可能会取消关闭,查看 [close event](#event-close). + +### `win.focus()` + +窗口获得焦点. + +### `win.isFocused()` + +返回 boolean, 窗口是否获得焦点. + +### `win.show()` + +展示并且使窗口获得焦点. + +### `win.showInactive()` + +展示窗口但是不获得焦点. + +### `win.hide()` + +隐藏窗口. + +### `win.isVisible()` + +返回 boolean, 窗口是否可见. + +### `win.maximize()` + +窗口最大化. + +### `win.unmaximize()` + +取消窗口最大化. + +### `win.isMaximized()` + +返回 boolean, 窗口是否最大化. + +### `win.minimize()` + +窗口最小化. 在一些os中,它将在dock中显示. + +### `win.restore()` + +将最小化的窗口恢复为之前的状态. + +### `win.isMinimized()` + +返回 boolean, 窗口是否最小化. + +### `win.setFullScreen(flag)` + +* `flag` Boolean + +设置是否全屏. + +### `win.isFullScreen()` + +返回 boolean, 窗口是否全屏化. + +### `win.setAspectRatio(aspectRatio[, extraSize])` _OS X_ + +* `aspectRatio` 维持部分视图内容窗口的高宽比值. +* `extraSize` Object (可选) - 维持高宽比值时不包含的额外size. + * `width` Integer + * `height` Integer + +由一个窗口来维持高宽比值. `extraSize` 允许开发者使用它,它的单位为像素,不包含在 `aspectRatio` 中.这个 API 可用来区分窗口的size和内容的size . + +想象一个普通可控的HD video 播放器窗口. 假如左边缘有15控制像素,右边缘有25控制像素,在播放器下面有50控制像素.为了在播放器内保持一个 16:9 的高宽比例,我们可以调用这个api传入参数16/9 and +[ 40, 50 ].第二个参数不管网页中的额外的宽度和高度在什么位置,只要它们存在就行.只需要把网页中的所有额外的高度和宽度加起来就行. + +### `win.setBounds(options[, animate])` + +* `options` Object + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer +* `animate` Boolean (可选) _OS X_ + +重新设置窗口的宽高值,并且移动到指定的 `x`, `y` 位置. + +### `win.getBounds()` + +返回一个对象,它包含了窗口的宽,高,x坐标,y坐标. + +### `win.setSize(width, height[, animate])` + +* `width` Integer +* `height` Integer +* `animate` Boolean (可选) _OS X_ + +重新设置窗口的宽高值. + +### `win.getSize()` + +返回一个数组,它包含了窗口的宽,高. + +### `win.setContentSize(width, height[, animate])` + +* `width` Integer +* `height` Integer +* `animate` Boolean (可选) _OS X_ + +重新设置窗口客户端的宽高值(例如网页界面). + +### `win.getContentSize()` + +返回一个数组,它包含了窗口客户端的宽,高. + +### `win.setMinimumSize(width, height)` + +* `width` Integer +* `height` Integer + +设置窗口最小化的宽高值. + +### `win.getMinimumSize()` + +返回一个数组,它包含了窗口最小化的宽,高. + +### `win.setMaximumSize(width, height)` + +* `width` Integer +* `height` Integer + +设置窗口最大化的宽高值. + +### `win.getMaximumSize()` + +返回一个数组,它包含了窗口最大化的宽,高. + +### `win.setResizable(resizable)` + +* `resizable` Boolean + +设置窗口是否可以被用户改变size. + +### `win.isResizable()` + +返回 boolean,窗口是否可以被用户改变size. + +### `win.setMovable(movable)` _OS X_ _Windows_ + +* `movable` Boolean + +设置窗口是否可以被用户拖动. Linux 无效. + +### `win.isMovable()` _OS X_ _Windows_ + +返回 boolean,窗口是否可以被用户拖动. Linux 总是返回 `true`. + +### `win.setMinimizable(minimizable)` _OS X_ _Windows_ + +* `minimizable` Boolean + +设置窗口是否可以最小化. Linux 无效. + +### `win.isMinimizable()` _OS X_ _Windows_ + +返回 boolean,窗口是否可以最小化. Linux 总是返回 `true`. + +### `win.setMaximizable(maximizable)` _OS X_ _Windows_ + +* `maximizable` Boolean + +设置窗口是否可以最大化. Linux 无效. + +### `win.isMaximizable()` _OS X_ _Windows_ + +返回 boolean,窗口是否可以最大化. Linux 总是返回 `true`. + +### `win.setFullScreenable(fullscreenable)` + +* `fullscreenable` Boolean + +设置点击最大化按钮是否可以全屏或最大化窗口. + +### `win.isFullScreenable()` + +返回 boolean,点击最大化按钮是否可以全屏或最大化窗口. + +### `win.setClosable(closable)` _OS X_ _Windows_ + +* `closable` Boolean + +设置窗口是否可以人为关闭. Linux 无效. + +### `win.isClosable()` _OS X_ _Windows_ + +返回 boolean,窗口是否可以人为关闭. Linux 总是返回 `true`. + +### `win.setAlwaysOnTop(flag)` + +* `flag` Boolean + +是否设置这个窗口始终在其他窗口之上.设置之后,这个窗口仍然是一个普通的窗口,不是一个不可以获得焦点的工具箱窗口. + +### `win.isAlwaysOnTop()` + +返回 boolean,当前窗口是否始终在其它窗口之前. + +### `win.center()` + +窗口居中. + +### `win.setPosition(x, y[, animate])` + +* `x` Integer +* `y` Integer +* `animate` Boolean (可选) _OS X_ + +移动窗口到对应的 `x` and `y` 坐标. + +### `win.getPosition()` + +返回一个包含当前窗口位置的数组. + +### `win.setTitle(title)` + +* `title` String + +改变原窗口的title. + +### `win.getTitle()` + +返回原窗口的title. + +**注意:** 界面title可能和窗口title不相同. + +### `win.flashFrame(flag)` + +* `flag` Boolean + +开始或停止显示窗口来获得用户的关注. + +### `win.setSkipTaskbar(skip)` + +* `skip` Boolean + +让窗口不在任务栏中显示. + +### `win.setKiosk(flag)` + +* `flag` Boolean + +进入或离开 kiosk 模式. + +### `win.isKiosk()` + +返回 boolean,是否进入或离开 kiosk 模式. + +### `win.getNativeWindowHandle()` + +以 `Buffer` 形式返回这个具体平台的窗口的句柄. + +windows上句柄类型为 `HWND` ,OS X `NSView*` , Linux `Window`. + +### `win.hookWindowMessage(message, callback)` _Windows_ + +* `message` Integer +* `callback` Function + +拦截windows 消息,在 WndProc 接收到消息时触发 `callback`函数. + +### `win.isWindowMessageHooked(message)` _Windows_ + +* `message` Integer + +返回 `true` or `false` 来代表是否拦截到消息. + +### `win.unhookWindowMessage(message)` _Windows_ + +* `message` Integer + +不拦截窗口消息. + +### `win.unhookAllWindowMessages()` _Windows_ + +窗口消息全部不拦截. + +### `win.setRepresentedFilename(filename)` _OS X_ + +* `filename` String + +设置窗口当前文件路径,并且将这个文件的图标放在窗口标题栏上. + +### `win.getRepresentedFilename()` _OS X_ + +获取窗口当前文件路径. + +### `win.setDocumentEdited(edited)` _OS X_ + +* `edited` Boolean + +明确指出窗口文档是否可以编辑,如果可以编辑则将标题栏的图标变成灰色. + +### `win.isDocumentEdited()` _OS X_ + +返回 boolean,当前窗口文档是否可编辑. + +### `win.focusOnWebView()` + +### `win.blurWebView()` + +### `win.capturePage([rect, ]callback)` + +* `rect` Object (可选) - 捕获Page位置 + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer +* `callback` Function + +捕获 `rect` 中的page 的快照.完成后将调用回调函数 `callback` 并返回 `image` . `image` 是存储了快照信息的[NativeImage](native-image.md)实例.如果不设置 `rect` 则将捕获所有可见page. + +### `win.print([options])` + +类似 `webContents.print([options])` + +### `win.printToPDF(options, callback)` + +类似 `webContents.printToPDF(options, callback)` + +### `win.loadURL(url[, options])` + +类似 `webContents.loadURL(url[, options])`. + +### `win.reload()` + +类似 `webContents.reload`. + +### `win.setMenu(menu)` _Linux_ _Windows_ + +* `menu` Menu + +设置菜单栏的 `menu` ,设置它为 `null` 则表示不设置菜单栏. + +### `win.setProgressBar(progress)` + +* `progress` Double + +在进度条中设置进度值,有效范围 [0, 1.0]. + +当进度小于0时则不显示进度; +当进度大于0时显示结果不确定. + +在libux上,只支持Unity桌面环境,需要指明 `*.desktop` 文件并且在 `package.json` 中添加文件名字.默认它为 `app.getName().desktop`. + +### `win.setOverlayIcon(overlay, description)` _Windows 7+_ + +* `overlay` [NativeImage](native-image.md) - 在底部任务栏右边显示图标. +* `description` String - 描述. + +向当前任务栏添加一个 16 x 16 像素的图标,通常用来覆盖一些应用的状态,或者直接来提示用户. + +### `win.setHasShadow(hasShadow)` _OS X_ + +* `hasShadow` (Boolean) + +设置窗口是否应该有阴影.在Windows和Linux系统无效. + +### `win.hasShadow()` _OS X_ + +返回 boolean,设置窗口是否有阴影.在Windows和Linux系统始终返回 +`true`. + +### `win.setThumbarButtons(buttons)` _Windows 7+_ + +* `buttons` Array + +在窗口的任务栏button布局出为缩略图添加一个有特殊button的缩略图工具栏. 返回一个 `Boolean` 对象来指示是否成功添加这个缩略图工具栏. + +因为空间有限,缩略图工具栏上的 button 数量不应该超过7个.一旦设置了,由于平台限制,就不能移动它了.但是你可使用一个空数组来调用api来清除 buttons . + +所有 `buttons` 是一个 `Button` 对象数组: + +* `Button` Object + * `icon` [NativeImage](native-image.md) - 在工具栏上显示的图标. + * `click` Function + * `tooltip` String (可选) - tooltip 文字. + * `flags` Array (可选) - 控制button的状态和行为. 默认它是 `['enabled']`. + +`flags` 是一个数组,它包含下面这些 `String`s: + +* `enabled` - button 为激活状态并且开放给用户. +* `disabled` -button 不可用. 目前它有一个可见的状态来表示它不会响应你的行为. +* `dismissonclick` - 点击button,这个缩略窗口直接关闭. +* `nobackground` - 不绘制边框,仅仅使用图像. +* `hidden` - button 对用户不可见. +* `noninteractive` - button 可用但是不可响应; 也不显示按下的状态. 它的值意味着这是一个在通知单使用 button 的实例. + +### `win.showDefinitionForSelection()` _OS X_ + +在界面查找选中文字时显示弹出字典. + +### `win.setAutoHideMenuBar(hide)` + +* `hide` Boolean + +设置窗口的菜单栏是否可以自动隐藏. 一旦设置了,只有当用户按下 `Alt` 键时则显示. + +如果菜单栏已经可见,调用 `setAutoHideMenuBar(true)` 则不会立刻隐藏. + +### `win.isMenuBarAutoHide()` + +返回 boolean,窗口的菜单栏是否可以自动隐藏. + +### `win.setMenuBarVisibility(visible)` + +* `visible` Boolean + +设置菜单栏是否可见.如果菜单栏自动隐藏,用户仍然可以按下 `Alt` 键来显示. + +### `win.isMenuBarVisible()` + +返回 boolean,菜单栏是否可见. + +### `win.setVisibleOnAllWorkspaces(visible)` + +* `visible` Boolean + +设置窗口是否在所有地方都可见. + +**注意:** 这个api 在windows无效. + +### `win.isVisibleOnAllWorkspaces()` + +返回 boolean,窗口是否在所有地方都可见. + +**注意:** 在 windows上始终返回 false. + +### `win.setIgnoreMouseEvents(ignore)` _OS X_ + +* `ignore` Boolean + +忽略窗口的所有鼠标事件. + +[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 \ No newline at end of file diff --git a/docs-translations/zh-CN/api/chrome-command-line-switches.md b/docs-translations/zh-CN/api/chrome-command-line-switches.md new file mode 100644 index 00000000000..78b84b77276 --- /dev/null +++ b/docs-translations/zh-CN/api/chrome-command-line-switches.md @@ -0,0 +1,140 @@ +# 支持的 Chrome 命令行开关 + +这页列出了Chrome浏览器和Electron支持的命令行开关. 你也可以在[app][app]模块的[ready][ready]事件发出之前使用[app.commandLine.appendSwitch][append-switch] 来添加它们到你应用的main脚本里面: + +```javascript +const app = require('electron').app; +app.commandLine.appendSwitch('remote-debugging-port', '8315'); +app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1'); + +app.on('ready', function() { + // Your code here +}); +``` + +## --client-certificate=`path` + +设置客户端的证书文件 `path` . + +## --ignore-connections-limit=`domains` + +忽略用 `,` 分隔的 `domains` 列表的连接限制. + +## --disable-http-cache + +禁止请求 HTTP 时使用磁盘缓存. + +## --remote-debugging-port=`port` + +在指定的 `端口` 通过 HTTP 开启远程调试. + +## --js-flags=`flags` + +指定引擎过渡到 JS 引擎. + +在启动Electron时,如果你想在主进程中激活 `flags` ,它将被转换. + +```bash +$ electron --js-flags="--harmony_proxies --harmony_collections" your-app +``` + +## --proxy-server=`address:port` + +使用一个特定的代理服务器,它将比系统设置的优先级更高.这个开关只有在使用 HTTP 协议时有效,它包含 HTTPS 和 WebSocket 请求. 值得注意的是,不是所有的代理服务器都支持 HTTPS 和 WebSocket 请求. + +## --proxy-bypass-list=`hosts` + +让 Electron 使用(原文:bypass) 提供的以 semi-colon 分隔的hosts列表的代理服务器.这个开关只有在使用 `--proxy-server` 时有效. + +例如: + +```javascript +app.commandLine.appendSwitch('proxy-bypass-list', ';*.google.com;*foo.com;1.2.3.4:5678') +``` + + +将会为所有的hosts使用代理服务器,除了本地地址 (`localhost`, +`127.0.0.1` etc.), `google.com` 子域, 以 `foo.com` 结尾的hosts,和所有类似 `1.2.3.4:5678`的. + +## --proxy-pac-url=`url` + +在指定的 `url` 上使用 PAC 脚本. + +## --no-proxy-server + +不使用代理服务并且总是使用直接连接.忽略所有的合理代理标志. + +## --host-rules=`rules` + +一个逗号分隔的 `rule` 列表来控制主机名如何映射. + +例如: + +* `MAP * 127.0.0.1` 强制所有主机名映射到 127.0.0.1 +* `MAP *.google.com proxy` 强制所有 google.com 子域 使用 "proxy". +* `MAP test.com [::1]:77` 强制 "test.com" 使用 IPv6 回环地址. 也强制使用端口 77. +* `MAP * baz, EXCLUDE www.google.com` 重新全部映射到 "baz", 除了 + "www.google.com". + +这些映射适用于终端网络请求 +(TCP 连接 +和 主机解析 以直接连接的方式, 和 `CONNECT` 以代理连接, 还有 终端 host 使用 `SOCKS` 代理连接). + +## --host-resolver-rules=`rules` + +类似 `--host-rules` ,但是 `rules` 只适合主机解析. + +## --ignore-certificate-errors + +忽略与证书相关的错误. + +## --ppapi-flash-path=`path` + +设置Pepper Flash插件的路径 `path` . + +## --ppapi-flash-version=`version` + +设置Pepper Flash插件版本号. + +## --log-net-log=`path` + +使网络日志事件能够被读写到 `path`. + +## --ssl-version-fallback-min=`version` + +设置最简化的 SSL/TLS 版本号 ("tls1", "tls1.1" or "tls1.2"),TLS 可接受回退. + +## --cipher-suite-blacklist=`cipher_suites` + +指定逗号分隔的 SSL 密码套件 列表实效. + +## --disable-renderer-backgrounding + +防止 Chromium 降低隐藏的渲染进程优先级. + +这个标志对所有渲染进程全局有效,如果你只想在一个窗口中禁止使用,你可以采用 hack 方法[playing silent audio][play-silent-audio]. + +## --enable-logging + +打印 Chromium 信息输出到控制台. + +如果在用户应用加载完成之前解析`app.commandLine.appendSwitch` ,这个开关将实效,但是你可以设置 `ELECTRON_ENABLE_LOGGING` 环境变量来达到相同的效果. + +## --v=`log_level` + +设置默认最大活跃 V-logging 标准; 默认为 0.通常 V-logging 标准值为肯定值. + +这个开关只有在 `--enable-logging` 开启时有效. + +## --vmodule=`pattern` + +赋予每个模块最大的 V-logging levels 来覆盖 `--v` 给的值.E.g. `my_module=2,foo*=3` 会改变所有源文件 `my_module.*` and `foo*.*` 的代码中的 logging level . + +任何包含向前的(forward slash)或者向后的(backward slash)模式将被测试用于阻止整个路径名,并且不仅是E.g模块.`*/foo/bar/*=2` 将会改变所有在 `foo/bar` 下的源文件代码中的 logging level . + +这个开关只有在 `--enable-logging` 开启时有效. + +[app]: app.md +[append-switch]: app.md#appcommandlineappendswitchswitch-value +[ready]: app.md#event-ready +[play-silent-audio]: https://github.com/atom/atom/pull/9485/files diff --git a/docs-translations/zh-CN/api/clipboard.md b/docs-translations/zh-CN/api/clipboard.md new file mode 100644 index 00000000000..77aa6d65c0b --- /dev/null +++ b/docs-translations/zh-CN/api/clipboard.md @@ -0,0 +1,117 @@ +# clipboard + +`clipboard` 模块提供方法来供复制和粘贴操作 . +下面例子展示了如何将一个字符串写道 clipboard 上: + +```javascript +const clipboard = require('electron').clipboard; +clipboard.writeText('Example String'); +``` + +在 X Window 系统上, 有一个可选的 clipboard. 你可以为每个方法使用 `selection` 来控制它: + +```javascript +clipboard.writeText('Example String', 'selection'); +console.log(clipboard.readText('selection')); +``` + +## 方法 + +`clipboard` 模块有以下方法: + +**注意:** 测试 APIs 已经标明,并且在将来会被删除 . + +### `clipboard.readText([type])` + +* `type` String (可选) + +以纯文本形式从 clipboard 返回内容 . + +### `clipboard.writeText(text[, type])` + +* `text` String +* `type` String (可选) + +以纯文本形式向 clipboard 添加内容 . + +### `clipboard.readHtml([type])` + +* `type` String (可选) + +返回 clipboard 中的标记内容. + +### `clipboard.writeHtml(markup[, type])` + +* `markup` String +* `type` String (可选) + +向 clipboard 添加 `markup` 内容 . + +### `clipboard.readImage([type])` + +* `type` String (可选) + +从 clipboard 中返回 [NativeImage](native-image.md) 内容. + +### `clipboard.writeImage(image[, type])` + +* `image` [NativeImage](native-image.md) +* `type` String (可选) + +向 clipboard 中写入 `image` . + +### `clipboard.readRtf([type])` + +* `type` String (可选) + +从 clipboard 中返回 RTF 内容. + +### `clipboard.writeRtf(text[, type])` + +* `text` String +* `type` String (可选) + +向 clipboard 中写入 RTF 格式的 `text` . + +### `clipboard.clear([type])` + +* `type` String (可选) + +清空 clipboard 内容. + +### `clipboard.availableFormats([type])` + +* `type` String (可选) + +返回 clipboard 支持的格式数组 . + +### `clipboard.has(data[, type])` _Experimental_ + +* `data` String +* `type` String (可选) + +返回 clipboard 是否支持指定 `data` 的格式. + +```javascript +console.log(clipboard.has('

selection

')); +``` + +### `clipboard.read(data[, type])` _Experimental_ + +* `data` String +* `type` String (可选) + +读取 clipboard 的 `data`. + +### `clipboard.write(data[, type])` + +* `data` Object + * `text` String + * `html` String + * `image` [NativeImage](native-image.md) +* `type` String (可选) + +```javascript +clipboard.write({text: 'test', html: "test"}); +``` +向 clipboard 写入 `data` . \ No newline at end of file diff --git a/docs-translations/zh-CN/api/content-tracing.md b/docs-translations/zh-CN/api/content-tracing.md new file mode 100644 index 00000000000..fd1edc9bc2f --- /dev/null +++ b/docs-translations/zh-CN/api/content-tracing.md @@ -0,0 +1,129 @@ +# contentTracing + +`content-tracing` 模块是用来收集由底层的Chromium content 模块 产生的搜索数据. 这个模块不具备web接口,所有需要我们在chrome浏览器中添加 `chrome://tracing/` 来加载生成文件从而查看结果. + +```javascript +const contentTracing = require('electron').contentTracing; + +const options = { + categoryFilter: '*', + traceOptions: 'record-until-full,enable-sampling' +} + +contentTracing.startRecording(options, function() { + console.log('Tracing started'); + + setTimeout(function() { + contentTracing.stopRecording('', function(path) { + console.log('Tracing data recorded to ' + path); + }); + }, 5000); +}); +``` + +## 方法 + + `content-tracing` 模块的方法如下: + +### `contentTracing.getCategories(callback)` + +* `callback` Function + +获得一组分类组. 分类组可以更改为新的代码路径。 + +一旦所有的子进程都接受到了`getCategories`方法请求, 分类组将调用 `callback`. + +### `contentTracing.startRecording(options, callback)` + +* `options` Object + * `categoryFilter` String + * `traceOptions` String +* `callback` Function + +开始向所有进程进行记录.(recording) + +一旦收到可以开始记录的请求,记录将会立马启动并且在子进程是异步记录听的. 当所有的子进程都收到 `startRecording` 请求的时候,`callback` 将会被调用. + +`categoryFilter`是一个过滤器,它用来控制那些分类组应该被用来查找.过滤器应当有一个可选的 `-` 前缀来排除匹配的分类组.不允许同一个列表既是包含又是排斥. + +例子: + +* `test_MyTest*`, +* `test_MyTest*,test_OtherStuff`, +* `"-excluded_category1,-excluded_category2` + +`traceOptions` 控制着哪种查找应该被启动,这是一个用逗号分隔的列表.可用参数如下: + +* `record-until-full` +* `record-continuously` +* `trace-to-console` +* `enable-sampling` +* `enable-systrace` + +前3个参数是来查找记录模块,并且以后都互斥.如果在`traceOptions` 中超过一个跟踪 +记录模式,那最后一个的优先级最高.如果没有指明跟踪 +记录模式,那么它默认为 `record-until-full`. + +在 `traceOptions` 中的参数被解析应用之前,查找参数初始化默认为 (`record_mode` 设置为 +`record-until-full`, `enable_sampling` 和 `enable_systrace` 设置为 `false`). + +### `contentTracing.stopRecording(resultFilePath, callback)` + +* `resultFilePath` String +* `callback` Function + +停止对所有子进程的记录. + +子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.这有利于在通过 IPC 发送查找数据之前减小查找时的运行开销,这样做很有价值.因此,发送查找数据,我们应当异步通知所有子进程来截取任何待查找的数据. + +一旦所有子进程接收到了 `stopRecording` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件. + +如果 `resultFilePath` 不为空,那么将把查找数据写入其中,否则写入一个临时文件.实际文件路径如果不为空,则将调用 `callback` . + +### `contentTracing.startMonitoring(options, callback)` + +* `options` Object + * `categoryFilter` String + * `traceOptions` String +* `callback` Function + +开始向所有进程进行监听.(monitoring) + +一旦收到可以开始监听的请求,记录将会立马启动并且在子进程是异步记监听的. 当所有的子进程都收到 `startMonitoring` 请求的时候,`callback` 将会被调用. + +### `contentTracing.stopMonitoring(callback)` + +* `callback` Function + +停止对所有子进程的监听. + +一旦所有子进程接收到了 `stopMonitoring` 请求,将调用 `callback` . + +### `contentTracing.captureMonitoringSnapshot(resultFilePath, callback)` + +* `resultFilePath` String +* `callback` Function + +获取当前监听的查找数据. + +子进程通常缓存查找数据,并且仅仅将数据截取和发送给主进程.因为如果直接通过 IPC 来发送查找数据的代价昂贵,我们宁愿避免不必要的查找运行开销.因此,为了停止查找,我们应当异步通知所有子进程来截取任何待查找的数据. + +一旦所有子进程接收到了 `captureMonitoringSnapshot` 请求,将调用 `callback` ,并且返回一个包含查找数据的文件. + +### `contentTracing.getTraceBufferUsage(callback)` + +* `callback` Function + +通过查找 buffer 进程来获取百分比最大使用量.当确定了TraceBufferUsage 的值确定的时候,就调用 `callback` . + +### `contentTracing.setWatchEvent(categoryName, eventName, callback)` + +* `categoryName` String +* `eventName` String +* `callback` Function + +任意时刻在任何进程上指定事件发生时将调用 `callback` . + +### `contentTracing.cancelWatchEvent()` + +取消 watch 事件. 如果启动查找,这或许会造成 watch 事件的回调函数 出错. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/crash-reporter.md b/docs-translations/zh-CN/api/crash-reporter.md new file mode 100644 index 00000000000..6f2de5e8459 --- /dev/null +++ b/docs-translations/zh-CN/api/crash-reporter.md @@ -0,0 +1,66 @@ +# crashReporter + +`crash-reporter` 模块开启发送应用崩溃报告. + +下面是一个自动提交崩溃报告给服务器的例子 : + +```javascript +const crashReporter = require('electron').crashReporter; + +crashReporter.start({ + productName: 'YourName', + companyName: 'YourCompany', + submitURL: 'https://your-domain.com/url-to-submit', + autoSubmit: true +}); +``` + +可以使用下面的项目来创建一个服务器,用来接收和处理崩溃报告 : + +* [socorro](https://github.com/mozilla/socorro) +* [mini-breakpad-server](https://github.com/atom/mini-breakpad-server) + +## 方法 + +`crash-reporter` 模块有如下方法: + +### `crashReporter.start(options)` + +* `options` Object + * `companyName` String + * `submitURL` String - 崩溃报告发送的路径,以post方式. + * `productName` String (可选) - 默认为 `Electron`. + * `autoSubmit` Boolean - 是否自动提交. + 默认为 `true`. + * `ignoreSystemCrashHandler` Boolean - 默认为 `false`. + * `extra` Object - 一个你可以定义的对象,附带在崩溃报告上一起发送 . 只有字符串属性可以被正确发送,不支持嵌套对象. + +只可以在使用其它 `crashReporter` APIs 之前使用这个方法. + +**注意:** 在 OS X, Electron 使用一个新的 `crashpad` 客户端, 与 Windows 和 Linux 的 `breakpad` 不同. 为了开启崩溃点搜集,你需要在主进程和其它每个你需要搜集崩溃报告的渲染进程中调用 `crashReporter.start` API 来初始化 `crashpad`. + +### `crashReporter.getLastCrashReport()` + +返回最后一个崩溃报告的日期和 ID.如果没有过崩溃报告发送过来,或者还没有开始崩溃报告搜集,将返回 `null` . + +### `crashReporter.getUploadedReports()` + +返回所有上载的崩溃报告,每个报告包含了上载日期和 ID. + +## crash-reporter Payload + +崩溃报告将发送下面 `multipart/form-data` `POST` 型的数据给 `submitURL` : + +* `ver` String - Electron 版本. +* `platform` String - 例如 'win32'. +* `process_type` String - 例如 'renderer'. +* `guid` String - 例如 '5e1286fc-da97-479e-918b-6bfb0c3d1c72' +* `_version` String - `package.json` 版本. +* `_productName` String - `crashReporter` `options` + 对象中的产品名字. +* `prod` String - 基础产品名字. 这种情况为 Electron. +* `_companyName` String - `crashReporter` `options` + 对象中的公司名字. +* `upload_file_minidump` File - 崩溃报告按照 `minidump` 的格式. +* `crashReporter` 中的 `extra` 对象的所有等级和一个属性. + `options` object \ No newline at end of file diff --git a/docs-translations/zh-CN/api/desktop-capturer.md b/docs-translations/zh-CN/api/desktop-capturer.md new file mode 100644 index 00000000000..954520d05ea --- /dev/null +++ b/docs-translations/zh-CN/api/desktop-capturer.md @@ -0,0 +1,64 @@ +# desktopCapturer + +`desktopCapturer` 模块可用来获取可用资源,这个资源可通过 `getUserMedia` 捕获得到. + +```javascript +// 在渲染进程中. +var desktopCapturer = require('electron').desktopCapturer; + +desktopCapturer.getSources({types: ['window', 'screen']}, function(error, sources) { + if (error) throw error; + for (var i = 0; i < sources.length; ++i) { + if (sources[i].name == "Electron") { + navigator.webkitGetUserMedia({ + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: sources[i].id, + minWidth: 1280, + maxWidth: 1280, + minHeight: 720, + maxHeight: 720 + } + } + }, gotStream, getUserMediaError); + return; + } + } +}); + +function gotStream(stream) { + document.querySelector('video').src = URL.createObjectURL(stream); +} + +function getUserMediaError(e) { + console.log('getUserMediaError'); +} +``` + +当调用 `navigator.webkitGetUserMedia` 时创建一个约束对象,如果使用 `desktopCapturer` 的资源,必须设置 `chromeMediaSource` 为 `"desktop"` ,并且 `audio` 为 `false`. + +如果你想捕获整个桌面的 audio 和 video,你可以设置 `chromeMediaSource` 为 `"screen"` ,和 `audio` 为 `true`. +当使用这个方法的时候,不可以指定一个 `chromeMediaSourceId`. + +## 方法 + +`desktopCapturer` 模块有如下方法: + +### `desktopCapturer.getSources(options, callback)` + +* `options` Object + * `types` Array - 一个 String 数组,列出了可以捕获的桌面资源类型, 可用类型为 `screen` 和 `window`. + * `thumbnailSize` Object (可选) - 建议缩略可被缩放的 size, 默认为 `{width: 150, height: 150}`. +* `callback` Function + +发起一个请求,获取所有桌面资源,当请求完成的时候使用 `callback(error, sources)` 调用 `callback` . + +`sources` 是一个 `Source` 对象数组, 每个 `Source` 表示了一个捕获的屏幕或单独窗口,并且有如下属性 : +* `id` String - 在 `navigator.webkitGetUserMedia` 中使用的捕获窗口或屏幕的 id . 格式为 `window:XX` 祸 + `screen:XX`,`XX` 是一个随机数. +* `name` String - 捕获窗口或屏幕的描述名 . 如果资源为屏幕,名字为 `Entire Screen` 或 `Screen `; 如果资源为窗口, 名字为窗口的标题. +* `thumbnail` [NativeImage](NativeImage.md) - 缩略图. + +**注意:** 不能保证 `source.thumbnail` 的 size 和 `options` 中的 `thumnbailSize` 一直一致. 它也取决于屏幕或窗口的缩放比例. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/dialog.md b/docs-translations/zh-CN/api/dialog.md new file mode 100644 index 00000000000..3e77eeaa2a5 --- /dev/null +++ b/docs-translations/zh-CN/api/dialog.md @@ -0,0 +1,94 @@ +# dialog + +`dialog` 模块提供了api来展示原生的系统对话框,例如打开文件框,alert框,所以web应用可以给用户带来跟系统应用相同的体验. + +对话框例子,展示了选择文件和目录: + +```javascript +var win = ...; // BrowserWindow in which to show the dialog +const dialog = require('electron').dialog; +console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); +``` + +**OS X 上的注意事项**: 如果你想像sheets一样展示对话框,只需要在`browserWindow` 参数中提供一个 `BrowserWindow` 的引用对象. + +## 方法 + +`dialog` 模块有以下方法: + +### `dialog.showOpenDialog([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (可选) +* `options` Object + * `title` String + * `defaultPath` String + * `filters` Array + * `properties` Array - 包含了对话框的特性值, 可以包含 `openFile`, `openDirectory`, `multiSelections` and + `createDirectory` +* `callback` Function (可选) + +成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`. + +`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: ['*'] } + ] +} +``` + +`extensions` 数组应当只包含扩展名,不应该包含通配符或'.'号 (例如 +`'png'` 正确,但是 `'.png'` 和 `'*.png'` 不正确). 展示全部文件的话, 使用 +`'*'` 通配符 (不支持其他通配符). + +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示. + +**注意:** 在 Windows 和 Linux ,一个打开的 dialog 不能既是文件选择框又是目录选择框, 所以如果在这些平台上设置 `properties` 的值为 +`['openFile', 'openDirectory']` , 将展示一个目录选择框. + +### `dialog.showSaveDialog([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (可选) +* `options` Object + * `title` String + * `defaultPath` String + * `filters` Array +* `callback` Function (可选) + +成功使用这个方法的话,就返回一个可供用户选择的文件路径数组,失败返回 `undefined`. + +`filters` 指定展示一个文件类型数组, 例子 +`dialog.showOpenDialog` . + +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(filenames)` 展示. + +### `dialog.showMessageBox([browserWindow, ]options[, callback])` + +* `browserWindow` BrowserWindow (可选) +* `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. 在 OS X 和 Windows 上, "Cancel" button 的索引值将一直是 `cancelId`, 不管之前是不是特别指出的. + * `noLink` Boolean - 在 Windows ,Electron 将尝试识别哪个button 是普通 button (如 "Cancel" 或 "Yes"), 然后再对话框中以链接命令(command links)方式展现其它的 button . 这能让对话框展示得很炫酷.如果你不喜欢这种效果,你可以设置 `noLink` 为 `true`. +* `callback` Function + +展示 message box, 它会阻塞进程,直到 message box 关闭为止.返回点击按钮的索引值. + +如果 `callback` 被调用, 将异步调用 API ,并且结果将用过 `callback(response)` 展示. + +### `dialog.showErrorBox(title, content)` + +展示一个传统的包含错误信息的对话框. + +在 `app` 模块触发 `ready` 事件之前,这个 api 可以被安全调用,通常它被用来在启动的早期阶段报告错误. 在 Linux 上,如果在 `app` 模块触发 `ready` 事件之前调用,message 将会被触发显示stderr,并且没有实际GUI 框显示. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/environment-variables.md b/docs-translations/zh-CN/api/environment-variables.md new file mode 100644 index 00000000000..0a35eb59dfd --- /dev/null +++ b/docs-translations/zh-CN/api/environment-variables.md @@ -0,0 +1,53 @@ +# 环境变量 + +一些 Electron 的行为受到环境变量的控制,因为他们的初始化比命令行和应用代码更早. + +POSIX shells 的例子: + +```bash +$ export ELECTRON_ENABLE_LOGGING=true +$ electron +``` + +Windows 控制台: + +```powershell +> set ELECTRON_ENABLE_LOGGING=true +> electron +``` + +## `ELECTRON_RUN_AS_NODE` + +类似node.js普通进程启动方式. + +## `ELECTRON_ENABLE_LOGGING` + +打印 Chrome 的内部日志到控制台. + +## `ELECTRON_LOG_ASAR_READS` + +当 Electron 读取 ASA 文档,把 read offset 和文档路径做日志记录到系统 `tmpdir`.结果文件将提供给 ASAR 模块来优化文档组织. + +## `ELECTRON_ENABLE_STACK_DUMPING` + +当 Electron 崩溃的时候,打印堆栈记录到控制台. + +如果 `crashReporter` 已经启动那么这个环境变量实效. + +## `ELECTRON_DEFAULT_ERROR_MODE` _Windows_ + +当 Electron 崩溃的时候,显示windows的崩溃对话框. + +如果 `crashReporter` 已经启动那么这个环境变量实效. + +## `ELECTRON_NO_ATTACH_CONSOLE` _Windows_ + +不可使用当前控制台. + +## `ELECTRON_FORCE_WINDOW_MENU_BAR` _Linux_ + +不可再 Linux 上使用全局菜单栏. + +## `ELECTRON_HIDE_INTERNAL_MODULES` + +关闭旧的内置模块如 `require('ipc')` 的通用模块. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/file-object.md b/docs-translations/zh-CN/api/file-object.md new file mode 100644 index 00000000000..ef320b36994 --- /dev/null +++ b/docs-translations/zh-CN/api/file-object.md @@ -0,0 +1,29 @@ +# `File`对象 + +为了让用户能够通过HTML5的file API直接操作本地文件,DOM的File接口提供了对本地文件的抽象。Electron在File接口中增加了一个path属性,它是文件在系统中的真实路径。 + +--- + +获取拖动到APP中文件的真实路径的例子: + +``` +
+ Drag your file here +
+ + +``` diff --git a/docs-translations/zh-CN/api/ipc-main.md b/docs-translations/zh-CN/api/ipc-main.md new file mode 100644 index 00000000000..aef9c454a06 --- /dev/null +++ b/docs-translations/zh-CN/api/ipc-main.md @@ -0,0 +1,85 @@ +# ipcMain + +`ipcMain` 模块是类 +[EventEmitter](https://nodejs.org/api/events.html) 的实例.当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息.从渲染进程发送过来的消息将触发事件. + +## 发送消息 + +同样也可以从主进程向渲染进程发送消息,查看更多 [webContents.send][web-contents-send] . + +* 发送消息,事件名为 `channel`. +* 回应同步消息, 你可以设置 `event.returnValue`. +* 回应异步消息, 你可以使用 + `event.sender.send(...)`. + +一个例子,在主进程和渲染进程之间发送和处理消息: + +```javascript +// In main process. +const ipcMain = require('electron').ipcMain; +ipcMain.on('asynchronous-message', function(event, arg) { + console.log(arg); // prints "ping" + event.sender.send('asynchronous-reply', 'pong'); +}); + +ipcMain.on('synchronous-message', function(event, arg) { + console.log(arg); // prints "ping" + event.returnValue = 'pong'; +}); +``` + +```javascript +// In renderer process (web page). +const ipcRenderer = require('electron').ipcRenderer; +console.log(ipcRenderer.sendSync('synchronous-message', 'ping')); // prints "pong" + +ipcRenderer.on('asynchronous-reply', function(event, arg) { + console.log(arg); // prints "pong" +}); +ipcRenderer.send('asynchronous-message', 'ping'); +``` + +## 监听消息 + +`ipcMain` 模块有如下监听事件方法: + +### `ipcMain.on(channel, listener)` + +* `channel` String +* `listener` Function + +监听 `channel`, 当新消息到达,将通过 `listener(event, args...)` 调用 `listener`. + +### `ipcMain.once(channel, listener)` + +* `channel` String +* `listener` Function + +为事件添加一个一次性用的`listener` 函数.这个 `listener` 只有在下次的消息到达 `channel` 时被请求调用,之后就被删除了. + +### `ipcMain.removeListener(channel, listener)` + +* `channel` String +* `listener` Function + +为特定的 `channel` 从监听队列中删除特定的 `listener` 监听者. + +### `ipcMain.removeAllListeners([channel])` + +* `channel` String (可选) + +删除所有监听者,或特指的 `channel` 的所有监听者. + +## 事件对象 + +传递给 `callback` 的 `event` 对象有如下方法: + +### `event.returnValue` + +将此设置为在一个同步消息中返回的值. + +### `event.sender` + +返回发送消息的 `webContents` ,你可以调用 `event.sender.send` 来回复异步消息,更多信息 [webContents.send][web-contents-send]. + +[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- \ No newline at end of file diff --git a/docs-translations/zh-CN/api/ipc-renderer.md b/docs-translations/zh-CN/api/ipc-renderer.md new file mode 100644 index 00000000000..beeaa6d7623 --- /dev/null +++ b/docs-translations/zh-CN/api/ipc-renderer.md @@ -0,0 +1,69 @@ +# ipcRenderer + +`ipcRenderer` 模块是一个 +[EventEmitter](https://nodejs.org/api/events.html) 类的实例. 它提供了有限的方法,你可以从渲染进程向主进程发送同步或异步消息. 也可以收到主进程的相应. + +查看 [ipcMain](ipc-main.md) 代码例子. + +## 消息监听 + +`ipcRenderer` 模块有下列方法来监听事件: + +### `ipcRenderer.on(channel, listener)` + +* `channel` String +* `listener` Function + +监听 `channel`, 当有新消息到达,使用 `listener(event, args...)` 调用 `listener` . + +### `ipcRenderer.once(channel, listener)` + +* `channel` String +* `listener` Function + +为这个事件添加一个一次性 `listener` 函数.这个 `listener` 将在下一次有新消息被发送到 `channel` 的时候被请求调用,之后就被删除了. + +### `ipcRenderer.removeListener(channel, listener)` + +* `channel` String +* `listener` Function + +从指定的 `channel` 中的监听者数组删除指定的 `listener` . + +### `ipcRenderer.removeAllListeners([channel])` + +* `channel` String (optional) + +删除所有的监听者,或者删除指定 `channel` 中的全部. + +## 发送消息 + +`ipcRenderer` 模块有如下方法来发送消息: + +### `ipcRenderer.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +通过 `channel` 向主进程发送异步消息,也可以发送任意参数.参数会被JSON序列化,之后就不会包含函数或原型链. + +主进程通过使用 `ipcMain` 模块来监听 `channel`,从而处理消息. + +### `ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +通过 `channel` 向主进程发送同步消息,也可以发送任意参数.参数会被JSON序列化,之后就不会包含函数或原型链. + +主进程通过使用 `ipcMain` 模块来监听 `channel`,从而处理消息, +通过 `event.returnValue` 来响应. + +__注意:__ 发送同步消息将会阻塞整个渲染进程,除非你知道你在做什么,否则就永远不要用它 . + +### `ipcRenderer.sendToHost(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +类似 `ipcRenderer.send` ,但是它的事件将发往 host page 的 `` 元素,而不是主进程. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/menu-item.md b/docs-translations/zh-CN/api/menu-item.md new file mode 100644 index 00000000000..3d5eceedcb9 --- /dev/null +++ b/docs-translations/zh-CN/api/menu-item.md @@ -0,0 +1,57 @@ +# 菜单项 +菜单项模块允许你向应用或[menu][1]添加选项。 + +查看[menu][1]例子。 + +## 类:MenuItem +使用下面的方法创建一个新的 `MenuItem` + +###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 - 定义给定的菜单的具体指定位置信息。 + +在创建菜单项时,如果有匹配的方法,建议指定 `role` 属性,不需要人为操作它的行为,这样菜单使用可以给用户最好的体验。 + + +`role`属性值可以为: + +* `undo` +* `redo` +* `cut` +* `copy` +* `paste` +* `selectall` +* `minimize` - 最小化当前窗口 +* `close` - 关闭当前窗口 + +在 OS X 上,`role` 还可以有以下值: + +* `about` - 匹配 `orderFrontStandardAboutPanel` 行为 +* `hide` - 匹配 `hide` 行为 +* `hideothers` - 匹配 `hideOtherApplications` 行为 +* `unhide` - 匹配 `unhideAllApplications` 行为 +* `front` - 匹配 `arrangeInFront` 行为 +* `window` - "Window" 菜单项 +* `help` - "Help" 菜单项 +* `services` - "Services" 菜单项 + + + + + + + [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 diff --git a/docs-translations/zh-CN/api/menu.md b/docs-translations/zh-CN/api/menu.md new file mode 100644 index 00000000000..d0cfeeb33eb --- /dev/null +++ b/docs-translations/zh-CN/api/menu.md @@ -0,0 +1,355 @@ +# 菜单 + +`menu` 类可以用来创建原生菜单,它可用作应用菜单和 +[context 菜单](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/ContextMenus). + +这个模块是一个主进程的模块,并且可以通过 `remote` 模块给渲染进程调用. + +每个菜单有一个或几个菜单项 [menu items](menu-item.md),并且每个菜单项可以有子菜单. + +下面这个例子是在网页(渲染进程)中通过 [remote](remote.md) 模块动态创建的菜单,并且右键显示: + +```html + + +``` + +例子,在渲染进程中使用模板api创建应用菜单: + +```javascript +var 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' + }, + ] + }, + { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + click: function(item, focusedWindow) { + if (focusedWindow) + focusedWindow.reload(); + } + }, + { + label: 'Toggle Full Screen', + accelerator: (function() { + if (process.platform == 'darwin') + return 'Ctrl+Command+F'; + else + return '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(); + } + }, + ] + }, + { + label: 'Window', + role: 'window', + submenu: [ + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize' + }, + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close' + }, + ] + }, + { + label: 'Help', + role: 'help', + submenu: [ + { + label: 'Learn More', + click: function() { require('electron').shell.openExternal('http://electron.atom.io') } + }, + ] + }, +]; + +if (process.platform == 'darwin') { + var name = require('electron').remote.app.getName(); + template.unshift({ + label: name, + 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(); } + }, + ] + }); + // Window menu. + template[3].submenu.push( + { + type: 'separator' + }, + { + label: 'Bring All to Front', + role: 'front' + } + ); +} + +var menu = Menu.buildFromTemplate(template); +Menu.setApplicationMenu(menu); +``` + +## 类: Menu + +### `new Menu()` + +创建一个新的菜单. + +## 方法 + +`菜单` 类有如下方法: + +### `Menu.setApplicationMenu(menu)` + +* `menu` Menu + +在 OS X 上设置应用菜单 `menu` . +在windows 和 linux,是为每个窗口都在其顶部设置菜单 `menu`. + +### `Menu.sendActionToFirstResponder(action)` _OS X_ + +* `action` String + +发送 `action` 给应用的第一个响应器.这个用来模仿 Cocoa 菜单的默认行为,通常你只需要使用 `MenuItem` 的属性 `role`. + +查看更多 OS X 的原生 action [OS X 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` Array + +一般来说,`template` 只是用来创建 [MenuItem](menu-item.md) 的数组 `参数` . + +你也可以向 `template` 元素添加其它东西,并且他们会变成已经有的菜单项的属性. + +##实例方法 + +`menu` 对象有如下实例方法 + +### `menu.popup([browserWindow, x, y, positioningItem])` + +* `browserWindow` BrowserWindow (可选) - 默认为 `null`. +* `x` Number (可选) - 默认为 -1. +* `y` Number (**必须** 如果x设置了) - 默认为 -1. +* `positioningItem` Number (可选) _OS X_ - 在指定坐标鼠标位置下面的菜单项的索引. 默认为 + -1. + +在 `browserWindow` 中弹出 context menu .你可以选择性地提供指定的 `x, y` 来设置菜单应该放在哪里,否则它将默认地放在当前鼠标的位置. + +### `menu.append(menuItem)` + +* `menuItem` MenuItem + +添加菜单项. + +### `menu.insert(pos, menuItem)` + +* `pos` Integer +* `menuItem` MenuItem + +在制定位置添加菜单项. + +### `menu.items()` + +获取一个菜单项数组. + +## OS X Application 上的菜单的注意事项 + +相对于windows 和 linux, OS X 上的应用菜单是完全不同的style,这里是一些注意事项,来让你的菜单项更原生化. + +### 标准菜单 + +在 OS X 上,有很多系统定义的标准菜单,例如 `Services` and +`Windows` 菜单.为了让你的应用更标准化,你可以为你的菜单的 `role` 设置值,然后 electron 将会识别他们并且让你的菜单更标准: + +* `window` +* `help` +* `services` + +### 标准菜单项行为 + +OS X 为一些菜单项提供了标准的行为方法,例如 `About xxx`, +`Hide xxx`, and `Hide Others`. 为了让你的菜单项的行为更标准化,你应该为菜单项设置 `role` 属性. + +### 主菜单名 + +在 OS X ,无论你设置的什么标签,应用菜单的第一个菜单项的标签始终未你的应用名字.想要改变它的话,你必须通过修改应用绑定的 `Info.plist` 文件来修改应用名字.更多信息参考[About Information +Property List Files][AboutInformationPropertyListFiles] . + +## 为制定浏览器窗口设置菜单 (*Linux* *Windows*) + +浏览器窗口的[`setMenu` 方法][setMenu] 能够设置菜单为特定浏览器窗口的类型. + +## 菜单项位置 + +当通过 `Menu.buildFromTemplate` 创建菜单的时候,你可以使用 `position` and `id` 来放置菜单项. + +`MenuItem` 的属性 `position` 格式为 `[placement]=[id]`,`placement` 取值为 `before`, `after`, 或 `endof` 和 `id`, `id` 是菜单已经存在的菜单项的唯一 ID: + +* `before` - 在对应引用id菜单项之前插入. 如果引用的菜单项不存在,则将其插在菜单末尾. +* `after` - 在对应引用id菜单项之后插入. 如果引用的菜单项不存在,则将其插在菜单末尾. +* `endof` - 在逻辑上包含对应引用id菜单项的集合末尾插入. 如果引用的菜单项不存在, 则将使用给定的id创建一个新的集合,并且这个菜单项将插入. + +当一个菜档项插入成功了,所有的没有插入的菜单项将一个接一个地在后面插入.所以如果你想在同一个位置插入一组菜单项,只需要为这组菜单项的第一个指定位置. + +### 例子 + +模板: + +```javascript +[ + {label: '4', id: '4'}, + {label: '5', id: '5'}, + {label: '1', id: '1', position: 'before=4'}, + {label: '2', id: '2'}, + {label: '3', id: '3'} +] +``` + +菜单: + +``` +- 1 +- 2 +- 3 +- 4 +- 5 +``` + +模板: + +```javascript +[ + {label: 'a', position: 'endof=letters'}, + {label: '1', position: 'endof=numbers'}, + {label: 'b', position: 'endof=letters'}, + {label: '2', position: 'endof=numbers'}, + {label: 'c', position: 'endof=letters'}, + {label: '3', position: 'endof=numbers'} +] +``` + +菜单: + +``` +- --- +- a +- b +- c +- --- +- 1 +- 2 +- 3 +``` + +[AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html +[setMenu]: +https://github.com/atom/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows \ No newline at end of file diff --git a/docs-translations/zh-CN/api/native-image.md b/docs-translations/zh-CN/api/native-image.md new file mode 100644 index 00000000000..3644ff0b8bc --- /dev/null +++ b/docs-translations/zh-CN/api/native-image.md @@ -0,0 +1,148 @@ +# nativeImage + +在 Electron 中, 对所有创建 images 的 api 来说, 你可以使用文件路径或 `nativeImage` 实例. 如果使用 `null` ,将创建一个空的image 对象. + +例如, 当创建一个 tray 或设置窗口的图标时候,你可以使用一个字符串的图片路径 : + +```javascript +var appIcon = new Tray('/Users/somebody/images/icon.png'); +var window = new BrowserWindow({icon: '/Users/somebody/images/window.png'}); +``` + +或者从剪切板中读取图片,它返回的是 `nativeImage`: + +```javascript +var image = clipboard.readImage(); +var appIcon = new Tray(image); +``` + +## 支持的格式 + +当前支持 `PNG` 和 `JPEG` 图片格式. 推荐 `PNG` ,因为它支持透明和无损压缩. + +在 Windows, 你也可以使用 `ICO` 图标的格式. + +## 高分辨率图片 + +如果平台支持 high-DPI,你可以在图片基础路径后面添加 `@2x` ,可以标识它为高分辨率的图片. + +例如,如果 `icon.png` 是一个普通图片并且拥有标准分辨率,然后 `icon@2x.png`将被当作高分辨率的图片处理,它将拥有双倍 DPI 密度. + +如果想同时支持展示不同分辨率的图片,你可以将拥有不同size 的图片放在同一个文件夹下,不用 DPI 后缀.例如 : + +```text +images/ +├── icon.png +├── icon@2x.png +└── icon@3x.png +``` + + +```javascript +var appIcon = new Tray('/Users/somebody/images/icon.png'); +``` + +也支持下面这些 DPI 后缀: + +* `@1x` +* `@1.25x` +* `@1.33x` +* `@1.4x` +* `@1.5x` +* `@1.8x` +* `@2x` +* `@2.5x` +* `@3x` +* `@4x` +* `@5x` + +## 模板图片 + +模板图片由黑色和清色(和一个 alpha 通道)组成. +模板图片不是单独使用的,而是通常和其它内容混合起来创建期望的最终效果. + +最常见的用力是将模板图片用到菜单栏图片上,所以它可以同时适应亮、黑不同的菜单栏. + +**注意:** 模板图片只在 OS X 上可用. + +为了将图片标识为一个模板图片,它的文件名应当以 `Template` 结尾. 例如: + +* `xxxTemplate.png` +* `xxxTemplate@2x.png` + +## 方法 + +`nativeImage` 类有如下方法: + +### `nativeImage.createEmpty()` + +创建一个空的 `nativeImage` 实例. + +### `nativeImage.createFromPath(path)` + +* `path` String + +从指定 `path` 创建一个新的 `nativeImage` 实例 . + +### `nativeImage.createFromBuffer(buffer[, scaleFactor])` + +* `buffer` [Buffer][buffer] +* `scaleFactor` Double (可选) + +从 `buffer` 创建一个新的 `nativeImage` 实例 .默认 `scaleFactor` 是 1.0. + +### `nativeImage.createFromDataURL(dataURL)` + +* `dataURL` String + +从 `dataURL` 创建一个新的 `nativeImage` 实例 . + +## 实例方法 + +`nativeImage` 有如下方法: + +```javascript +const nativeImage = require('electron').nativeImage; + +var image = nativeImage.createFromPath('/Users/somebody/images/icon.png'); +``` + +### `image.toPng()` + +返回一个 [Buffer][buffer] ,它包含了图片的 `PNG` 编码数据. + +### `image.toJpeg(quality)` + +* `quality` Integer (**必须**) - 在 0 - 100 之间. + +返回一个 [Buffer][buffer] ,它包含了图片的 `JPEG` 编码数据. + +### `image.toDataURL()` + +返回图片数据的 URL. + +### `image.getNativeHandle()` _OS X_ + +返回一个保存了 c 指针的 [Buffer][buffer] 来潜在处理原始图像.在OS X, 将会返回一个 `NSImage` 指针实例. + +注意那返回的指针是潜在原始图像的弱指针,而不是一个复制,你_必须_ 确保与 `nativeImage` 的关联不间断 . + +### `image.isEmpty()` + +返回一个 boolean ,标识图片是否为空. + +### `image.getSize()` + +返回图片的 size. + +[buffer]: https://nodejs.org/api/buffer.html#buffer_class_buffer + +### `image.setTemplateImage(option)` + +* `option` Boolean + +将图片标识为模板图片. + +### `image.isTemplateImage()` + +返回一个 boolean ,标识图片是否是模板图片. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/power-monitor.md b/docs-translations/zh-CN/api/power-monitor.md new file mode 100644 index 00000000000..3394b4a284e --- /dev/null +++ b/docs-translations/zh-CN/api/power-monitor.md @@ -0,0 +1,36 @@ +# powerMonitor + +`power-monitor`模块是用来监听能源区改变的.只能在主进程中使用.在 `app` 模块的 `ready` 事件触发之后就不能使用这个模块了. + +例如: + +```javascript +app.on('ready', function() { + require('electron').powerMonitor.on('suspend', function() { + console.log('The system is going to sleep'); + }); +}); +``` + +## 事件 + +`power-monitor` 模块可以触发下列事件: + +### 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 new file mode 100644 index 00000000000..3a045eaea77 --- /dev/null +++ b/docs-translations/zh-CN/api/power-save-blocker.md @@ -0,0 +1,48 @@ +# powerSaveBlocker + +`powerSaveBlocker` 模块是用来阻止应用系统进入睡眠模式的,因此这允许应用保持系统和屏幕继续工作. + +例如: + +```javascript +const powerSaveBlocker = require('electron').powerSaveBlocker; + +var id = powerSaveBlocker.start('prevent-display-sleep'); +console.log(powerSaveBlocker.isStarted(id)); + +powerSaveBlocker.stop(id); +``` + +## 方法 + +`powerSaveBlocker` 模块有如下方法: + +### `powerSaveBlocker.start(type)` + +* `type` String - 强行保存阻塞类型. + * `prevent-app-suspension` - 阻止应用挂起. + 保持系统活跃,但是允许屏幕不亮. 用例: + 下载文件或者播放音频. + * `prevent-display-sleep`- 阻止应用进入休眠. 保持系统和屏幕活跃,屏幕一直亮. 用例: 播放音频. + +开始阻止系统进入睡眠模式.返回一个整数,这个整数标识了保持活跃的blocker. + +**注意:** `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. + +让指定blocker 停止活跃. + +### `powerSaveBlocker.isStarted(id)` + +* `id` Integer - 通过 `powerSaveBlocker.start` 返回的保持活跃的 blocker id. + +返回 boolean, 是否对应的 `powerSaveBlocker` 已经启动. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/process.md b/docs-translations/zh-CN/api/process.md new file mode 100644 index 00000000000..d07741247aa --- /dev/null +++ b/docs-translations/zh-CN/api/process.md @@ -0,0 +1,48 @@ +# 进程 + +Electron 中的 `process` 对象 与 upstream node 中的有以下的不同点: + +* `process.type` String - 进程类型, 可以是 `browser` (i.e. main process) + 或 `renderer`. +* `process.versions['electron']` String - Electron的版本. +* `process.versions['chrome']` String - Chromium的版本. +* `process.resourcesPath` String - JavaScript源代码路径. +* `process.mas` Boolean - 在Mac App Store 创建, 它的值为 `true`, 在其它的地方值为 `undefined`. + +## 事件 + +### 事件: 'loaded' + +在Electron已经加载了其内部预置脚本和它准备加载主进程或渲染进程的时候触发. + +当node被完全关闭的时候,它可以被预加载脚本使用来添加(原文: removed)与node无关的全局符号来回退到全局范围: + +```js +// preload.js +var _setImmediate = setImmediate; +var _clearImmediate = clearImmediate; +process.once('loaded', function() { + global.setImmediate = _setImmediate; + global.clearImmediate = _clearImmediate; +}); +``` + +## 属性 + +### `process.noAsar` + +设置它为 `true` 可以使 `asar` 文件在node的内置模块中实效. + +## 方法 + +`process` 对象有如下方法: + +### `process.hang()` + +使当前进程的主线成挂起. + +### `process.setFdLimit(maxDescriptors)` _OS X_ _Linux_ + +* `maxDescriptors` Integer + +设置文件描述符软限制于 `maxDescriptors` 或硬限制与os, 无论它是否低于当前进程. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/protocol.md b/docs-translations/zh-CN/api/protocol.md new file mode 100644 index 00000000000..a7dab97c5d5 --- /dev/null +++ b/docs-translations/zh-CN/api/protocol.md @@ -0,0 +1,183 @@ +# 协议 + +`protocol` 模块可以注册一个自定义协议,或者使用一个已经存在的协议. + +例子,使用一个与 `file://` 功能相似的协议 : + +```javascript +const electron = require('electron'); +const app = electron.app; +const path = require('path'); + +app.on('ready', function() { + var protocol = electron.protocol; + protocol.registerFileProtocol('atom', function(request, callback) { + var url = request.url.substr(7); + callback({path: path.normalize(__dirname + '/' + url)}); + }, function (error) { + if (error) + console.error('Failed to register protocol') + }); +}); +``` + +**注意:** 这个模块只有在 `app` 模块的 `ready` 事件触发之后才可使用. + +## 方法 + +`protocol` 模块有如下方法: + +### `protocol.registerStandardSchemes(schemes)` + +* `schemes` Array - 将一个自定义的方案注册为标准的方案. + +一个标准的 `scheme` 遵循 RFC 3986 的 +[generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3) 标准. 这包含了 `file:` 和 `filesystem:`. + +### `protocol.registerServiceWorkerSchemes(schemes)` + +* `schemes` Array - 将一个自定义的方案注册为处理 service workers. + +### `protocol.registerFileProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +注册一个协议,用来发送响应文件.当通过这个协议来发起一个请求的时候,将使用 `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 - 上传的文件路径. + +为了处理请求,调用 `callback` 时需要使用文件路径或者一个带 `path` 参数的对象, 例如 `callback(filePath)` 或 +`callback({path: filePath})`. + +当不使用任何参数调用 `callback` 时,你可以指定一个数字或一个带有 `error` 参数的对象,来标识 `request` 失败.你可以使用的 error number 可以参考 +[net error list][net-error]. + +默认 `scheme` 会被注册为一个 `http:` 协议,它与遵循 "generic URI syntax" 规则的协议解析不同,例如 `file:` ,所以你或许应该调用 `protocol.registerStandardSchemes` 来创建一个标准的 scheme. + +### `protocol.registerBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +注册一个 `scheme` 协议,用来发送响应 `Buffer` . + +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `Buffer` 对象,或一个有 `data`, +`mimeType`, 和 `charset` 属性的对象来调用 `callback` . + +例子: + +```javascript +protocol.registerBufferProtocol('atom', function(request, callback) { + callback({mimeType: 'text/html', data: new Buffer('
Response
')}); +}, function (error) { + if (error) + console.error('Failed to register protocol') +}); +``` + +### `protocol.registerStringProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +注册一个 `scheme` 协议,用来发送响应 `String` . + +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `String` 对象,或一个有 `data`, +`mimeType`, 和 `charset` 属性的对象来调用 `callback` . + +### `protocol.registerHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +注册一个 `scheme` 协议,用来发送 HTTP 请求作为响应. + +这个方法的用法类似 `registerFileProtocol`,除非使用一个 `redirectRequest` 对象,或一个有 `url`, `method`, +`referrer`, `uploadData` 和 `session` 属性的对象来调用 `callback` . + +* `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (可选) + * `uploadData` Object (可选) + +默认这个 HTTP 请求会使用当前 session .如果你想使用不同的session值,你应该设置 `session` 为 `null`. + +POST 请求应当包含 `uploadData` 对象. + +* `uploadData` object + * `contentType` String - 内容的 MIME type. + * `data` String - 被发送的内容. + +### `protocol.unregisterProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function (可选) + +注销自定义协议 `scheme`. + +### `protocol.isProtocolHandled(scheme, callback)` + +* `scheme` String +* `callback` Function + +将使用一个布尔值来调用 `callback` ,这个布尔值标识了是否已经存在 `scheme` 的句柄了. + +### `protocol.interceptFileProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应文件. + +### `protocol.interceptStringProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `String`. + +### `protocol.interceptBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (可选) + +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送响应 `Buffer`. + +### `protocol.interceptHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +拦截 `scheme` 协议并且使用 `handler` 作为协议的新的句柄来发送新的响应 HTTP 请求. +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a new HTTP request as a response. + +### `protocol.uninterceptProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function +取消对 `scheme` 的拦截,使用它的原始句柄进行处理. + +[net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h \ No newline at end of file diff --git a/docs-translations/zh-CN/api/remote.md b/docs-translations/zh-CN/api/remote.md new file mode 100644 index 00000000000..a686abf2f61 --- /dev/null +++ b/docs-translations/zh-CN/api/remote.md @@ -0,0 +1,137 @@ +# remote + +`remote` 模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途径。 + +Electron中, 与GUI相关的模块(如 `dialog`, `menu` 等)只存在于主进程,而不在渲染进程中 +。为了能从渲染进程中使用它们,需要用`ipc`模块来给主进程发送进程间消息。使用 `remote` +模块,可以调用主进程对象的方法,而无需显式地发送进程间消息,这类似于 Java 的 [RMI][rmi]。 +下面是从渲染进程创建一个浏览器窗口的例子: + +```javascript +const remote = require('electron').remote; +const BrowserWindow = remote.BrowserWindow; + +var win = new BrowserWindow({ width: 800, height: 600 }); +win.loadURL('https://github.com'); +``` + +**注意:** 反向操作(从主进程访问渲染进程),可以使用[webContents.executeJavascript](web-contents.md#webcontentsexecutejavascriptcode-usergesture). + +## 远程对象 + +`remote`模块返回的每个对象(包括函数)都代表了主进程中的一个对象(我们称之为远程对象或者远程函数)。 +当调用远程对象的方法、执行远程函数或者使用远程构造器(函数)创建新对象时,其实就是在发送同步的进程间消息。 + +在上面的例子中, `BrowserWindow` 和 `win` 都是远程对象,然而 +`new BrowserWindow` 并没有在渲染进程中创建 `BrowserWindow` 对象。 +而是在主进程中创建了 `BrowserWindow` 对象,并在渲染进程中返回了对应的远程对象,即 +`win` 对象。 + +请注意只有 [可枚举属性](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties) 才能通过 remote 进行访问. + +## 远程对象的生命周期 + +Electron 确保在渲染进程中的远程对象存在(换句话说,没有被垃圾收集),那主进程中的对应对象也不会被释放。 +当远程对象被垃圾收集之后,主进程中的对应对象才会被取消关联。 + +如果远程对象在渲染进程泄露了(即,存在某个表中但永远不会释放),那么主进程中的对应对象也一样会泄露, +所以你必须小心不要泄露了远程对象。If the remote object is leaked in the renderer process (e.g. stored in a map but +never freed), the corresponding object in the main process will also be leaked, +so you should be very careful not to leak remote objects. + +不过,主要的值类型如字符串和数字,是传递的副本。 + +## 给主进程传递回调函数 + +在主进程中的代码可以从渲染进程——`remote`模块——中接受回调函数,但是使用这个功能的时候必须非常非常小心。Code in the main process can accept callbacks from the renderer - for instance +the `remote` module - but you should be extremely careful when using this +feature. + +首先,为了避免死锁,传递给主进程的回调函数会进行异步调用。所以不能期望主进程来获得传递过去的回调函数的返回值。First, in order to avoid deadlocks, the callbacks passed to the main process +are called asynchronously. You should not expect the main process to +get the return value of the passed callbacks. + +比如,你不能主进程中给`Array.map`传递来自渲染进程的函数。 + +```javascript +// 主进程 mapNumbers.js +exports.withRendererCallback = function(mapper) { + return [1,2,3].map(mapper); +} + +exports.withLocalCallback = function() { + return exports.mapNumbers(function(x) { + return x + 1; + }); +} +``` + +```javascript +// 渲染进程 +var mapNumbers = require("remote").require("./mapNumbers"); + +var withRendererCb = mapNumbers.withRendererCallback(function(x) { + return x + 1; +}) + +var withLocalCb = mapNumbers.withLocalCallback() + +console.log(withRendererCb, withLocalCb) // [true, true, true], [2, 3, 4] +``` + +如你所见,渲染器回调函数的同步返回值没有按预期产生,与主进程中的一模一样的回调函数的返回值不同。 + +其次,传递给主进程的函数会持续到主进程对他们进行垃圾回收。 + +例如,下面的代码第一眼看上去毫无问题。给远程对象的`close`事件绑定了一个回调函数: + +```javascript +remote.getCurrentWindow().on('close', function() { + // blabla... +}); +``` + +但记住主进程会一直保持对这个回调函数的引用,除非明确的卸载它。如果不卸载,每次重新载入窗口都会再次绑定,这样每次重启就会泄露一个回调函数。 + +更严重的是,由于前面安装了回调函数的上下文已经被释放,所以当主进程的 `close` 事件触发的时候,会抛出异常。 + +为了避免这个问题,要确保对传递给主进程的渲染器的回调函数进行清理。可以清理事件处理器,或者明确告诉主进行取消来自已经退出的渲染器进程中的回调函数。 + +## 访问主进程中的内置模块 + +在主进程中的内置模块已经被添加为`remote`模块中的属性,所以可以直接像使用`electron`模块一样直接使用它们。 + +```javascript +const app = remote.app; +``` + +## 方法 + +`remote` 模块有以下方法: + +### `remote.require(module)` + +* `module` String + +返回在主进程中执行 `require(module)` 所返回的对象。 + +### `remote.getCurrentWindow()` + +返回该网页所属的 [`BrowserWindow`](browser-window.md) 对象。 + +### `remote.getCurrentWebContents()` + +返回该网页的 [`WebContents`](web-contents.md) 对象 + +### `remote.getGlobal(name)` + +* `name` String + +返回在主进程中名为 `name` 的全局变量(即 `global[name]`) 。 + +### `remote.process` + +返回主进程中的 `process` 对象。等同于 +`remote.getGlobal('process')` 但是有缓存。 + +[rmi]: http://en.wikipedia.org/wiki/Java_remote_method_invocation diff --git a/docs-translations/zh-CN/api/screen.md b/docs-translations/zh-CN/api/screen.md new file mode 100644 index 00000000000..0de4c975afb --- /dev/null +++ b/docs-translations/zh-CN/api/screen.md @@ -0,0 +1,135 @@ +# screen + +`screen` 模块检索屏幕的 size,显示,鼠标位置等的信息.在 `app` 模块的`ready` 事件触发之前不可使用这个模块. + +`screen` 是一个 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +**注意:** 在渲染进程 / 开发者工具栏, `window.screen` 是一个预设值的 DOM +属性, 所以这样写 `var screen = require('electron').screen` 将不会工作. +在我们下面的例子, 我们取代使用可变名字的 `electronScreen`. +一个例子,创建一个充满真个屏幕的窗口 : + +```javascript +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; + +var mainWindow; + +app.on('ready', function() { + var electronScreen = electron.screen; + var size = electronScreen.getPrimaryDisplay().workAreaSize; + mainWindow = new BrowserWindow({ width: size.width, height: size.height }); +}); +``` + +另一个例子,在次页外创建一个窗口: + +```javascript +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; + +var mainWindow; + +app.on('ready', function() { + var electronScreen = electron.screen; + var displays = electronScreen.getAllDisplays(); + var externalDisplay = null; + for (var i in displays) { + if (displays[i].bounds.x != 0 || displays[i].bounds.y != 0) { + externalDisplay = displays[i]; + break; + } + } + + if (externalDisplay) { + mainWindow = new BrowserWindow({ + x: externalDisplay.bounds.x + 50, + y: externalDisplay.bounds.y + 50 + }); + } +}); +``` + +## `Display` 对象 + +`Display` 对象表示了物力方式连接系统. 一个伪造的 `Display` 或许存在于一个无头系统中,或者一个 `Display` 相当于一个远程的、虚拟的 display. + +* `display` object + * `id` Integer - 与display 相关的唯一性标志. + * `rotation` Integer - 可以是 0, 1, 2, 3, 每个代表了屏幕旋转的度数 0, 90, 180, 270. + * `scaleFactor` Number - Output device's pixel scale factor. + * `touchSupport` String - 可以是 `available`, `unavailable`, `unknown`. + * `bounds` Object + * `size` Object + * `workArea` Object + * `workAreaSize` Object + +## 事件 + +`screen` 模块有如下事件: + +### Event: 'display-added' + +返回: + +* `event` Event +* `newDisplay` Object + +当添加了 `newDisplay` 时发出事件 + +### Event: 'display-removed' + +返回: + +* `event` Event +* `oldDisplay` Object + +当移出了 `oldDisplay` 时发出事件 + +### Event: 'display-metrics-changed' + +返回: + +* `event` Event +* `display` Object +* `changedMetrics` Array + +当一个 `display` 中的一个或更多的 metrics 改变时发出事件. +`changedMetrics` 是一个用来描述这个改变的数组.可能的变化为 `bounds`, +`workArea`, `scaleFactor` 和 `rotation`. + +## 方法 + +`screen` 模块有如下方法: + +### `screen.getCursorScreenPoint()` + +返回当前鼠标的绝对路径 . + +### `screen.getPrimaryDisplay()` + +返回最主要的 display. + +### `screen.getAllDisplays()` + +返回一个当前可用的 display 数组. + +### `screen.getDisplayNearestPoint(point)` + +* `point` Object + * `x` Integer + * `y` Integer + +返回离指定点最近的 display. + +### `screen.getDisplayMatching(rect)` + +* `rect` Object + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +返回与提供的边界范围最密切相关的 display. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/session.md b/docs-translations/zh-CN/api/session.md new file mode 100644 index 00000000000..b9664c08e65 --- /dev/null +++ b/docs-translations/zh-CN/api/session.md @@ -0,0 +1,481 @@ +# session + +`session` 模块可以用来创建一个新的 `Session` 对象. + +你也可以通过使用 [`webContents`](web-contents.md) 的属性 `session` 来使用一个已有页面的 `session` ,`webContents` 是[`BrowserWindow`](browser-window.md) 的属性. + +```javascript +const BrowserWindow = require('electron').BrowserWindow; + +var win = new BrowserWindow({ width: 800, height: 600 }); +win.loadURL("http://github.com"); + +var ses = win.webContents.session; +``` + +## 方法 + +`session` 模块有如下方法: + +### session.fromPartition(partition) + +* `partition` String + +从字符串 `partition` 返回一个新的 `Session` 实例. + +如果 `partition` 以 `persist:` 开头,那么这个page将使用一个持久的 session,这个 session 将对应用的所有 page 可用.如果没前缀,这个 page 将使用一个历史 session.如果 `partition` 为空,那么将返回应用的默认 session . + +## 属性 + +`session` 模块有如下属性: + +### session.defaultSession + +返回应用的默认 session 对象. + +## Class: Session + +可以在 `session` 模块中创建一个 `Session` 对象 : + +```javascript +const session = require('electron').session; + +var ses = session.fromPartition('persist:name'); +``` + +### 实例事件 + +实例 `Session` 有以下事件: + +#### Event: 'will-download' + +* `event` Event +* `item` [DownloadItem](download-item.md) +* `webContents` [WebContents](web-contents.md) + +当 Electron 将要从 `webContents` 下载 `item` 时触发. + +调用 `event.preventDefault()` 可以取消下载,并且在进程的下个 tick中,这个 `item` 也不可用. + +```javascript +session.defaultSession.on('will-download', function(event, item, webContents) { + event.preventDefault(); + require('request')(item.getURL(), function(data) { + require('fs').writeFileSync('/somewhere', data); + }); +}); +``` + +### 实例方法 + +实例 `Session` 有以下方法: + +#### `ses.cookies` + +`cookies` 赋予你全力来查询和修改 cookies. 例如: + +```javascript +// 查询所有 cookies. +session.defaultSession.cookies.get({}, function(error, cookies) { + console.log(cookies); +}); + +// 查询与指定 url 相关的所有 cookies. +session.defaultSession.cookies.get({ url : "http://www.github.com" }, function(error, cookies) { + console.log(cookies); +}); + +// 设置 cookie; +// may overwrite equivalent cookies if they exist. +var cookie = { url : "http://www.github.com", name : "dummy_name", value : "dummy" }; +session.defaultSession.cookies.set(cookie, function(error) { + if (error) + console.error(error); +}); +``` + +#### `ses.cookies.get(filter, callback)` + +* `filter` Object + * `url` String (可选) - 与获取 cookies 相关的 + `url`.不设置的话就是从所有 url 获取 cookies . + * `name` String (可选) - 通过 name 过滤 cookies. + * `domain` String (可选) - 获取对应域名或子域名的 cookies . + * `path` String (可选) - 获取对应路径的 cookies . + * `secure` Boolean (可选) - 通过安全性过滤 cookies. + * `session` Boolean (可选) - 过滤掉 session 或 持久的 cookies. +* `callback` Function + +发送一个请求,希望获得所有匹配 `details` 的 cookies, +在完成的时候,将通过 `callback(error, cookies)` 调用 `callback`. + +`cookies`是一个 `cookie` 对象. + +* `cookie` Object + * `name` String - cookie 名. + * `value` String - cookie值. + * `domain` String - cookie域名. + * `hostOnly` String - 是否 cookie 是一个 host-only cookie. + * `path` String - cookie路径. + * `secure` Boolean - 是否是安全 cookie. + * `httpOnly` Boolean - 是否只是 HTTP cookie. + * `session` Boolean - cookie 是否是一个 session cookie 或一个带截至日期的持久 + cookie . + * `expirationDate` Double (可选) - cookie的截至日期,数值为UNIX纪元以来的秒数. 对session cookies 不提供. + +#### `ses.cookies.set(details, callback)` + +* `details` Object + * `url` String - 与获取 cookies 相关的 + `url`. + * `name` String - cookie 名. 忽略默认为空. + * `value` String - cookie 值. 忽略默认为空. + * `domain` String - cookie的域名. 忽略默认为空. + * `path` String - cookie 的路径. 忽略默认为空. + * `secure` Boolean - 是否已经进行了安全性标识. 默认为 + false. + * `session` Boolean - 是否已经 HttpOnly 标识. 默认为 false. + * `expirationDate` Double - cookie的截至日期,数值为UNIX纪元以来的秒数. 如果忽略, cookie 变为 session cookie. +* `callback` Function + +使用 `details` 设置 cookie, 完成时使用 `callback(error)` 掉哟个 `callback` . + +#### `ses.cookies.remove(url, name, callback)` + +* `url` String - 与 cookies 相关的 + `url`. +* `name` String - 需要删除的 cookie 名. +* `callback` Function + +删除匹配 `url` 和 `name` 的 cookie, 完成时使用 `callback()`调用`callback`. + +#### `ses.getCacheSize(callback)` + +* `callback` Function + * `size` Integer - 单位 bytes 的缓存 size. + +返回 session 的当前缓存 size . + +#### `ses.clearCache(callback)` + +* `callback` Function - 操作完成时调用 + +清空 session 的 HTTP 缓存. + +#### `ses.clearStorageData([options, ]callback)` + +* `options` Object (可选) + * `origin` String - 应当遵循 `window.location.origin` 的格式 + `scheme://host:port`. + * `storages` Array - 需要清理的 storages 类型, 可以包含 : + `appcache`, `cookies`, `filesystem`, `indexdb`, `local storage`, + `shadercache`, `websql`, `serviceworkers` + * `quotas` Array - 需要清理的类型指标, 可以包含: + `temporary`, `persistent`, `syncable`. +* `callback` Function - 操作完成时调用. + +清除 web storages 的数据. + +#### `ses.flushStorageData()` + +将没有写入的 DOMStorage 写入磁盘. + +#### `ses.setProxy(config, callback)` + +* `config` Object + * `pacScript` String - 与 PAC 文件相关的 URL. + * `proxyRules` String - 代理使用规则. +* `callback` Function - 操作完成时调用. + +设置 proxy settings. + +当 `pacScript` 和 `proxyRules` 一同提供时,将忽略 `proxyRules`,并且使用 `pacScript` 配置 . + +`proxyRules` 需要遵循下面的规则: + +``` +proxyRules = schemeProxies[";"] +schemeProxies = ["="] +urlScheme = "http" | "https" | "ftp" | "socks" +proxyURIList = [","] +proxyURL = ["://"][":"] +``` + +例子: + +* `http=foopy:80;ftp=foopy2` - 为 `http://` URL 使用 HTTP 代理 `foopy:80` , 和为 `ftp://` URL + HTTP 代理 `foopy2:80` . +* `foopy:80` - 为所有 URL 使用 HTTP 代理 `foopy:80` . +* `foopy:80,bar,direct://` - 为所有 URL 使用 HTTP 代理 `foopy:80` , 如果 `foopy:80` 不可用,则切换使用 `bar`, 再往后就不使用代理了. +* `socks4://foopy` - 为所有 URL 使用 SOCKS v4 代理 `foopy:1080`. +* `http=foopy,socks5://bar.com` - 为所有 URL 使用 HTTP 代理 `foopy`, 如果 `foopy`不可用,则切换到 SOCKS5 代理 `bar.com`. +* `http=foopy,direct://` - 为所有http url 使用 HTTP 代理,如果 `foopy`不可用,则不使用代理. +* `http=foopy;socks=foopy2` - 为所有http url 使用 `foopy` 代理,为所有其他 url 使用 `socks4://foopy2` 代理. + +### `ses.resolveProxy(url, callback)` + +* `url` URL +* `callback` Function + +解析 `url` 的代理信息.当请求完成的时候使用 `callback(proxy)` 调用 `callback`. + +#### `ses.setDownloadPath(path)` + +* `path` String - 下载地址 + +设置下载保存地址,默认保存地址为各自 app 应用的 `Downloads`目录. + +#### `ses.enableNetworkEmulation(options)` + +* `options` Object + * `offline` Boolean - 是否模拟网络故障. + * `latency` Double - 每毫秒的 RTT + * `downloadThroughput` Double - 每 Bps 的下载速率. + * `uploadThroughput` Double - 每 Bps 的上载速率. + +通过给定配置的 `session` 来模拟网络. + +```javascript +// 模拟 GPRS 连接,使用的 50kbps 流量,500 毫秒的 rtt. +window.webContents.session.enableNetworkEmulation({ + latency: 500, + downloadThroughput: 6400, + uploadThroughput: 6400 +}); + +// 模拟网络故障. +window.webContents.session.enableNetworkEmulation({offline: true}); +``` + +#### `ses.disableNetworkEmulation()` + +停止所有已经使用 `session` 的活跃模拟网络. +重置为原始网络类型. + +#### `ses.setCertificateVerifyProc(proc)` + +* `proc` Function + +为 `session` 设置证书验证过程,当请求一个服务器的证书验证时,使用 `proc(hostname, certificate, callback)` 调用 `proc`.调用 `callback(true)` 来接收证书,调用 `callback(false)` 来拒绝验证证书. + +调用了 `setCertificateVerifyProc(null)` ,则将会回复到默认证书验证过程. + +```javascript +myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, callback) { + if (hostname == 'github.com') + callback(true); + else + callback(false); +}); +``` + +#### `ses.setPermissionRequestHandler(handler)` + +* `handler` Function + * `webContents` Object - [WebContents](web-contents.md) 请求许可. + * `permission` String - 枚举了 'media', 'geolocation', 'notifications', 'midiSysex', 'pointerLock', 'fullscreen'. + * `callback` Function - 允许或禁止许可. + +为对应 `session` 许可请求设置响应句柄.调用 `callback(true)` 接收许可,调用 `callback(false)` 禁止许可. + +```javascript +session.fromPartition(partition).setPermissionRequestHandler(function(webContents, permission, callback) { + if (webContents.getURL() === host) { + if (permission == "notifications") { + callback(false); // denied. + return; + } + } + + callback(true); +}); +``` + +#### `ses.clearHostResolverCache([callback])` + +* `callback` Function (可选) - 操作结束调用. + +清除主机解析缓存. + +#### `ses.webRequest` + +在其生命周期的不同阶段,`webRequest` API 设置允许拦截并修改请求内容. + +每个 API 接收一可选的 `filter` 和 `listener`,当 API 事件发生的时候使用 `listener(details)` 调用 `listener`,`details` 是一个用来描述请求的对象.为 `listener` 使用 `null` 则会退定事件. + +`filter` 是一个拥有 `urls` 属性的对象,这是一个 url 模式数组,这用来过滤掉不匹配指定 url 模式的请求.如果忽略 `filter` ,那么所有请求都将可以成功匹配. + +所有事件的 `listener` 都有一个回调事件,当 `listener` 完成它的工作的时候,它将使用一个 `response` 对象来调用. + +```javascript +// 将所有请求的代理都修改为下列 url. +var filter = { + urls: ["https://*.github.com/*", "*://electron.github.io"] +}; + +session.defaultSession.webRequest.onBeforeSendHeaders(filter, function(details, callback) { + details.requestHeaders['User-Agent'] = "MyAgent"; + callback({cancel: false, requestHeaders: details.requestHeaders}); +}); +``` + +#### `ses.webRequest.onBeforeRequest([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当一个请求即将开始的时候,使用 `listener(details, callback)` 调用 `listener`. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `uploadData` Array (可选) +* `callback` Function + +`uploadData` 是一个 `data` 数组对象: + +* `data` Object + * `bytes` Buffer - 被发送的内容. + * `file` String - 上载文件路径. + +`callback` 必须使用一个 `response` 对象来调用: + +* `response` Object + * `cancel` Boolean (可选) + * `redirectURL` String (可选) - 原始请求阻止发送或完成,而不是重定向. + +#### `ses.webRequest.onBeforeSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +一旦请求报文头可用了,在发送 HTTP 请求的之前,使用 `listener(details, callback)` 调用 `listener`.这也许会在服务器发起一个tcp 连接,但是在发送任何 http 数据之前发生. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object +* `callback` Function + +必须使用一个 `response` 对象来调用 `callback` : + +* `response` Object + * `cancel` Boolean (可选) + * `requestHeaders` Object (可选) - 如果提供了,将使用这些 headers 来创建请求. + +#### `ses.webRequest.onSendHeaders([filter, ]listener)` + +* `filter` Object +* `listener` Function + +在一个请求正在发送到服务器的时候,使用 `listener(details)` 来调用 `listener` ,之前 `onBeforeSendHeaders` 修改部分响应可用,同时取消监听. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `requestHeaders` Object + +#### `ses.webRequest.onHeadersReceived([filter,] listener)` + +* `filter` Object +* `listener` Function + +当 HTTP 请求报文头已经到达的时候,使用 `listener(details, callback)` 调用 `listener` . + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `statusLine` String + * `statusCode` Integer + * `responseHeaders` Object +* `callback` Function + +必须使用一个 `response` 对象来调用 `callback` : + +* `response` Object + * `cancel` Boolean + * `responseHeaders` Object (可选) - 如果提供, 服务器将假定使用这些头来响应. + +#### `ses.webRequest.onResponseStarted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当响应body的首字节到达的时候,使用 `listener(details)` 调用 `listener`.对 http 请求来说,这意味着状态线和响应头可用了. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean - 标识响应是否来自磁盘 + cache. + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onBeforeRedirect([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当服务器的重定向初始化正要启动时,使用 `listener(details)` 调用 `listener`. + +* `details` Object + * `id` String + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `redirectURL` String + * `statusCode` Integer + * `ip` String (可选) - 请求的真实服务器ip 地址 + * `fromCache` Boolean + * `responseHeaders` Object + +#### `ses.webRequest.onCompleted([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当请求完成的时候,使用 `listener(details)` 调用 `listener`. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `responseHeaders` Object + * `fromCache` Boolean + * `statusCode` Integer + * `statusLine` String + +#### `ses.webRequest.onErrorOccurred([filter, ]listener)` + +* `filter` Object +* `listener` Function + +当一个错误发生的时候,使用 `listener(details)` 调用 `listener`. + +* `details` Object + * `id` Integer + * `url` String + * `method` String + * `resourceType` String + * `timestamp` Double + * `fromCache` Boolean + * `error` String - 错误描述. \ No newline at end of file diff --git a/docs-translations/zh-CN/api/synopsis.md b/docs-translations/zh-CN/api/synopsis.md new file mode 100644 index 00000000000..a1a2a6d0122 --- /dev/null +++ b/docs-translations/zh-CN/api/synopsis.md @@ -0,0 +1,71 @@ +# 简介 + +所有的[Node.js's built-in modules][1]在Electron中都可用,并且所有的node的第三方组件也可以放心使用(包括[自身的模块][2])。 + +Electron也提供了一些额外的内置组件来开发传统桌面应用。一些组件只可以在主进程中使用,一些只可以在渲染进程中使用,但是也有部分可以在这2种进程中都可使用。 + +基本规则:GUI模块或者系统底层的模块只可以在主进程中使用。要使用这些模块,你应当很熟悉[主进程vs渲染进程][3]脚本的概念。 + +主进程脚本看起来像个普通的nodejs脚本 + +```javascript +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; + +var window = null; + +app.on('ready', function() { + window = new BrowserWindow({width: 800, height: 600}); + window.loadURL('https://github.com'); +}); +``` + +渲染进程和传统的web界面一样,除了它具有使用node模块的能力: + +```html + + + + + + +``` + +如果想运行应用,参考 `Run your app` 。 + +## 解构任务 + +如果你使用的是CoffeeScript或Babel,你可以使用[destructuring assignment][4]来让使用内置模块更简单: + +```javascript +const {app, BrowserWindow} = require('electron'); +``` + +然而如果你使用的是普通的JavaScript,你就需要等到Chrome支持ES6了。 + +##使用内置模块时禁用旧样式 + +在版本v0.35.0之前,所有的内置模块都需要按造 `require('module-name')` 形式来使用,虽然它有很多[弊端][5],我们仍然在老的应用中友好的支持它。 + +为了完整的禁用旧样式,你可以设置环境变量 `ELECTRON_HIDE_INTERNAL_MODULES ` : + +```javascript +process.env.ELECTRON_HIDE_INTERNAL_MODULES = 'true' +``` + +或者调用 `hideInternalModules` API: + +```javascript +require('electron').hideInternalModules() +``` + + + [1]:http://nodejs.org/api/ + [2]:https://github.com/heyunjiang/electron/blob/master/docs/tutorial/using-native-node-modules.md + [3]:https://github.com/heyunjiang/electron/blob/master/docs/tutorial/quick-start.md#the-main-process + [4]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment + [5]:https://github.com/atom/electron/issues/387 \ No newline at end of file diff --git a/docs-translations/zh-CN/api/tray.md b/docs-translations/zh-CN/api/tray.md new file mode 100644 index 00000000000..d5647871ac7 --- /dev/null +++ b/docs-translations/zh-CN/api/tray.md @@ -0,0 +1,205 @@ +# Tray + +用一个 `Tray` 来表示一个图标,这个图标处于正在运行的系统的通知区 ,通常被添加到一个 context menu 上. + +```javascript +const electron = require('electron'); +const app = electron.app; +const Menu = electron.Menu; +const Tray = electron.Tray; + +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' } + ]); + appIcon.setToolTip('This is my application.'); + appIcon.setContextMenu(contextMenu); +}); + +``` + +__平台限制:__ + +* 在 Linux, 如果支持应用指示器则使用它,否则使用 `GtkStatusIcon` 代替. +* 在 Linux ,配置了只有有了应用指示器的支持, 你必须安装 `libappindicator1` 来让 tray icon 执行. +* 应用指示器只有在它拥有 context menu 时才会显示. +* 当在linux 上使用了应用指示器,将忽略点击事件. +* 在 Linux,为了让单独的 `MenuItem` 起效,需要再次调用 `setContextMenu` .例如: + +```javascript +contextMenu.items[2].checked = false; +appIcon.setContextMenu(contextMenu); +``` +如果想在所有平台保持完全相同的行为,不应该依赖点击事件,而是一直将一个 context menu 添加到 tray icon. + +## Class: Tray + +`Tray` 是一个 [事件发出者][event-emitter]. + +### `new Tray(image)` + +* `image` [NativeImage](native-image.md) + +创建一个与 `image` 相关的 icon. + +## 事件 + +`Tray` 模块可发出下列事件: + +**注意:** 一些事件只能在特定的os中运行,已经标明. + +### 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 + +当tray icon被点击的时候发出事件. + +__注意:__ `bounds` 只在 OS X 和 Windows 上起效. + +### Event: 'right-click' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - tray icon 的 bounds. + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +当tray icon被鼠标右键点击的时候发出事件. + +### Event: 'double-click' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - tray icon 的 bounds. + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +当tray icon被双击的时候发出事件. + +### Event: 'balloon-show' _Windows_ + +当tray 气泡显示的时候发出事件. + +### Event: 'balloon-click' _Windows_ + +当tray 气泡被点击的时候发出事件. + +### Event: 'balloon-closed' _Windows_ + +当tray 气泡关闭的时候发出事件,因为超时或人为关闭. + +### Event: 'drop' _OS X_ + +当tray icon上的任何可拖动项被删除的时候发出事件. + +### Event: 'drop-files' _OS X_ + +* `event` +* `files` Array - 已删除文件的路径. + +当tray icon上的可拖动文件被删除的时候发出事件. + +### Event: 'drag-enter' _OS X_ + +当一个拖动操作进入tray icon的时候发出事件. + +### Event: 'drag-leave' _OS X_ + +当一个拖动操作离开tray icon的时候发出事件. +Emitted when a drag operation exits the tray icon. + +### Event: 'drag-end' _OS X_ + +当一个拖动操作在tray icon上或其它地方停止拖动的时候发出事件. + +## 方法 + +`Tray` 模块有以下方法: + +**Note:** 一些方法只能在特定的os中运行,已经标明. + +### `Tray.destroy()` + +立刻删除 tray icon. + +### `Tray.setImage(image)` + +* `image` [NativeImage](native-image.md) + +让 `image` 与 tray icon 关联起来. + +### `Tray.setPressedImage(image)` _OS X_ + +* `image` [NativeImage](native-image.md) + +当在 OS X 上按压 tray icon 的时候, 让 `image` 与 tray icon 关联起来. + +### `Tray.setToolTip(toolTip)` + +* `toolTip` String + +为 tray icon 设置 hover text. + +### `Tray.setTitle(title)` _OS X_ + +* `title` String + +在状态栏沿着 tray icon 设置标题. + +### `Tray.setHighlightMode(highlight)` _OS X_ + +* `highlight` Boolean + +当 tray icon 被点击的时候,是否设置它的背景色变为高亮(blue).默认为 true. + +### `Tray.displayBalloon(options)` _Windows_ + +* `options` Object + * `icon` [NativeImage](native-image.md) + * `title` String + * `content` String + +展示一个 tray balloon. + +### `Tray.popUpContextMenu([menu, position])` _OS X_ _Windows_ + +* `menu` Menu (optional) +* `position` Object (可选) - 上托位置. + * `x` Integer + * `y` Integer + +从 tray icon 上托出 context menu . 当划过 `menu` 的时候, `menu` 显示,代替 tray 的 context menu . + +`position` 只在 windows 上可用,默认为 (0, 0) . + +### `Tray.setContextMenu(menu)` + +* `menu` Menu + +为这个 icon 设置 context menu . + +[event-emitter]: http://nodejs.org/api/events.html#events_class_events_eventemitter \ No newline at end of file diff --git a/docs-translations/zh-CN/api/web-contents.md b/docs-translations/zh-CN/api/web-contents.md new file mode 100644 index 00000000000..f168849863a --- /dev/null +++ b/docs-translations/zh-CN/api/web-contents.md @@ -0,0 +1,862 @@ +# webContents + +`webContents` 是一个 +[事件发出者](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +它负责渲染并控制网页,也是 [`BrowserWindow`](browser-window.md) 对象的属性.一个使用 `webContents` 的例子: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; + +var win = new BrowserWindow({width: 800, height: 1500}); +win.loadURL("http://github.com"); + +var webContents = win.webContents; +``` + +## 事件 + +`webContents` 对象可发出下列事件: + +### Event: 'did-finish-load' + +当导航完成时发出事件,`onload` 事件也完成. + +### Event: 'did-fail-load' + +返回: + +* `event` Event +* `errorCode` Integer +* `errorDescription` String +* `validatedURL` String + +这个事件类似 `did-finish-load` ,但是是在加载失败或取消加载时发出, 例如, `window.stop()` 请求结束.错误代码的完整列表和它们的含义都可以在 [here](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) 找到. + +### Event: 'did-frame-finish-load' + +返回: + +* `event` Event +* `isMainFrame` Boolean + +当一个 frame 导航完成的时候发出事件. + +### Event: 'did-start-loading' + +当 tab 的spinner 开始 spinning的时候. + +### Event: 'did-stop-loading' + +当 tab 的spinner 结束 spinning的时候. + +### Event: 'did-get-response-details' + +返回: + +* `event` Event +* `status` Boolean +* `newURL` String +* `originalURL` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object + +当有关请求资源的详细信息可用的时候发出事件. +`status` 标识了 socket链接来下载资源. + +### Event: 'did-get-redirect-request' + +返回: + +* `event` Event +* `oldURL` String +* `newURL` String +* `isMainFrame` Boolean +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object + +当在请求资源时收到重定向的时候发出事件. + +### Event: 'dom-ready' + +返回: + +* `event` Event + +当指定 frame 中的 文档加载完成的时候发出事件. + +### Event: 'page-favicon-updated' + +返回: + +* `event` Event +* `favicons` Array - Array of URLs + +当 page 收到图标 url 的时候发出事件. + +### Event: 'new-window' + +返回: + +* `event` Event +* `url` String +* `frameName` String +* `disposition` String - 可为 `default`, `foreground-tab`, `background-tab`, + `new-window` 和 `other`. +* `options` Object - 创建新的 `BrowserWindow`时使用的参数. + +当 page 请求打开指定 url 窗口的时候发出事件.这可以是通过 `window.open` 或一个外部连接如 `` 发出的请求. + +默认指定 `url` 的 `BrowserWindow` 会被创建. + +调用 `event.preventDefault()` 可以用来阻止打开窗口. + +### Event: 'will-navigate' + +返回: + +* `event` Event +* `url` String + +当用户或 page 想要开始导航的时候发出事件.它可在当 `window.location` 对象改变或用户点击 page 中的链接的时候发生. + +当使用 api(如 `webContents.loadURL` 和 `webContents.back`) 以编程方式来启动导航的时候,这个事件将不会发出. + +它也不会在页内跳转发生, 例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 事件可以达到目的. + +调用 `event.preventDefault()` 可以阻止导航. + +### Event: 'did-navigate' + +返回: + +* `event` Event +* `url` String + +当一个导航结束时候发出事件. + +页内跳转时不会发出这个事件,例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 事件可以达到目的. + +### Event: 'did-navigate-in-page' + +返回: + +* `event` Event +* `url` String + +当页内导航发生的时候发出事件. + +当页内导航发生的时候,page 的url 改变,但是不会跳出界面.例如当点击锚链接时或者 DOM 的 `hashchange` 事件发生. + +### Event: 'crashed' + +当渲染进程崩溃的时候发出事件. + +### Event: 'plugin-crashed' + +返回: + +* `event` Event +* `name` String +* `version` String + +当插件进程崩溃时候发出事件. + +### Event: 'destroyed' + +当 `webContents` 被删除的时候发出事件. + +### Event: 'devtools-opened' + +当开发者工具栏打开的时候发出事件. + +### 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 +* `callback` Function + +当验证证书或 `url` 失败的时候发出事件. + +使用方法类似 [`app` 的 `certificate-error` 事件](app.md#event-certificate-error). + +### Event: 'select-client-certificate' + +返回: + +* `event` Event +* `url` URL +* `certificateList` [Objects] + * `data` Buffer - PEM encoded data + * `issuerName` String - Issuer's Common Name +* `callback` Function + +当请求客户端证书的时候发出事件. + +使用方法类似 [`app` 的 `select-client-certificate` 事件](app.md#event-select-client-certificate). + +### Event: 'login' + +返回: + +* `event` Event +* `request` Object + * `method` String + * `url` URL + * `referrer` URL +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String +* `callback` Function + +当 `webContents` 想做基本验证的时候发出事件. + +使用方法类似 [the `login` event of `app`](app.md#event-login). + +### Event: 'found-in-page' + +返回: + +* `event` Event +* `result` Object + * `requestId` Integer + * `finalUpdate` Boolean - 标识是否还有更多的值可以查看. + * `activeMatchOrdinal` Integer (可选) - 活动匹配位置 + * `matches` Integer (可选) - 匹配数量. + * `selectionArea` Object (可选) - 协调首个匹配位置. + +当使用 [`webContents.findInPage`](web-contents.md#webcontentsfindinpage) 进行页内查找并且找到可用值得时候发出事件. + +### Event: 'media-started-playing' + +当媒体开始播放的时候发出事件. + +### Event: 'media-paused' + +当媒体停止播放的时候发出事件. + +### Event: 'did-change-theme-color' + +当page 的主题色时候发出事件.这通常由于引入了一个 meta 标签 : + +```html + +``` + +### Event: 'cursor-changed' + +返回: + +* `event` Event +* `type` String +* `image` NativeImage (可选) +* `scale` Float (可选) + +当鼠标的类型发生改变的时候发出事件. `type` 的参数可以是 `default`, +`crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, +`ne-resize`, `nw-resize`, `s-resize`, `se-resize`, `sw-resize`, `w-resize`, +`ns-resize`, `ew-resize`, `nesw-resize`, `nwse-resize`, `col-resize`, +`row-resize`, `m-panning`, `e-panning`, `n-panning`, `ne-panning`, `nw-panning`, +`s-panning`, `se-panning`, `sw-panning`, `w-panning`, `move`, `vertical-text`, +`cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, +`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. + +如果 `type` 参数值为 `custom`, `image` 参数会在一个`NativeImage` 中控制自定义鼠标图片, 并且 `scale` 会控制图片的缩放比例. + +## 实例方法 + +`webContents` 对象有如下的实例方法: + +### `webContents.loadURL(url[, options])` + +* `url` URL +* `options` Object (可选) + * `httpReferrer` String - A HTTP Referrer url. + * `userAgent` String - 产生请求的用户代理 + * `extraHeaders` String - 以 "\n" 分隔的额外头 + +在窗口中加载 `url` , `url` 必须包含协议前缀, +比如 `http://` 或 `file://`. 如果加载想要忽略 http 缓存,可以使用 `pragma` 头来达到目的. + +```javascript +const options = {"extraHeaders" : "pragma: no-cache\n"} +webContents.loadURL(url, options) +``` + +### `webContents.downloadURL(url)` + +* `url` URL + +初始化一个指定 `url` 的资源下载,不导航跳转. `session` 的 `will-download` 事件会触发. + +### `webContents.getURL()` + +返回当前page 的 url. + +```javascript +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL("http://github.com"); + +var currentURL = win.webContents.getURL(); +``` + +### `webContents.getTitle()` + +返回当前page 的 标题. + +### `webContents.isLoading()` + +返回一个布尔值,标识当前页是否正在加载. + +### `webContents.isWaitingForResponse()` + +返回一个布尔值,标识当前页是否正在等待主要资源的第一次响应. + +### `webContents.stop()` + +停止还为开始的导航. + +### `webContents.reload()` + +重载当前页. + +### `webContents.reloadIgnoringCache()` + +重载当前页,忽略缓存. + +### `webContents.canGoBack()` + +返回一个布尔值,标识浏览器是否能回到前一个page. + +### `webContents.canGoForward()` + +返回一个布尔值,标识浏览器是否能前往下一个page. + +### `webContents.canGoToOffset(offset)` + +* `offset` Integer + +返回一个布尔值,标识浏览器是否能前往指定 `offset` 的page. + +### `webContents.clearHistory()` + +清除导航历史. + +### `webContents.goBack()` + +让浏览器回退到前一个page. + +### `webContents.goForward()` + +让浏览器回前往下一个page. + +### `webContents.goToIndex(index)` + +* `index` Integer + +让浏览器回前往指定 `index` 的page. + +### `webContents.goToOffset(offset)` + +* `offset` Integer + +导航到相对于当前页的偏移位置页. + +### `webContents.isCrashed()` + +渲染进程是否崩溃. + +### `webContents.setUserAgent(userAgent)` + +* `userAgent` String + +重写本页用户代理. + +### `webContents.getUserAgent()` + +返回一个 `String` ,标识本页用户代理信息. + +### `webContents.insertCSS(css)` + +* `css` String + +为当前页插入css. + +### `webContents.executeJavaScript(code[, userGesture, callback])` + +* `code` String +* `userGesture` Boolean (可选) +* `callback` Function (可选) - 脚本执行完成后调用的回调函数. + * `result` + +评估 page `代码`. + +浏览器窗口中的一些 HTML API ,例如 `requestFullScreen`,只能被用户手势请求.设置 `userGesture` 为 `true` 可以取消这个限制. + +### `webContents.setAudioMuted(muted)` + +* `muted` Boolean + +减缓当前也的 audio 的播放速度. + +### `webContents.isAudioMuted()` + +返回一个布尔值,标识当前页是否减缓了 audio 的播放速度. + +### `webContents.undo()` + +执行网页的编辑命令 `undo` . + +### `webContents.redo()` + +执行网页的编辑命令 `redo` . + +### `webContents.cut()` + +执行网页的编辑命令 `cut` . + +### `webContents.copy()` + +执行网页的编辑命令 `copy` . + +### `webContents.paste()` + +执行网页的编辑命令 `paste` . + +### `webContents.pasteAndMatchStyle()` + +执行网页的编辑命令 `pasteAndMatchStyle` . + +### `webContents.delete()` + +执行网页的编辑命令 `delete` . + +### `webContents.selectAll()` + +执行网页的编辑命令 `selectAll` . + +### `webContents.unselect()` + +执行网页的编辑命令 `unselect` . + +### `webContents.replace(text)` + +* `text` String + +执行网页的编辑命令 `replace` . + +### `webContents.replaceMisspelling(text)` + +* `text` String + +执行网页的编辑命令 `replaceMisspelling` . + +### `webContents.insertText(text)` + +* `text` String + +插入 `text` 到获得了焦点的元素. + +### `webContents.findInPage(text[, options])` + +* `text` String - 查找内容, 不能为空. +* `options` Object (可选) + * `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) 事件来取得. + +### `webContents.stopFindInPage(action)` + +* `action` String - 指定一个行为来接替停止 + [`webContents.findInPage`](web-contents.md#webcontentfindinpage) 请求. + * `clearSelection` - 转变为一个普通的 selection. + * `keepSelection` - 清除 selection. + * `activateSelection` - 获取焦点并点击 selection node. + +使用给定的 `action` 来为 `webContents` 停止任何 `findInPage` 请求. + +```javascript +webContents.on('found-in-page', function(event, result) { + if (result.finalUpdate) + webContents.stopFindInPage("clearSelection"); +}); + +const requestId = webContents.findInPage("api"); +``` + +### `webContents.hasServiceWorker(callback)` + +* `callback` Function + +检查是否有任何 ServiceWorker 注册了,并且返回一个布尔值,来作为 `callback`响应的标识. + +### `webContents.unregisterServiceWorker(callback)` + +* `callback` Function + +如果存在任何 ServiceWorker ,则全部注销,并且当JS承诺执行行或JS拒绝执行而失败的时候,返回一个布尔值,它标识了相应的 `callback`. + +### `webContents.print([options])` + +* `options` Object (可选) + * `silent` Boolean - 不需要请求用户的打印设置. 默认为 `false`. + * `printBackground` Boolean - 打印背景和网页图片. 默认为 `false`. + +打印窗口的网页. 当设置 `silent` 为 `false` 的时候,Electron 将使用系统默认的打印机和打印方式来打印. + +在网页中调用 `window.print()` 和 调用 `webContents.print({silent: false, printBackground: false})`具有相同的作用. + +**注意:** 在 Windows, 打印 API 依赖于 `pdf.dll`. 如果你的应用不使用任何的打印, 你可以安全删除 `pdf.dll` 来减少二进制文件的size. + +### `webContents.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`. +* `callback` Function + +打印窗口的网页为 pdf ,使用 Chromium 预览打印的自定义设置. + +完成时使用 `callback(error, data)` 调用 `callback` . `data` 是一个 `Buffer` ,包含了生成的 pdf 数据. + +默认,空的 `options` 被视为 : + +```javascript +{ + marginsType: 0, + printBackground: false, + printSelectionOnly: false, + landscape: false +} +``` + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +const fs = require('fs'); + +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL("http://github.com"); + +win.webContents.on("did-finish-load", function() { + // Use default printing options + win.webContents.printToPDF({}, function(error, data) { + if (error) throw error; + fs.writeFile("/tmp/print.pdf", data, function(error) { + if (error) + throw error; + console.log("Write PDF successfully."); + }) + }) +}); +``` + +### `webContents.addWorkSpace(path)` + +* `path` String + +添加指定的路径给开发者工具栏的 workspace.必须在 DevTools 创建之后使用它 : + +```javascript +mainWindow.webContents.on('devtools-opened', function() { + mainWindow.webContents.addWorkSpace(__dirname); +}); +``` + +### `webContents.removeWorkSpace(path)` + +* `path` String + +从开发者工具栏的 workspace 删除指定的路径. + +### `webContents.openDevTools([options])` + +* `options` Object (可选) + * `detach` Boolean - 在一个新窗口打开开发者工具栏 + +打开开发者工具栏. + +### `webContents.closeDevTools()` + +关闭开发者工具栏. + +### `webContents.isDevToolsOpened()` + +返回布尔值,开发者工具栏是否打开. + +### `webContents.isDevToolsFocused()` + +返回布尔值,开发者工具栏视图是否获得焦点. + +### `webContents.toggleDevTools()` + +Toggles 开发者工具. + +### `webContents.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +在 (`x`, `y`) 开始检测元素. + +### `webContents.inspectServiceWorker()` + +为 service worker 上下文打开开发者工具栏. + +### `webContents.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +通过 `channel` 发送异步消息给渲染进程,你也可发送任意的参数.参数应该在 JSON 内部序列化,并且此后没有函数或原形链被包括了. + +渲染进程可以通过使用 `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!'); + }); +}); +``` + +```html + + + + + + +``` + +### `webContents.enableDeviceEmulation(parameters)` + +`parameters` Object, properties: + +* `screenPosition` String - 指定需要模拟的屏幕 + (默认 : `desktop`) + * `desktop` + * `mobile` +* `screenSize` Object - 设置模拟屏幕 size (screenPosition == mobile) + * `width` Integer - 设置模拟屏幕 width + * `height` Integer - 设置模拟屏幕 height +* `viewPosition` Object - 在屏幕放置 view + (screenPosition == mobile) (默认: `{x: 0, y: 0}`) + * `x` Integer - 设置偏移左上角的x轴 + * `y` Integer - 设置偏移左上角的y轴 +* `deviceScaleFactor` Integer - 设置设备比例因子 (如果为0,默认为原始屏幕比例) (默认: `0`) +* `viewSize` Object - 设置模拟视图 size (空表示不覆盖) + * `width` Integer - 设置模拟视图 width + * `height` Integer - 设置模拟视图 height +* `fitToView` Boolean - 如果有必要的话,是否把模拟视图按比例缩放来适应可用空间 (默认: `false`) +* `offset` Object - 可用空间内的模拟视图偏移 (不在适应模式) (默认: `{x: 0, y: 0}`) + * `x` Float - 设置相对左上角的x轴偏移值 + * `y` Float - 设置相对左上角的y轴偏移值 +* `scale` Float - 可用空间内的模拟视图偏移 (不在适应视图模式) (默认: `1`) + +使用给定的参数来开启设备模拟. + +### `webContents.disableDeviceEmulation()` + +使用 `webContents.enableDeviceEmulation` 关闭设备模拟. + +### `webContents.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`, + `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, + `numLock`, `left`, `right`. + +向 page 发送一个输入 `event` . + +对键盘事件来说,`event` 对象还有如下属性 : + +* `keyCode` String (**必需**) - 特点是将作为键盘事件发送. 可用的 key codes [Accelerator](accelerator.md). + + +对鼠标事件来说,`event` 对象还有如下属性 : + +* `x` Integer (**required**) +* `y` Integer (**required**) +* `button` String - button 按下, 可以是 `left`, `middle`, `right` +* `globalX` Integer +* `globalY` Integer +* `movementX` Integer +* `movementY` Integer +* `clickCount` Integer + +对鼠标滚轮事件来说,`event` 对象还有如下属性 : + +* `deltaX` Integer +* `deltaY` Integer +* `wheelTicksX` Integer +* `wheelTicksY` Integer +* `accelerationRatioX` Integer +* `accelerationRatioY` Integer +* `hasPreciseScrollingDeltas` Boolean +* `canScroll` Boolean + +### `webContents.beginFrameSubscription(callback)` + +* `callback` Function + +开始订阅 提交 事件和捕获数据帧,当有 提交 事件时,使用 `callback(frameBuffer)` 调用 `callback`. + +`frameBuffer` 是一个包含原始像素数据的 `Buffer`,像素数据是按照 32bit BGRA 格式有效存储的,但是实际情况是取决于处理器的字节顺序的(大多数的处理器是存放小端序的,如果是在大端序的处理器上,数据是 32bit ARGB 格式). + +### `webContents.endFrameSubscription()` + +停止订阅帧提交事件. + +### `webContents.savePage(fullPath, saveType, callback)` + +* `fullPath` String - 文件的完整路径. +* `saveType` String - 指定保存类型. + * `HTMLOnly` - 只保存html. + * `HTMLComplete` - 保存整个 page 内容. + * `MHTML` - 保存完整的 html 为 MHTML. +* `callback` Function - `function(error) {}`. + * `error` Error + +如果保存界面过程初始化成功,返回 true. + +```javascript +win.loadURL('https://github.com'); + +win.webContents.on('did-finish-load', function() { + win.webContents.savePage('/tmp/test.html', 'HTMLComplete', function(error) { + if (!error) + console.log("Save page successfully"); + }); +}); +``` + +## 实例属性 + +`WebContents` 对象也有下列属性: + +### `webContents.session` + +返回这个 `webContents` 使用的 [session](session.md) 对象. + +### `webContents.hostWebContents` + +返回这个 `webContents` 的父 `webContents` . + +### `webContents.devToolsWebContents` + +获取这个 `WebContents` 的开发者工具栏的 `WebContents` . + +**注意:** 用户不可保存这个对象,因为当开发者工具栏关闭的时候它的值为 `null` . + +### `webContents.debugger` + +调试 API 为 [remote debugging protocol][rdp] 提供交替传送. + +```javascript +try { + win.webContents.debugger.attach("1.1"); +} catch(err) { + console.log("Debugger attach failed : ", err); +}; + +win.webContents.debugger.on('detach', function(event, reason) { + console.log("Debugger detached due to : ", reason); +}); + +win.webContents.debugger.on('message', function(event, method, params) { + if (method == "Network.requestWillBeSent") { + if (params.request.url == "https://www.github.com") + win.webContents.debugger.detach(); + } +}) + +win.webContents.debugger.sendCommand("Network.enable"); +``` + +#### `webContents.debugger.attach([protocolVersion])` + +* `protocolVersion` String (可选) - 请求调试协议版本. + +添加 `webContents` 调试. + +#### `webContents.debugger.isAttached()` + +返回一个布尔值,标识是否已经给 `webContents` 添加了调试. + +#### `webContents.debugger.detach()` + +删除 `webContents` 调试. + +#### `webContents.debugger.sendCommand(method[, commandParams, callback])` + +* `method` String - 方法名, 应该是由远程调试协议定义的方法. +* `commandParams` Object (可选) - 请求参数为 JSON 对象. +* `callback` Function (可选) - Response + * `error` Object - 错误消息,标识命令失败. + * `result` Object - 回复在远程调试协议中由 'returns'属性定义的命令描述. + +发送给定命令给调试目标. + +#### Event: 'detach' + +* `event` Event +* `reason` String - 拆分调试器原因. + +在调试 session 结束时发出事件.这在 `webContents` 关闭时或 `webContents` 请求开发者工具栏时发生. + +#### Event: 'message' + +* `event` Event +* `method` String - 方法名. +* `params` Object - 在远程调试协议中由 'parameters' 属性定义的事件参数. + +每当调试目标发出事件时发出. + +[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol \ No newline at end of file diff --git a/docs-translations/zh-CN/api/web-frame.md b/docs-translations/zh-CN/api/web-frame.md new file mode 100644 index 00000000000..d278e942713 --- /dev/null +++ b/docs-translations/zh-CN/api/web-frame.md @@ -0,0 +1,101 @@ +# webFrame + +`web-frame` 模块允许你自定义如何渲染当前网页 . + +例子,放大当前页到 200%. + +```javascript +var webFrame = require('electron').webFrame; + +webFrame.setZoomFactor(2); +``` + +## 方法 + +`web-frame` 模块有如下方法: + +### `webFrame.setZoomFactor(factor)` + +* `factor` Number - 缩放参数. + +将缩放参数修改为指定的参数值.缩放参数是百分制的,所以 300% = 3.0. + +### `webFrame.getZoomFactor()` + +返回当前缩放参数值. + +### `webFrame.setZoomLevel(level)` + +* `level` Number - 缩放水平 + +将缩放水平修改为指定的水平值. 原始 size 为 0 ,并且每次增长都表示放大 20% 或缩小 20%,默认限制为原始 size 的 300% 到 50% 之间 . + +### `webFrame.getZoomLevel()` + +返回当前缩放水平值. + +### `webFrame.setZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +设置缩放水平的最大值和最小值. + +### `webFrame.setSpellCheckProvider(language, autoCorrectWord, provider)` + +* `language` String +* `autoCorrectWord` Boolean +* `provider` Object + +为输入框或文本域设置一个拼写检查 provider . + +`provider` 必须是一个对象,它有一个 `spellCheck` 方法,这个方法返回扫过的单词是否拼写正确 . + +例子,使用 [node-spellchecker][spellchecker] 作为一个 provider: + +```javascript +webFrame.setSpellCheckProvider("en-US", true, { + spellCheck: function(text) { + return !(require('spellchecker').isMisspelled(text)); + } +}); +``` + +### `webFrame.registerURLSchemeAsSecure(scheme)` + +* `scheme` String + +注册 `scheme` 为一个安全的 scheme. + + +安全的 schemes 不会引发混合内容 warnings.例如, `https` 和 +`data` 是安全的 schemes ,因为它们不能被活跃网络攻击而失效. + +### `webFrame.registerURLSchemeAsBypassingCSP(scheme)` + +* `scheme` String + +忽略当前网页内容的安全策略,直接从 `scheme` 加载. + +### `webFrame.registerURLSchemeAsPrivileged(scheme)` + +* `scheme` String + +通过资源的内容安全策略,注册 `scheme` 为安全的 scheme,允许注册 ServiceWorker并且支持 fetch API. + +### `webFrame.insertText(text)` + +* `text` String + +向获得焦点的原色插入内容 . + +### `webFrame.executeJavaScript(code[, userGesture])` + +* `code` String +* `userGesture` Boolean (可选) - 默认为 `false`. + +评估页面代码 . + +在浏览器窗口中,一些 HTML APIs ,例如 `requestFullScreen`,只可以通过用户手势来使用.设置`userGesture` 为 `true` 可以突破这个限制 . + +[spellchecker]: https://github.com/atom/node-spellchecker \ No newline at end of file diff --git a/docs-translations/zh-CN/api/web-view-tag.md b/docs-translations/zh-CN/api/web-view-tag.md new file mode 100644 index 00000000000..e1e31f6f5a0 --- /dev/null +++ b/docs-translations/zh-CN/api/web-view-tag.md @@ -0,0 +1,691 @@ +# `` 标签 + +使用 `webview` 标签来把 'guest' 内容(例如 web pages )嵌入到你的 Electron app 中. guest内容包含在 `webview` 容器中.一个嵌入你应用的page控制着guest内容如何布局摆放和表达含义. + +与 `iframe` 不同, `webview` 和你的应用运行的是不同的进程. 它不拥有渲染进程的权限,并且应用和嵌入内容之间的交互全部都是异步的.因为这能保证应用的安全性不受嵌入内容的影响. + +## 例子 + +把一个 web page 嵌入到你的app,首先添加 `webview` 标签到你的app待嵌入page(展示 guest content). 在一个最简单的 `webview` 中,它包含了 web page 的文件路径和一个控制 `webview` 容器展示效果的css样式: + +```html + +``` + +如果想随时控制 guest 内容,可以添加 JavaScript 脚本来监听 `webview` 事件使用 `webview` 方法来做出响应. 这里是2个事件监听的例子:一个监听 web page 准备加载,另一个监听 web page 停止加载,并且在加载的时候显示一条 "loading..." 信息: + +```html + +``` + +## 标签属性 + +`webview` 标签有下面一些属性 : + +### `src` + +```html + +``` + +将一个可用的url做为这个属性的初始值添加到顶部导航. + +如果把当前页的src添加进去将加载当前page. + + `src`同样可以填 data urls,例如 +`data:text/plain,Hello, world!`. + +### `autosize` + +```html + +``` + +如果这个属性的值为 "on" , `webview` 容器将会根据属性`minwidth`, `minheight`, `maxwidth`, 和 +`maxheight` 的值在它们之间自适应. 只有在 `autosize` 开启的时候这个约束才会有效. 当 `autosize` 开启的时候, `webview` 容器的 size 只能在上面那四个属性值之间. + +### `nodeintegration` + +```html + +``` + +如果设置了这个属性, `webview` 中的 guest page 将整合node,并且拥有可以使用系统底层的资源,例如 `require` 和 `process` . + +### `plugins` + +```html + +``` + +如果这个属性的值为 "on" , `webview` 中的 guest page 就可以使用浏览器插件。 + +### `preload` + +```html + +``` + +在 guest page 中的其他脚本执行之前预加载一个指定的脚本。规定预加载脚本的url须如 `file:` 或者 `asar:`,因为它在是 guest page 中通过通过 `require` 命令加载的。 + +如果 guest page 没有整合 node ,这个脚本将试图使用真个 Node APIs ,但是在这个脚本执行完毕时,之前由node插入的全局对象会被删除。 + + +### `httpreferrer` + +```html + +``` + +为 guest page 设置 referrer URL。 + +### `useragent` + +```html + +``` + +在 guest page 加载之前为其设置用户代理。如果页面已经加载了,可以使用 `setUserAgent` 方法来改变用户代理。 + +### `disablewebsecurity` + +```html + +``` + +如果这个属性的值为 "on" , guest page会禁用web安全控制. + +### partition + +```html + + +``` + +为page设置session。如果初始值为 `partition` ,这个 page 将会为app中的所有 page 应用同一个持续有效的 session。如果没有 `persist:` 前缀, 这个 page 将会使用一个历史 session 。通过分配使用相同的 `partition`, 所有的page都可以分享相同的session。如果 `partition` 没有设置,那app将使用默认的session. + +这个值只能在在第一个渲染进程之前设置修改,之后修改的话会无效并且抛出一个DOM异常. + +### `allowpopups` + +```html + +``` + +如果这个属性的值为 "on" ,将允许 guest page 打开一个新窗口。 + +### `blinkfeatures` + +```html + +``` + +这个属性的值为一个用逗号分隔的列表,它的值指定特性被启用。你可以从[setFeatureEnabledFromString][blink-feature-string]函数找到完整的支持特性。 + +## 方法 + + `webview` 的方法集合: + +**注意:** webview 元素必须在使用这些方法之前加载完毕。 + +**例如** + +```javascript +webview.addEventListener("dom-ready", function() { + webview.openDevTools(); +}); +``` + +### `.loadURL(url[, options])` + +* `url` URL +* `options` Object (可选) + * `httpReferrer` String - 一个http类型的url. + * `userAgent` String -用于发起请求的用户代理. + * `extraHeaders` String - 额外的headers,用 "\n"分隔. + +加载 webview 中的 `url`,`url` 必须包含协议前缀,例如 `http://` 或 `file://`. + +### `.getURL()` + +从 guest page 中返回 url. + +### `.getTitle()` + +从 guest page 中返回 title. + +### `.isLoading()` + +返回一个 guest page 是否仍在加载资源的布尔值. + +### `.isWaitingForResponse()` + +返回一个 guest page 是否正在等待page的主要资源做出回应的布尔值. + + +### `.stop()` + +停止渲染. + +### `.reload()` + +重新加载 guest page. + +### `.reloadIgnoringCache()` + +忽视缓存,重新加载 guest page. + +### `.canGoBack()` + +返回一个 guest page 是否能够回退的布尔值. + +### `.canGoForward()` + +返回一个 guest page 是否能够前进的布尔值. + +### `.canGoToOffset(offset)` + +* `offset` Integer + +返回一个 guest page 是否能够前进到 `offset` 的布尔值. + +### `.clearHistory()` + +清除导航历史. + +### `.goBack()` + +guest page 回退. + +### `.goForward()` + +guest page 前进. + +### `.goToIndex(index)` + +* `index` Integer + +guest page 导航到指定的绝对位置. + +### `.goToOffset(offset)` + +* `offset` Integer + +guest page 导航到指定的相对位置. + +### `.isCrashed()` + +返回一个 渲染进程是否崩溃 的布尔值. + +### `.setUserAgent(userAgent)` + +* `userAgent` String + +重新设置用户代理. + +### `.getUserAgent()` + +返回用户代理名字,返回类型:`String`. + +### `.insertCSS(css)` + +* `css` String + +插入css. + +### `.executeJavaScript(code, userGesture, callback)` + +* `code` String +* `userGesture` Boolean - 默认 `false`. +* `callback` Function (可选) - 回调函数. + * `result` + +评估 `code` ,如果 `userGesture` 值为 true ,它将在这个page里面创建用户手势. HTML APIs ,如 `requestFullScreen`,它需要用户响应,那么将自动通过这个参数优化. + +### `.openDevTools()` + +为 guest page 打开开发工具调试窗口. + +### `.closeDevTools()` + +为 guest page 关闭开发工具调试窗口. + +### `.isDevToolsOpened()` + +返回一个 guest page 是否打开了开发工具调试窗口的布尔值. + +### `.isDevToolsFocused()` + +返回一个 guest page 是否聚焦了开发工具调试窗口的布尔值. + +### `.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +开始检查 guest page 在 (`x`, `y`) 位置的元素. + +### `.inspectServiceWorker()` + +在 guest page 中为服务人员打开开发工具. + +### `.setAudioMuted(muted)` + +* `muted` Boolean +设置 guest page 流畅(muted). + +### `.isAudioMuted()` + +返回一个 guest page 是否流畅的布尔值. + +### `.undo()` + +在page中编辑执行 `undo` 命令. + +### `.redo()` + +在page中编辑执行 `redo` 命令. + +### `.cut()` + +在page中编辑执行 `cut` 命令. + +### `.copy()` + +在page中编辑执行 `copy` 命令. + +### `.paste()` + +在page中编辑执行 `paste` 命令. + +### `.pasteAndMatchStyle()` + +在page中编辑执行 `pasteAndMatchStyle` 命令. + +### `.delete()` + +在page中编辑执行 `delete` 命令. + +### `.selectAll()` + +在page中编辑执行 `selectAll` 命令. + +### `.unselect()` + +在page中编辑执行 `unselect` 命令. + +### `.replace(text)` + +* `text` String + +在page中编辑执行 `replace` 命令. + +### `.replaceMisspelling(text)` + +* `text` String + +在page中编辑执行 `replaceMisspelling` 命令. + +### `.insertText(text)` + +* `text` String + +插入文本. + +### `.findInPage(text[, options])` + +* `text` String - 搜索内容,不能为空. +* `options` Object (可选) + * `forward` Boolean - 向前或向后, 默认为 `true`. + * `findNext` Boolean - 是否查找的第一个结果, + 默认为 `false`. + * `matchCase` Boolean - 是否区分大小写, + 默认为 `false`. + * `wordStart` Boolean - 是否只查找首字母. + 默认为 `false`. + * `medialCapitalAsWordStart` Boolean - 当配合 `wordStart`的时候,接受一个文字中的匹配项,要求匹配项是以大写字母开头后面跟小写字母或者没有字母。可以接受一些其他单词内部匹配, 默认为 `false`. + +发起一个请求来寻找页面中的所有匹配 `text` 的地方并且返回一个 `Integer`来表示这个请求用的请求Id. 这个请求结果可以通过订阅[`found-in-page`](web-view-tag.md#event-found-in-page) 事件来取得. + +### `.stopFindInPage(action)` + +* `action` String - 指定一个行为来接替停止 + [`.findInPage`](web-view-tag.md#webviewtagfindinpage) 请求. + * `clearSelection` - 转变为一个普通的 selection. + * `keepSelection` - 清除 selection. + * `activateSelection` - 聚焦并点击 selection node. + +使用 `action` 停止 `findInPage` 请求. + +### `.print([options])` + +打印输出 `webview` 的 web page. 类似 `webContents.print([options])`. + +### `.printToPDF(options, callback)` + +以pdf格式打印输出 `webview` 的 web page. 类似 `webContents.printToPDF(options, callback)`. + +### `.send(channel[, arg1][, arg2][, ...])` + +* `channel` String +* `arg` (可选) + +通过 `channel` 向渲染进程发出异步消息,你也可以发送任意的参数。 +渲染进程通过`ipcRenderer` 模块监听 `channel` 事件来控制消息. + +例子 +[webContents.send](web-contents.md#webcontentssendchannel-args). + +### `.sendInputEvent(event)` + +* `event` Object + +向 page 发送输入事件. + +查看 [webContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent) +关于 `event` 对象的相信介绍. + +### `.getWebContents()` + +返回和这个 `webview` 相关的 [WebContents](web-contents.md). + +## DOM 事件 + +`webview` 可用下面的 DOM 事件: + +### Event: 'load-commit' + +返回: + +* `url` String +* `isMainFrame` Boolean + +加载完成触发. 这个包含当前文档的导航和副框架的文档加载,但是不包含异步资源加载. + +### Event: 'did-finish-load' + +在导航加载完成时触发,也就是tab 的 spinner停止spinning,并且加载事件处理. + +### Event: 'did-fail-load' + +Returns: + +* `errorCode` Integer +* `errorDescription` String +* `validatedURL` String + +类似 `did-finish-load` ,在加载失败或取消是触发,例如提出 `window.stop()`. + +### Event: 'did-frame-finish-load' + +返回: + +* `isMainFrame` Boolean + +当一个 frame 完成 加载时触发. + +### Event: 'did-start-loading' + +开始加载时触发. + +### Event: 'did-stop-loading' + +停止家在时触发. + +### Event: 'did-get-response-details' + +返回: + +* `status` Boolean +* `newURL` String +* `originalURL` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object + +当获得返回详情的时候触发. + +`status` 指示 socket 连接来下载资源. + +### Event: 'did-get-redirect-request' + +返回: + +* `oldURL` String +* `newURL` String +* `isMainFrame` Boolean + +当重定向请求资源被接收的时候触发. + +### Event: 'dom-ready' + +当指定的frame文档加载完毕时触发. + +### Event: 'page-title-updated' + +返回: + +* `title` String +* `explicitSet` Boolean + +当导航中的页面title被设置时触发. +在title通过文档路径异步加载时`explicitSet`为false. + +### Event: 'page-favicon-updated' + +返回: + +* `favicons` Array - Array of URLs. + +当page收到了图标url时触发. + +### Event: 'enter-html-full-screen' + +当通过HTML API使界面进入全屏时触发. + +### Event: 'leave-html-full-screen' + +当通过HTML API使界面退出全屏时触发. + +### Event: 'console-message' + +返回: + +* `level` Integer +* `message` String +* `line` Integer +* `sourceId` String + +当客户端输出控制台信息的时候触发. + +下面示例代码将所有信息输出到内置控制台,没有考虑到输出等级和其他属性。 + +```javascript +webview.addEventListener('console-message', function(e) { + console.log('Guest page logged a message:', e.message); +}); +``` + +### Event: 'found-in-page' + +返回: + +* `result` Object + * `requestId` Integer + * `finalUpdate` Boolean - 指明下面是否还有更多的回应. + * `activeMatchOrdinal` Integer (可选) - 活动匹配位置 + * `matches` Integer (optional) - 匹配数量. + * `selectionArea` Object (optional) - 整合第一个匹配域. + +在请求[`webview.findInPage`](web-view-tag.md#webviewtagfindinpage)结果有效时触发. + +```javascript +webview.addEventListener('found-in-page', function(e) { + if (e.result.finalUpdate) + webview.stopFindInPage("keepSelection"); +}); + +const rquestId = webview.findInPage("test"); +``` + +### Event: 'new-window' + +返回: + +* `url` String +* `frameName` String +* `disposition` String - 可以为 `default`, `foreground-tab`, `background-tab`, + `new-window` 和 `other`. +* `options` Object - 参数应该被用作创建新的 + `BrowserWindow`. + +在 guest page 试图打开一个新的浏览器窗口时触发. + +下面示例代码在系统默认浏览器中打开了一个新的url. + +```javascript +webview.addEventListener('new-window', function(e) { + require('electron').shell.openExternal(e.url); +}); +``` + +### Event: 'will-navigate' + +返回: + +* `url` String + +当用户或page尝试开始导航时触发. +它能在 `window.location` 变化或者用户点击连接的时候触发. + +这个事件在以 APIS 编程方式开始导航时不会触发,例如 `.loadURL` 和 `.back`. + +在页面内部导航跳转也将不回触发这个事件,例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 来实现页内跳转事件. + +使用 `event.preventDefault()` 并不会起什么用. + +### Event: 'did-navigate' + +返回: + +* `url` String + +当导航结束时触发. + +在页面内部导航跳转也将不回触发这个事件,例如点击锚链接或更新 `window.location.hash`.使用 `did-navigate-in-page` 来实现页内跳转事件. + +### Event: 'did-navigate-in-page' + +返回: + +* `url` String + +当页内导航发生时触发. +当业内导航发生时,page url改变了,但是不会跳出 page . 例如在锚链接被电击或DOM `hashchange` 事件发生时触发. + +### Event: 'close' + +在 guest page试图关闭自己的时候触发. + +下面的示例代码指示了在客户端试图关闭自己的时候将改变导航连接为`about:blank`. + +```javascript +webview.addEventListener('close', function() { + webview.src = 'about:blank'; +}); +``` + +### Event: 'ipc-message' + +返回: + +* `channel` String +* `args` Array + +在 guest page 向嵌入页发送一个异步消息的时候触发. + +你可以很简单的使用 `sendToHost` 方法和 `ipc-message` 事件在 guest page 和 嵌入页(embedder page)之间通信: + +```javascript +// In embedder page. +webview.addEventListener('ipc-message', function(event) { + console.log(event.channel); + // Prints "pong" +}); +webview.send('ping'); +``` + +```javascript +// In guest page. +var ipcRenderer = require('electron').ipcRenderer; +ipcRenderer.on('ping', function() { + ipcRenderer.sendToHost('pong'); +}); +``` + +### Event: 'crashed' + +在渲染进程崩溃的时候触发. + +### Event: 'gpu-crashed' + +在GPU进程崩溃的时候触发. + +### Event: 'plugin-crashed' + +返回: + +* `name` String +* `version` String + +在插件进程崩溃的时候触发. + +### Event: 'destroyed' + +在界面内容销毁的时候触发. + +### Event: 'media-started-playing' + +在媒体准备播放的时候触发. + +### Event: 'media-paused' + +在媒体暂停播放或播放放毕的时候触发. + +### Event: 'did-change-theme-color' + +在页面的主体色改变的时候触发. +在使用 meta 标签的时候这就很常见了: + +```html + +``` + +### Event: 'devtools-opened' + +在开发者工具打开的时候触发. + +### Event: 'devtools-closed' + +在开发者工具关闭的时候触发. + +### Event: 'devtools-focused' + +在开发者工具获取焦点的时候触发. + +[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 \ No newline at end of file diff --git a/docs-translations/zh-CN/api/window-open.md b/docs-translations/zh-CN/api/window-open.md new file mode 100644 index 00000000000..069e2c35226 --- /dev/null +++ b/docs-translations/zh-CN/api/window-open.md @@ -0,0 +1,60 @@ +# `window.open` 函数 + +当在界面中使用 `window.open` 来创建一个新的窗口时候,将会创建一个 `BrowserWindow` 的实例,并且将返回一个标识,这个界面通过标识来对这个新的窗口进行有限的控制. + +这个标识对传统的web界面来说,通过它能对子窗口进行有限的功能性兼容控制. +想要完全的控制这个窗口,可以直接创建一个 `BrowserWindow` . + +新创建的 `BrowserWindow` 默认为继承父窗口的属性参数,想重写属性的话可以在 `features` 中设置他们. + +### `window.open(url[, frameName][, features])` + +* `url` String +* `frameName` String (可选) +* `features` String (可选) + +创建一个新的window并且返回一个 `BrowserWindowProxy` 类的实例. + + `features` 遵循标准浏览器的格式,但是每个feature 应该作为 `BrowserWindow` 参数的一个字段. + +### `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/build-instructions-linux.md b/docs-translations/zh-CN/development/build-instructions-linux.md new file mode 100644 index 00000000000..0f76e78b9a5 --- /dev/null +++ b/docs-translations/zh-CN/development/build-instructions-linux.md @@ -0,0 +1,123 @@ +# Build Instructions (Linux) + +遵循下面的引导,在 Linux 上构建 Electron . + +## Prerequisites + +* Python 2.7.x. 一些发行版如 CentOS 仍然使用 Python 2.6.x ,所以或许需要 check 你的 Python 版本,使用 `python -V`. +* Node.js v0.12.x. 有很多方法来安装 Node. 可以从 [Node.js](http://nodejs.org)下载原文件并且编译它 .也可以作为一个标准的用户在 home 目录下安装 node .或者尝试使用仓库 [NodeSource](https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories). +* Clang 3.4 或更新的版本. +* GTK+开发头文件和libnotify. + +在 Ubuntu, 安装下面的库 : + +```bash +$ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ + libnotify-dev libgnome-keyring-dev libgconf2-dev \ + libasound2-dev libcap-dev libcups2-dev libxtst-dev \ + libxss1 libnss3-dev gcc-multilib g++-multilib +``` + +在 Fedora, 安装下面的库 : + +```bash +$ sudo yum install clang dbus-devel gtk2-devel libnotify-devel libgnome-keyring-devel \ + xorg-x11-server-utils libcap-devel cups-devel libXtst-devel \ + alsa-lib-devel libXrandr-devel GConf2-devel nss-devel +``` + +其它版本的也许提供了相似的包来安装,通过包管理器,例如 pacman. +或一个可以编译源文件的. + +## 使用虚拟机 + +如果在虚拟机上构建 Electron,你需要一个固定大小的设备,至少需要 25 gigabytes . + +## 获取代码 + +```bash +$ git clone https://github.com/atom/electron.git +``` + +## Bootstrapping + +bootstrap 脚本也是必要下载的构建依赖,来创建项目文件.需要使用 Python 2.7.x 来让脚本成功执行.正确下载文件会花费较长的时间. +注意我们使用的是 `ninja` 来构建 Electron,所以没有生成 `Makefile` 项目. + +```bash +$ cd electron +$ ./script/bootstrap.py -v +``` + +### 交叉编译 + +如果想创建一个 `arm` target ,应当还要下载下面的依赖 : + +```bash +$ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \ + g++-arm-linux-gnueabihf +``` + +为了编译 `arm` 或 `ia32` targets, 你应当为 `bootstrap.py` 脚本使用 +`--target_arch` 参数: + +```bash +$ ./script/bootstrap.py -v --target_arch=arm +``` + +## 构建 + +创建 `Release` 、 `Debug` target: + +```bash +$ ./script/build.py +``` + +这个脚本也许会在目录 `out/R` 下创建一个巨大的可执行的 Electron . 文件大小或许会超过 1.3 gigabytes. 原因是 Release target 二进制文件包含了 调试符号 .运行 `create-dist.py` 脚本来减小文件的 size : + +```bash +$ ./script/create-dist.py +``` +这会在 `dist` 目录下创建一个有大量小文件的工作空间. 运行 create-dist.py 脚本之后, 或许你想删除仍然在 `out/R` 下的 1.3+ gigabyte 二进制文件. + +可以只创建 `Debug` target: + +```bash +$ ./script/build.py -c D +``` + +创建完毕, 可以在 `out/D`下面找到 `electron`. + +## Cleaning + +删除构建文件 : + +```bash +$ ./script/clean.py +``` + +## 解决问题 + +确保你已经安装了所有的依赖 . + +### Error While Loading Shared Libraries: libtinfo.so.5 + +预构建的 `clang` 会尝试链接到 `libtinfo.so.5`. 取决于 host 架构, 适当的使用 `libncurses`: + +```bash +$ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5 +``` + +## Tests + +测试你的修改是否符合项目代码风格,使用: + +```bash +$ ./script/cpplint.py +``` + +测试有效性使用: + +```bash +$ ./script/test.py +``` \ No newline at end of file diff --git a/docs-translations/zh-CN/development/build-instructions-osx.md b/docs-translations/zh-CN/development/build-instructions-osx.md new file mode 100644 index 00000000000..15485e57acc --- /dev/null +++ b/docs-translations/zh-CN/development/build-instructions-osx.md @@ -0,0 +1,62 @@ +# Build Instructions (OS X) + +遵循下面的引导,在 OS X 上构建 Electron . + +## 前提 + +* OS X >= 10.8 +* [Xcode](https://developer.apple.com/technologies/tools/) >= 5.1 +* [node.js](http://nodejs.org) (外部) + +如果你通过 Homebrew 使用 Python 下载,需要安装下面的 Python 模块: + +* pyobjc + +## 获取代码 + +```bash +$ git clone https://github.com/atom/electron.git +``` + +## Bootstrapping + +bootstrap 脚本也是必要下载的构建依赖,来创建项目文件.注意我们使用的是 [ninja](https://ninja-build.org/) 来构建 Electron,所以没有生成 Xcode 项目. + +```bash +$ cd electron +$ ./script/bootstrap.py -v +``` + +## 构建 + +创建 `Release` 、 `Debug` target: + +```bash +$ ./script/build.py +``` + +可以只创建 `Debug` target: + +```bash +$ ./script/build.py -c D +``` + +创建完毕, 可以在 `out/D` 下面找到 `Electron.app`. + +## 32位支持 + +在 OS X 上,构建 Electron 只支持 64位的,不支持 32位的 . + +## 测试 + +测试你的修改是否符合项目代码风格,使用: + +```bash +$ ./script/cpplint.py +``` + +测试有效性使用: + +```bash +$ ./script/test.py +``` \ No newline at end of file diff --git a/docs-translations/zh-CN/development/build-instructions-windows.md b/docs-translations/zh-CN/development/build-instructions-windows.md new file mode 100644 index 00000000000..7b11dc7f57f --- /dev/null +++ b/docs-translations/zh-CN/development/build-instructions-windows.md @@ -0,0 +1,136 @@ +# Build Instructions (Windows) + +遵循下面的引导,在 Windows 上构建 Electron . + +## 前提 + +* Windows 7 / Server 2008 R2 or higher +* Visual Studio 2013 with Update 4 - [download VS 2013 Community Edition for + free](https://www.visualstudio.com/news/vs2013-community-vs). +* [Python 2.7](http://www.python.org/download/releases/2.7/) +* [Node.js](http://nodejs.org/download/) +* [Git](http://git-scm.com) + +如果你现在还没有安装 Windows , [modern.ie](https://www.modern.ie/en-us/virtualization-tools#downloads) 有一个 timebombed 版本的 Windows ,你可以用它来构建 Electron. + +构建 Electron 完全的依赖于命令行,并且不可通过 Visual Studio. +可以使用任何的编辑器来开发 Electron ,未来会支持 Visual Studio. + +**注意:** 虽然 Visual Studio 不是用来构建的,但是它仍然 +**必须的** ,因为我们需要它提供的构建工具栏. + +**注意:** Visual Studio 2015 不可用. 请确定使用 MSVS +**2013**. + +## 获取代码 + +```powershell +$ git clone https://github.com/atom/electron.git +``` + +## Bootstrapping + +bootstrap 脚本也是必要下载的构建依赖,来创建项目文件.注意我们使用的是 `ninja` 来构建 Electron,所以没有生成 Visual Studio 项目. + +```powershell +$ cd electron +$ python script\bootstrap.py -v +``` + +## 构建 + +创建 `Release` 、 `Debug` target: + +```powershell +$ python script\build.py +``` + +可以只创建 `Debug` target: + +```powershell +$ python script\build.py -c D +``` + +创建完毕, 可以在 `out/D`(debug target) 或 `out\R` (release target) 下面找到 `electron.exe`. + +## 64bit Build + +为了构建64位的 target,在运行 bootstrap 脚本的时候需要使用 `--target_arch=x64` : + +```powershell +$ python script\bootstrap.py -v --target_arch=x64 +``` + +其他构建步骤完全相同. + +## Tests + +测试你的修改是否符合项目代码风格,使用: + +```powershell +$ python script\cpplint.py +``` + +测试有效性使用: + +```powershell +$ python script\test.py +``` +在构建 debug 时为 Tests包含原生模块 (例如 `runas`) 将不会执行(详情 [#2558](https://github.com/atom/electron/issues/2558)), 但是它们在构建 release 会起效. + +运行 release 构建使用 : + +```powershell +$ python script\test.py -R +``` + +## 解决问题 + +### Command xxxx not found + +如果你遇到了一个错误,类似 `Command xxxx not found`, 可以尝试使用 `VS2012 Command Prompt` 控制台来执行构建脚本 . + +### Fatal internal compiler error: C1001 + +确保你已经安装了 Visual Studio 的最新安装包 . + +### Assertion failed: ((handle))->activecnt >= 0 + +如果在 Cygwin 下构建的,你可能会看到 `bootstrap.py` 失败并且附带下面错误 : + +``` +Assertion failed: ((handle))->activecnt >= 0, file src\win\pipe.c, line 1430 + +Traceback (most recent call last): + File "script/bootstrap.py", line 87, in + sys.exit(main()) + File "script/bootstrap.py", line 22, in main + update_node_modules('.') + File "script/bootstrap.py", line 56, in update_node_modules + execute([NPM, 'install']) + File "/home/zcbenz/codes/raven/script/lib/util.py", line 118, in execute + raise e +subprocess.CalledProcessError: Command '['npm.cmd', 'install']' returned non-zero exit status 3 +``` + +这是由同时使用 Cygwin Python 和 Win32 Node 造成的 bug.解决办法就是使用 Win32 Python 执行 bootstrap 脚本 (假定你已经在目录 `C:\Python27` 下安装了 Python): + +```powershell +$ /cygdrive/c/Python27/python.exe script/bootstrap.py +``` + +### LNK1181: cannot open input file 'kernel32.lib' + +重新安装 32位的 Node.js. + +### Error: ENOENT, stat 'C:\Users\USERNAME\AppData\Roaming\npm' + +简单创建目录 [应该可以解决问题](http://stackoverflow.com/a/25095327/102704): + +```powershell +$ mkdir ~\AppData\Roaming\npm +``` + +### node-gyp is not recognized as an internal or external command + +如果你使用 Git Bash 来构建,或许会遇到这个错误,可以使用 PowerShell 或 VS2012 Command Prompt 来代替 . \ No newline at end of file diff --git a/docs-translations/zh-CN/development/build-system-overview.md b/docs-translations/zh-CN/development/build-system-overview.md new file mode 100644 index 00000000000..6bd1452b816 --- /dev/null +++ b/docs-translations/zh-CN/development/build-system-overview.md @@ -0,0 +1,42 @@ +# Build System Overview + +Electron 使用 [gyp](https://gyp.gsrc.io/) 来生成项目 ,使用 [ninja](https://ninja-build.org/) 来构建项目. 项目配置可以在 `.gyp` 和 `.gypi` 文件中找到. + +## Gyp 文件 + +下面的 `gyp` 文件包含了构建 Electron 的主要规则 : + +* `atom.gyp` 定义了 Electron 它自己是怎样被构建的. +* `common.gypi` 调整 node 的构建配置,来让它结合 Chromium 一起构建. +* `vendor/brightray/brightray.gyp` 定义了 `brightray` 是如何被构建的,并且包含了默认配置来连接到 Chromium. +* `vendor/brightray/brightray.gypi` 包含了常用的创建配置. + +## 创建组件 + +在 Chromium 还是一个相当大的项目的时候,最后链接阶段会花了好几分钟,这让开发变得很困难. 为了解决这个困难,Chromium 引入了 "component build" ,这让每个创建的组建都是分隔开的共享库,让链接更快,但是这浪费了文件大小和性能. + +在 Electron 中,我们采用了一个非常相似的方法 : 在创建 `Debug` , 二进制文件会被链接进入一个 Chromium 组件的共享版本库来达到快速链接; 在创建 `Release`, 二进制文件会被链接进入一个静态版本库, 所以我们可以有最小的二进制文件size和最佳的体验. + +## Minimal Bootstrapping + +在运行 bootstrap 脚本的时候,所有的 Chromium 预编译二进制文件会被下载.默认静态库和共享库会被下载,并且项目的最后大小会在 800MB 到 2GB 之间,这取决于平台类型. + +默认,`libchromiumcontent` 是从 Amazon Web Services 上下载下来的.如果设置了 `LIBCHROMIUMCONTENT_MIRROR` 环境变量,bootstrap脚本会从这里下载下来. [`libchromiumcontent-qiniu-mirror`](https://github.com/hokein/libchromiumcontent-qiniu-mirror) 是 `libchromiumcontent` 的映射.如果你不能连接 AWS,你可以切换下载路径:`export LIBCHROMIUMCONTENT_MIRROR=http://7xk3d2.dl1.z0.glb.clouddn.com/` +如果只是想快速搭建一个 Electron 的测试或开发环境,可以通过 `--dev` 参数只下载共享版本库: + +```bash +$ ./script/bootstrap.py --dev +$ ./script/build.py -c D +``` + +## Two-Phase Project Generation + +在 `Release` 和 `Debug` 构建的时候后,Electron 链接了不同配置的库 .然而 `gyp`不支持为不同的配置文件进行不同的链接设置. + +为了规避这个问题,Electron 在运行 `gyp` 的时候,使用了一个 `gyp` 的变量 `libchromiumcontent_component`来控制应该使用哪个链接设置,并且只生成一个目标. + +## Target Names + +与大多数的项目不同,它们使用 `Release` 和 `Debug` 作为目标名字,而 Electron 使用使用的是 `R` 和 `D`.这是因为如果只定义了一个 `Release` 或 `Debug` 构建配置,`gyp` 会随机崩溃,并且在同一时候,Electron 只生成一个目标,如上所述. + +这只对开发者可用,如果想重新构建 Electron ,将不会成功. \ No newline at end of file diff --git a/docs-translations/zh-CN/development/setting-up-symbol-server.md b/docs-translations/zh-CN/development/setting-up-symbol-server.md new file mode 100644 index 00000000000..01098bcc77c --- /dev/null +++ b/docs-translations/zh-CN/development/setting-up-symbol-server.md @@ -0,0 +1,38 @@ +# Setting Up Symbol Server in Debugger + +调试 symbols 让你有更好的调试 sessions. 它们有可执行的动态库的函数信息,并且提供信息来获得洁净的呼叫栈. 一个 Symbol 服务器允许调试器自动加载正确的 symbols, 二进制文件 和 资源文件,不用再去强制用户下载巨大的调试文件. 服务器函数类似 +[Microsoft's symbol server](http://support.microsoft.com/kb/311503) ,所以这里的记录可用. + +注意,因为公众版本的 Electron 构建是最优化的,调试不一定一直简单.调试器将不会给显示出所有变量内容,并且因为内联,尾调用,和其它编译器优化,执行路径会看起来很怪异 . 唯一的解决办法是搭建一个不优化的本地构建. + +Electron 使用的官方 symbol 服务器地址为 +`http://54.249.141.255:8086/atom-shell/symbols` . +你不能直接访问这个路径,必须将其添加到你的调试工具的 symbol 路径上.在下面的例子中,使用了一个本地缓存目录来避免重复从服务器获取 PDB. 在你的电脑上使用一个恰当的缓存目录来代替 `c:\code\symbols` . + +## Using the Symbol Server in Windbg + +Windbg symbol 路径被配制为一个限制带星号字符的字符串. 要只使用 Electron 的 symbol 服务器, 将下列记录添加到你的 symbol 路径 (__注意:__ 如果你愿意使用一个不同的地点来下载 symbols,你可以在你的电脑中使用任何可写的目录来代替 `c:\code\symbols`): + +``` +SRV*c:\code\symbols\*http://54.249.141.255:8086/atom-shell/symbols +``` + +使用 Windbg 菜单或通过输入 `.sympath` 命令,在环境中设置一个 `_NT_SYMBOL_PATH` 字符串.如果你也想从微软的 symbol 服务器获得 symbols ,你应当首先将它们先列出来 : + +``` +SRV*c:\code\symbols\*http://msdl.microsoft.com/download/symbols;SRV*c:\code\symbols\*http://54.249.141.255:8086/atom-shell/symbols +``` + +## 在 Visual Studio 中使用 symbol 服务器 + + + + +## Troubleshooting: Symbols will not load + +在 Windbg 中输入下列命令,打印出未什么 symbols 没有加载 : + +``` +> !sym noisy +> .reload /f chromiumcontent.dll +``` \ No newline at end of file diff --git a/docs-translations/zh-CN/faq/electron-faq.md b/docs-translations/zh-CN/faq/electron-faq.md new file mode 100644 index 00000000000..3431c6018ab --- /dev/null +++ b/docs-translations/zh-CN/faq/electron-faq.md @@ -0,0 +1,139 @@ +# Electron 常见问题 + +## Electron 会在什么时候升级到最新版本的 Chrome? + +通常来说,在稳定版的 Chrome 发布后两周内,我们会更新 Electron 内的 Chrome 版本。 + +我们只会使用 stable 版本的 Chrome。但如果在 beta 或 dev 版本中有一个重要的更新,我们会把补丁应用到现版本的 Chrome 上。 + +## Electron 会在什么时候升级到最新版本的 Node.js? + +我们通常会在最新版的 Node.js 发布后一个月左右将 Electron 更新到这个版本的 Node.js。我们通过这种方式来避免新版本的 Node.js +带来的 bug(这种 bug 太常见了)。 + +Node.js 的新特性通常是由新版本的 V8 带来的。由于 Electron 使用的是 Chrome 浏览器中附带的 V8 引擎,所以 Electron 内往往已经 +有了部分新版本 Node.js 才有的崭新特性。 + +## 如何在两个网页间共享数据? + +在两个网页(渲染进程)间共享数据最简单的方法是使用浏览器中已经实现的 HTML5 API,比较好的方案是用 [Storage API][storage], +[`localStorage`][local-storage],[`sessionStorage`][session-storage] 或者 [IndexedDB][indexed-db]。 + +你还可以用 Electron 内的 IPC 机制实现。将数据存在主进程的某个全局变量中,然后在多个渲染进程中使用 `remote` 模块来访问它。 + +```javascript +// 在主进程中 +global.sharedObject = { + someProperty: 'default value' +}; +``` + +```javascript +// 在第一个页面中 +require('remote').getGlobal('sharedObject').someProperty = 'new value'; +``` + +```javascript +// 在第二个页面中 +console.log(require('remote').getGlobal('sharedObject').someProperty); +``` + +## 为什么应用的窗口、托盘在一段时间后不见了? + +这通常是因为用来存放窗口、托盘的变量被垃圾收集了。 + +你可以参考以下两篇文章来了解为什么会遇到这个问题。 + +* [内存管理][memory-management] +* [变量作用域][variable-scope] + +如果你只是要一个快速的修复方案,你可以用下面的方式改变变量的作用域,防止这个变量被垃圾收集。 + +从 + +```javascript +app.on('ready', function() { + var tray = new Tray('/path/to/icon.png'); +}) +``` + +改为 + +```javascript +var tray = null; +app.on('ready', function() { + tray = new Tray('/path/to/icon.png'); +}) +``` + +## 在 Electron 中,我为什么不能用 jQuery、RequireJS、Meteor、AngularJS? + +因为 Electron 在运行环境中引入了 Node.js,所以在 DOM 中有一些额外的变量,比如 `module`、`exports` 和 `require`。这导致 +了许多库不能正常运行,因为它们也需要将同名的变量加入运行环境中。 + +我们可以通过禁用 Node.js 来解决这个问题,用如下的方式: + +```javascript +// 在主进程中 +var mainWindow = new BrowserWindow({ + webPreferences: { + nodeIntegration: false + } +}); +``` + +假如你依然需要使用 Node.js 和 Electron 提供的 API,你需要在引入那些库之前将这些变量重命名,比如: + +```html + + + + +``` + +## 为什么 `require('electron').xxx` 的结果是 undefined? + +在使用 Electron 的提供的模块时,你可能会遇到和以下类似的错误: + +``` +> require('electron').webFrame.setZoomFactor(1.0); +Uncaught TypeError: Cannot read property 'setZoomLevel' of undefined +``` + +这是因为你在项目中或者在全局中安装了[npm 上获取的 `electron` 模块][electron-module],它把 Electron 的内置模块覆写了。 + +你可以通过以下方式输出 `electron` 模块的路径来确认你是否使用了正确的模块。 + +```javascript +console.log(require.resolve('electron')); +``` + +确认以下它是不是像下面这样的: + +``` +"/path/to/Electron.app/Contents/Resources/atom.asar/renderer/api/lib/exports/electron.js" +``` + +假如输出的路径类似于 `node_modules/electron/index.js`,那么你需要移除或者重命名 npm 上的 `electron` 模块。 + +```bash +npm uninstall electron +npm uninstall -g electron +``` + +如果你依然遇到了这个问题,你可能需要检查一下拼写或者是否在错误的进程中调用了这个模块。比如, +`require('electron').app` 只能在主进程中使用, 然而 `require('electron').webFrame` 只能在渲染进程中使用。 + +[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/zh-CN/tutorial/application-distribution.md b/docs-translations/zh-CN/tutorial/application-distribution.md index c1fddce15ea..f3cf3692b5d 100644 --- a/docs-translations/zh-CN/tutorial/application-distribution.md +++ b/docs-translations/zh-CN/tutorial/application-distribution.md @@ -1,6 +1,7 @@ # 应用部署 -为了使用Electron部署你的应用程序,你存放应用程序的文件夹需要叫做 `app` 并且需要放在 Electron 的资源文件夹下(在 OS X 中是指 `Electron.app/Contents/Resources/`,在 Linux 和 Windows 中是指 `resources/`) +为了使用 Electron 部署你的应用程序,你存放应用程序的文件夹需要叫做 `app` 并且需要放在 Electron 的 +资源文件夹下(在 OS X 中是指 `Electron.app/Contents/Resources/`,在 Linux 和 Windows 中是指 `resources/`) 就像这样: 在 OS X 中: @@ -55,12 +56,12 @@ electron/resources/ ### Windows 你可以将 `electron.exe` 改成任意你喜欢的名字,然后可以使用像 -[rcedit](https://github.com/atom/rcedit) 或者[ResEdit](http://www.resedit.net) -编辑它的icon和其他信息。 +[rcedit](https://github.com/atom/rcedit) +编辑它的 icon 和其他信息。 ### OS X -你可以将 `Electron.app` 改成任意你喜欢的名字,然后你也需要修改这些文件中的 +你可以将 `Electron.app` 改成任意你喜欢的名字,然后你也需要修改这些文件中的 `CFBundleDisplayName`, `CFBundleIdentifier` 以及 `CFBundleName` 字段。 这些文件如下: @@ -103,7 +104,7 @@ MyApp.app/Contents ### grunt打包脚本 -手动的检查 Electron 代码并重编译是很复杂晦涩的,因此有一个 Grunt任务可以自动自动的处理 +手动检查 Electron 代码并重编译是很复杂晦涩的,因此有一个Grunt任务可以自动的处理 这些内容 [grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). 这个任务会自动的处理编辑 `.gyp` 文件,从源代码进行编译,然后重编译你的应用程序的本地 Node 模块以匹配这个新的可执行文件的名称。 diff --git a/docs-translations/zh-CN/tutorial/application-packaging.md b/docs-translations/zh-CN/tutorial/application-packaging.md index ee4b7bf63b8..13a24d2d1c3 100644 --- a/docs-translations/zh-CN/tutorial/application-packaging.md +++ b/docs-translations/zh-CN/tutorial/application-packaging.md @@ -1,34 +1,38 @@ # 应用打包 -为舒缓Windows下路径名过长的问题[issues](https://github.com/joyent/node/issues/6960), 也略对`require`加速以及简单隐匿你的源代码, 你可以通过极小的源代码改动将你的应用打包成[asar][asar]. +为舒缓 Windows 下路径名过长的问题[issues](https://github.com/joyent/node/issues/6960), +也略对 `require` 加速以及简单隐匿你的源代码,你可以通过极小的源代码改动将你的应用打包成 [asar][asar]。 -## 生成`asar`包 +## 生成 `asar` 包 -[asar][asar]是一种将多个文件合并成一个文件的类tar风格的归档格式。 Electron可以无需解压,即从其中读取任意文件内容。 +[asar][asar] 是一种将多个文件合并成一个文件的类 tar 风格的归档格式。 +Electron 可以无需解压,即从其中读取任意文件内容。 -参照如下步骤将你的应用打包成`asar`: +参照如下步骤将你的应用打包成 `asar`: -### 1. 安装asar +### 1. 安装 asar ```bash $ npm install -g asar ``` -### 2. 用`asar pack`打包 +### 2. 用 `asar pack` 打包 ```bash $ asar pack your-app app.asar ``` -## 使用`asar`包 +## 使用 `asar` 包 -在Electron中有两类APIs:Node.js提供的Node APIs和Chromium提供的Web APIs。这两种APIs都支持从`asar`包中读取文件。 +在 Electron 中有两类 APIs:Node.js 提供的 Node API 和 Chromium 提供的 Web API。 +这两种 API 都支持从 `asar` 包中读取文件。 ### Node API -由于Electron中打了特别补丁, Node APIs中如`fs.readFile`或者`require`之类的方法可以将`asar`视之为虚拟文件夹,读取`asar`里面的文件就和从真实的文件系统中读取一样。 +由于 Electron 中打了特别补丁, Node API 中如 `fs.readFile` 或者 `require` 之类 +的方法可以将 `asar` 视之为虚拟文件夹,读取 `asar` 里面的文件就和从真实的文件系统中读取一样。 -例如,假设我们在`/path/to`文件夹下有个`example.asar`包: +例如,假设我们在 `/path/to` 文件夹下有个 `example.asar` 包: ```bash $ asar list /path/to/example.asar @@ -40,27 +44,27 @@ $ asar list /path/to/example.asar /static/jquery.min.js ``` -从`asar`包读取一个文件: +从 `asar` 包读取一个文件: ```javascript const fs = require('fs'); fs.readFileSync('/path/to/example.asar/file.txt'); ``` -列出`asar`包中根目录下的所有文件: +列出 `asar` 包中根目录下的所有文件: ```javascript const fs = require('fs'); fs.readdirSync('/path/to/example.asar'); ``` -使用`asar`包中的一个模块: +使用 `asar` 包中的一个模块: ```javascript require('/path/to/example.asar/dir/module.js'); ``` -你也可以使用`BrowserWindow`来显示一个`asar`包里的web页面: +你也可以使用 `BrowserWindow` 来显示一个 `asar` 包里的 web 页面: ```javascript const BrowserWindow = require('electron').BrowserWindow; @@ -70,9 +74,9 @@ win.loadURL('file:///path/to/example.asar/static/index.html'); ### Web API -在Web页面里,用`file:`协议可以获取`asar`包中文件。和Node API一样,视`asar`包如虚拟文件夹。 +在 Web 页面里,用 `file:` 协议可以获取 `asar` 包中文件。和 Node API 一样,视 `asar` 包如虚拟文件夹。 -例如,用`$.get`获取文件: +例如,用 `$.get` 获取文件: ```html ``` -### 像“文件”那样处理`asar`包 +### 像“文件”那样处理 `asar` 包 -有些场景,如:核查`asar`包的校验和,我们需要像读取“文件”那样读取`asar`包的内容(而不是当成虚拟文件夹)。你可以使用内置的`original-fs`(提供和`fs`一样的APIs)模块来读取`asar`包的真实信息。 +有些场景,如:核查 `asar` 包的校验和,我们需要像读取“文件”那样读取 `asar` 包的内容(而不是当成虚拟文件夹)。 +你可以使用内置的 `original-fs` (提供和 `fs` 一样的 API)模块来读取 `asar` 包的真实信息。 ```javascript var originalFs = require('original-fs'); originalFs.readFileSync('/path/to/example.asar'); ``` -## Node API缺陷 +## Node API 缺陷 -尽管我们已经尽了最大努力使得`asar`包在Node API下的应用尽可能的趋向于真实的目录结构,但仍有一些底层Node API我们无法保证其正常工作。 +尽管我们已经尽了最大努力使得 `asar` 包在 Node API 下的应用尽可能的趋向于真实的目录结构,但仍有一些底层 Node API 我们无法保证其正常工作。 -### `asar`包是只读的 +### `asar` 包是只读的 -`asar`包中的内容不可更改,所以Node APIs里那些可以用来修改文件的方法在对待`asar`包时都无法正常工作。 +`asar` 包中的内容不可更改,所以 Node APIs 里那些可以用来修改文件的方法在对待 `asar` 包时都无法正常工作。 -### Working Directory在`asar`包中无效 +### Working Directory 在 `asar` 包中无效 -尽管`asar`包是虚拟文件夹,但其实并没有真实的目录架构对应在文件系统里,所以你不可能将working Directory设置成`asar`包里的一个文件夹。将`asar`中的文件夹以`cwd`形式作为参数传入一些API中也会报错。 +尽管 `asar` 包是虚拟文件夹,但其实并没有真实的目录架构对应在文件系统里,所以你不可能将 working Directory +设置成 `asar` 包里的一个文件夹。将 `asar` 中的文件夹以 `cwd` 形式作为参数传入一些 API 中也会报错。 -### API中的额外“开箱” +### API 中的额外“开箱” -大部分`fs`API可以无需解压即从`asar`包中读取文件或者文件的信息,但是在处理一些依赖真实文件路径的底层系统方法时,Electron会将所需文件解压到临时目录下,然后将临时目录下的真实文件路径传给底层系统方法使其正常工作。 对于这类API,耗费会略多一些。 +大部分 `fs` API 可以无需解压即从 `asar` 包中读取文件或者文件的信息,但是在处理一些依赖真实文件路径的底层 +系统方法时,Electron 会将所需文件解压到临时目录下,然后将临时目录下的真实文件路径传给底层系统方法使其正 +常工作。 对于这类API,耗费会略多一些。 -以下是一些需要额外解压的APIs: +以下是一些需要额外解压的 API: * `child_process.execFile` * `child_process.execFileSync` @@ -116,26 +124,32 @@ originalFs.readFileSync('/path/to/example.asar'); * `fs.openSync` * `process.dlopen` - `require`native模块时用到 -### `fs.stat`获取的stat信息不可靠 +### `fs.stat` 获取的 stat 信息不可靠 -对`asar`包中的文件取`fs.stat`,返回的`Stats`对象不是精确值,因为这些文件不是真实存在于文件系统里。所以除了文件大小和文件类型以外,你不应该依赖`Stats`对象的值。 +对 `asar` 包中的文件取 `fs.stat`,返回的 `Stats` 对象不是精确值,因为这些文件不是真实存在于文件系 +统里。所以除了文件大小和文件类型以外,你不应该依赖 `Stats` 对象的值。 -### 执行`asar`包中的程序 +### 执行 `asar` 包中的程序 -Node中有一些可以执行程序的API,如`child_process.exec`,`child_process.spawn`和`child_process.execFile`等,但只有`execFile`可以执行`asar`包中的程序。 +Node 中有一些可以执行程序的 API,如 `child_process.exec`,`child_process.spawn` 和 `child_process.execFile` 等, +但只有 `execFile` 可以执行 `asar` 包中的程序。 -因为`exec`和`spawn`允许`command`替代`file`作为输入,而`command`是需要在shell下执行的,目前没有可靠的方法来判断`command`中是否在操作一个`asar`包中的文件,而且即便可以判断,我们依旧无法保证可以在无任何副作用的情况下替换`command`中的文件路径。 +因为 `exec` 和 `spawn` 允许 `command` 替代 `file` 作为输入,而 `command` 是需要在 shell 下执行的,目前没有 +可靠的方法来判断 `command` 中是否在操作一个 `asar` 包中的文件,而且即便可以判断,我们依旧无法保证可以在无任何 +副作用的情况下替换 `command` 中的文件路径。 ## 打包时排除文件 -如上所述,一些Node API会在调用时将文件解压到文件系统中,除了效率问题外,也有可能引起杀毒软件的注意! +如上所述,一些 Node API 会在调用时将文件解压到文件系统中,除了效率问题外,也有可能引起杀毒软件的注意! -为解决这个问题,你可以在生成`asar`包时使用`--unpack`选项来排除一些文件,使其不打包到`asar`包中,下面是如何排除一些用作共享用途的native模块的方法: +为解决这个问题,你可以在生成 `asar` 包时使用 `--unpack` 选项来排除一些文件,使其不打包到 `asar` 包中, +下面是如何排除一些用作共享用途的 native 模块的方法: ```bash $ asar pack app app.asar --unpack *.node ``` -经过上述命令后,除了生成的`app.asar`包以外,还有一个包含了排除文件的`app.asar.unpacked`文件夹,你需要将这个文件夹一起拷贝,提供给用户。 +经过上述命令后,除了生成的 `app.asar` 包以外,还有一个包含了排除文件的 `app.asar.unpacked` 文件夹, +你需要将这个文件夹一起拷贝,提供给用户。 [asar]: https://github.com/atom/asar diff --git a/docs-translations/zh-CN/tutorial/debugging-main-process.md b/docs-translations/zh-CN/tutorial/debugging-main-process.md index 48f3579394e..361d7c2d57e 100644 --- a/docs-translations/zh-CN/tutorial/debugging-main-process.md +++ b/docs-translations/zh-CN/tutorial/debugging-main-process.md @@ -1,6 +1,6 @@ # 主进程调试 -浏览器窗口的开发工具仅能调试渲染器的进程脚本(比如web 页面)。为了提供一个可以调试主进程 +浏览器窗口的开发工具仅能调试渲染器的进程脚本(比如 web 页面)。为了提供一个可以调试主进程 的方法,Electron 提供了 `--debug` 和 `--debug-brk` 开关。 ## 命令行开关 @@ -18,16 +18,33 @@ ## 使用 node-inspector 来调试 -__备注:__ Electron 使用 node v0.11.13 版本,目前对 node-inspector支持的不是特别好, +__备注:__ Electron 目前对 node-inspector 支持的不是特别好, 如果你通过 node-inspector 的 console 来检查 `process` 对象,主进程就会崩溃。 -### 1. 开始 [node-inspector][node-inspector] 服务 +### 1. 确认你已经安装了 [node-gyp 所需工具](https://github.com/nodejs/node-gyp#installation) + +### 2. 安装 [node-inspector][node-inspector] ```bash -$ node-inspector +$ npm install node-inspector ``` -### 2. 打开 Electron 的调试模式 +### 3. 安装 `node-pre-gyp` 的一个修订版 + +```bash +$ npm install git+https://git@github.com/enlight/node-pre-gyp.git#detect-electron-runtime-in-find +``` + +### 4. 为 Electron 重新编译 `node-inspector` `v8` 模块(将 target 参数修改为你的 Electron 的版本号) + +```bash +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +``` + +[How to install native modules][how-to-install-native-modules]. + +### 5. 打开 Electron 的调试模式 你也可以用调试参数来运行 Electron : @@ -41,7 +58,13 @@ $ electron --debug=5858 your/app $ electron --debug-brk=5858 your/app ``` -### 3. 加载调试器界面 +### 6. 使用 Electron 开启 [node-inspector][node-inspector] 服务 + +```bash +$ ELECTRON_RUN_AS_NODE=true path/to/electron.exe node_modules/node-inspector/bin/inspector.js +``` + +### 7. 加载调试器界面 在 Chrome 中打开 http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 diff --git a/docs-translations/zh-CN/tutorial/devtools-extension.md b/docs-translations/zh-CN/tutorial/devtools-extension.md index 9a2d9fc8832..0270eae2955 100644 --- a/docs-translations/zh-CN/tutorial/devtools-extension.md +++ b/docs-translations/zh-CN/tutorial/devtools-extension.md @@ -1,10 +1,10 @@ # DevTools扩展 -为了使调试更容易,Electron原生支持[Chrome DevTools Extension][devtools-extension]。 +为了使调试更容易,Electron 原生支持 [Chrome DevTools Extension][devtools-extension]。 -对于大多数DevTools的扩展,你可以直接下载源码,然后通过`BrowserWindow.addDevToolsExtension`API加载它们。Electron会记住已经加载了哪些扩展,所以你不需要每次创建一个新window时都调用`BrowserWindow.addDevToolsExtension`API。 +对于大多数DevTools的扩展,你可以直接下载源码,然后通过 `BrowserWindow.addDevToolsExtension` API 加载它们。Electron会记住已经加载了哪些扩展,所以你不需要每次创建一个新window时都调用 `BrowserWindow.addDevToolsExtension` API。 -** 注:React DevTools目前不能直接工作,详情留意[https://github.com/atom/electron/issues/915](https://github.com/atom/electron/issues/915) ** +** 注:React DevTools目前不能直接工作,详情留意 [https://github.com/atom/electron/issues/915](https://github.com/atom/electron/issues/915) ** 例如,要用[React DevTools Extension](https://github.com/facebook/react-devtools),你得先下载他的源码: @@ -13,33 +13,33 @@ $ cd /some-directory $ git clone --recursive https://github.com/facebook/react-devtools.git ``` -参考[`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md)来编译这个扩展源码。 +参考 [`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md) 来编译这个扩展源码。 -然后你就可以在任意页面的DevTools里加载React DevTools了,通过控制台输入如下命令加载扩展: +然后你就可以在任意页面的 DevTools 里加载 React DevTools 了,通过控制台输入如下命令加载扩展: ```javascript const BrowserWindow = require('electron').remote.BrowserWindow; BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); ``` -要卸载扩展,可以调用`BrowserWindow.removeDevToolsExtension`API(扩展名作为参数传入),该扩展在下次打开DevTools时就不会加载了: +要卸载扩展,可以调用 `BrowserWindow.removeDevToolsExtension` API (扩展名作为参数传入),该扩展在下次打开DevTools时就不会加载了: ```javascript BrowserWindow.removeDevToolsExtension('React Developer Tools'); ``` -## DevTools扩展的格式 +## DevTools 扩展的格式 -理论上,Electron可以加载所有为chrome浏览器编写的DevTools扩展,但它们必须存放在文件夹里。那些以`crx`形式发布的扩展是不能被加载的,除非你把它们解压到一个文件夹里。 +理论上,Electron 可以加载所有为 chrome 浏览器编写的 DevTools 扩展,但它们必须存放在文件夹里。那些以 `crx` 形式发布的扩展是不能被加载的,除非你把它们解压到一个文件夹里。 ## 后台运行(background pages) -Electron目前并不支持chrome扩展里的后台运行(background pages)功能,所以那些依赖此特性的DevTools扩展在Electron里可能无法正常工作。 +Electron 目前并不支持 chrome 扩展里的后台运行(background pages)功能,所以那些依赖此特性的 DevTools 扩展在 Electron 里可能无法正常工作。 ## `chrome.*` APIs -有些chrome扩展使用了`chrome.*`APIs,而且这些扩展在Electron中需要额外实现一些代码才能使用,所以并不是所有的这类扩展都已经在Electron中实现完毕了。 +有些 chrome 扩展使用了 `chrome.*`APIs,而且这些扩展在 Electron 中需要额外实现一些代码才能使用,所以并不是所有的这类扩展都已经在 Electron 中实现完毕了。 -考虑到并非所有的`chrome.*`APIs都实现完毕,如果DevTools正在使用除了`chrome.devtools.*`之外的其它APIs,这个扩展很可能无法正常工作。你可以通过报告这个扩展的异常信息,这样做方便我们对该扩展的支持。 +考虑到并非所有的 `chrome.*`APIs 都实现完毕,如果 DevTools 正在使用除了 `chrome.devtools.*` 之外的其它 APIs,这个扩展很可能无法正常工作。你可以通过报告这个扩展的异常信息,这样做方便我们对该扩展的支持。 [devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs-translations/zh-CN/tutorial/mac-app-store-submission-guide.md b/docs-translations/zh-CN/tutorial/mac-app-store-submission-guide.md new file mode 100644 index 00000000000..45cff454a96 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/mac-app-store-submission-guide.md @@ -0,0 +1,147 @@ +# Mac App Store 应用提交向导 + +自从 v0.34.0, Electron 就允许提交应用包到 Mac App Store +(MAS) . 这个向导提供的信息有 : 如何提交应用和 MAS 构建的限制. + +__注意:__ 从 v0.36.0,当应用成为沙箱之后,会有一个 bug 阻止 GPU 进程开启 , 所以在这个 bug 修复之前,建议使用 v0.35.x .更多查看 [issue #3871][issue-3871] . + +__注意:__ 提交应用到 Mac App Store 需要参加 [Apple Developer +Program][developer-program] , 这需要花钱. + +## 如何提交 + +下面步骤介绍了一个简单的提交应用到商店方法.然而,这些步骤不能保证你的应用被 Apple 接受;你仍然需要阅读 Apple 的 [Submitting Your App][submitting-your-app] 关于如何满足 Mac App Store 要求的向导. + +### 获得证书 + +为了提交应用到商店,首先需要从 Apple 获得一个证书.可以遵循 [existing guides][nwjs-guide]. + +### App 签名 + +获得证书之后,你可以使用 [Application Distribution](application-distribution.md) 打包你的应用, 然后前往提交你的应用.这个步骤基本上和其他程序一样,但是这 key 一个个的标识 Electron 的每个依赖. + +首先,你需要准备2个授权文件 . + +`child.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + +``` + +`parent.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + + +``` + +然后使用下面的脚本标识你的应用 : + +```bash +#!/bin/bash + +# Name of your app. +APP="YourApp" +# The path of you app to sign. +APP_PATH="/path/to/YouApp.app" +# The path to the location you want to put the signed package. +RESULT_PATH="~/Desktop/$APP.pkg" +# The name of certificates you requested. +APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" +INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" + +FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" + +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" +if [ -d "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" ]; then + # Signing a non-MAS build. + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Mantle.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/ReactiveCocoa.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" +fi +codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" + +productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" +``` +如果你是 OS X 下的应用沙箱使用新手,应当仔细阅读 Apple 的 [Enabling App Sandbox][enable-app-sandbox] 来有一点基础,然后向授权文件添加你的应用需要的许可 keys . + +### 上传你的应用并检查提交 + +在签名应用之后,可以使用应用 Loader 来上传到 iTunes 链接处理 , 确保在上传之前你已经 [created a record][create-record]. 然后你能 [submit your app for review][submit-for-review]. + +## MAS构建限制 + +为了让你的应用沙箱满足所有条件,在 MAS 构建的时候,下面的模块被禁用了 : + +* `crashReporter` +* `autoUpdater` + +并且下面的行为也改变了: + +* 一些机子的视频采集功能无效. +* 某些特征不可访问. +* Apps 不可识别 DNS 改变. + +也由于应用沙箱的使用方法,应用可以访问的资源被严格限制了 ; 阅读更多信息 [App Sandboxing][app-sandboxing] . + +## Electron 使用的加密算法 + +取决于你所在地方的国家和地区 , Mac App Store 或许需要记录你应用的加密算法 , 甚至要求你提交一个 U.S 加密注册(ERN) 许可的复印件. + +Electron 使用下列加密算法: + +* AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt) +* HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf) +* ECDSA - ANS X9.62–2005 +* ECDH - ANS X9.63–2001 +* HKDF - [NIST SP 800-56C](http://csrc.nist.gov/publications/nistpubs/800-56C/SP-800-56C.pdf) +* PBKDF2 - [RFC 2898](https://tools.ietf.org/html/rfc2898) +* RSA - [RFC 3447](http://www.ietf.org/rfc/rfc3447) +* SHA - [FIPS 180-4](http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf) +* Blowfish - https://www.schneier.com/cryptography/blowfish/ +* CAST - [RFC 2144](https://tools.ietf.org/html/rfc2144), [RFC 2612](https://tools.ietf.org/html/rfc2612) +* DES - [FIPS 46-3](http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf) +* DH - [RFC 2631](https://tools.ietf.org/html/rfc2631) +* DSA - [ANSI X9.30](http://webstore.ansi.org/RecordDetail.aspx?sku=ANSI+X9.30-1%3A1997) +* EC - [SEC 1](http://www.secg.org/sec1-v2.pdf) +* IDEA - "On the Design and Security of Block Ciphers" book by X. Lai +* MD2 - [RFC 1319](http://tools.ietf.org/html/rfc1319) +* MD4 - [RFC 6150](https://tools.ietf.org/html/rfc6150) +* MD5 - [RFC 1321](https://tools.ietf.org/html/rfc1321) +* MDC2 - [ISO/IEC 10118-2](https://www.openssl.org/docs/manmaster/crypto/mdc2.html) +* RC2 - [RFC 2268](https://tools.ietf.org/html/rfc2268) +* RC4 - [RFC 4345](https://tools.ietf.org/html/rfc4345) +* RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf +* RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) + +如何获取 ERN 许可, 可看这篇文章: [How to legally +submit an app to Apple’s App Store when it uses encryption (or how to obtain an +ERN)][ern-tutorial]. + +[developer-program]: https://developer.apple.com/support/compare-memberships/ +[submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html +[nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps +[enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html +[create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html +[submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html +[app-sandboxing]: https://developer.apple.com/app-sandboxing/ +[issue-3871]: https://github.com/atom/electron/issues/3871 +[ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ \ No newline at end of file diff --git a/docs-translations/zh-CN/tutorial/quick-start.md b/docs-translations/zh-CN/tutorial/quick-start.md index d54dddbfc93..c4d668417e4 100644 --- a/docs-translations/zh-CN/tutorial/quick-start.md +++ b/docs-translations/zh-CN/tutorial/quick-start.md @@ -14,7 +14,7 @@ Electron 可以让你使用纯 JavaScript 调用丰富的原生 APIs 来创造 在一般浏览器中,网页通常会在沙盒环境下运行,并且不允许访问原生资源。然而,Electron 用户拥有在网页中调用 io.js 的 APIs 的能力,可以与底层操作系统直接交互。 ## 主进程与渲染进程的区别 -主进程使用 BroswerWindow 实例创建网页。每个 BroswerWindow 实例都在自己的渲染进程里运行着一个网页。当一个 BroswerWindow 实例被销毁后,相应的渲染进程也会被终止。 +主进程使用 BrowserWindow 实例创建网页。每个 BrowserWindow 实例都在自己的渲染进程里运行着一个网页。当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。 主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的,并且只关心他们自己的网页。 @@ -127,7 +127,7 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ 在你完成了你的应用后,你可以按照[应用部署][4]指导发布一个版本,并且以已经打包好的形式运行应用。 - [1]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/ipc-renderer.md + [1]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/ipc-main-process.md [2]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/remote.md [3]: https://github.com/atom/electron/releases [4]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/tutorial/application-distribution.md diff --git a/docs-translations/zh-CN/tutorial/supported-platforms.md b/docs-translations/zh-CN/tutorial/supported-platforms.md index d4d42ec03ff..e03ccd82364 100644 --- a/docs-translations/zh-CN/tutorial/supported-platforms.md +++ b/docs-translations/zh-CN/tutorial/supported-platforms.md @@ -20,7 +20,7 @@ Ubuntu 12.04 下编译的,`arm` 版的二进制文件是在 ARM v7(硬浮点 Debian Wheezy 版本的 NEON)下完成的。 预编译二进制文件是否能够运行,取决于其中是否包括了编译平台链接的库,所以只有 Ubuntu 12.04 -可以保证正常工作,但是以下的平台也被证实可以运行 Electron的预编译版本: +可以保证正常工作,但是以下的平台也被证实可以运行 Electron 的预编译版本: * Ubuntu 12.04 及更新 * Fedora 21 diff --git a/docs-translations/zh-CN/tutorial/using-native-node-modules.md b/docs-translations/zh-CN/tutorial/using-native-node-modules.md index e03e7b8948a..0fe67d4a08a 100644 --- a/docs-translations/zh-CN/tutorial/using-native-node-modules.md +++ b/docs-translations/zh-CN/tutorial/using-native-node-modules.md @@ -1,12 +1,12 @@ # 使用原生模块 -Electron同样也支持原生模块,但由于和官方的Node相比使用了不同的V8引擎,如果你想编译原生模块,则需要手动设置Electron的headers的位置。 +Electron 同样也支持原生模块,但由于和官方的 Node 相比使用了不同的 V8 引擎,如果你想编译原生模块,则需要手动设置 Electron 的 headers 的位置。 ## 原生Node模块的兼容性 -当Node开始换新的V8引擎版本时,原生模块可能“坏”掉。为确保一切工作正常,你需要检查你想要使用的原生模块是否被Electron内置的Node支持。你可以在[这里](https://github.com/atom/electron/releases)查看Electron内置的Node版本,或者使用`process.version`(参考:[快速入门](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md))查看。 +当 Node 开始换新的V8引擎版本时,原生模块可能“坏”掉。为确保一切工作正常,你需要检查你想要使用的原生模块是否被 Electron 内置的 Node 支持。你可以在[这里](https://github.com/atom/electron/releases)查看 Electron 内置的 Node 版本,或者使用 `process.version` (参考:[快速入门](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md))查看。 -考虑到[NAN](https://github.com/nodejs/nan/)可以使你的开发更容易对多版本Node的支持,建议使用它来开发你自己的模块。你也可以使用[NAN](https://github.com/nodejs/nan/)来移植旧的模块到新的Node版本,以使它们可以在新的Electron下良好工作。 +考虑到 [NAN](https://github.com/nodejs/nan/) 可以使你的开发更容易对多版本 Node 的支持,建议使用它来开发你自己的模块。你也可以使用 [NAN](https://github.com/nodejs/nan/) 来移植旧的模块到新的 Nod e版本,以使它们可以在新的 Electron 下良好工作。 ## 如何安装原生模块 @@ -14,7 +14,7 @@ Electron同样也支持原生模块,但由于和官方的Node相比使用了 ### 最简单方式 -最简单的方式就是通过[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild)包重新编译原生模块,它帮你自动完成了下载headers、编译原生模块等步骤: +最简单的方式就是通过 [`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) 包重新编译原生模块,它帮你自动完成了下载 headers、编译原生模块等步骤: ```sh npm install --save-dev electron-rebuild @@ -26,9 +26,9 @@ npm install --save-dev electron-rebuild .\node_modules\.bin\electron-rebuild.cmd ``` -### 通过npm安装 +### 通过 npm 安装 -你当然也可以通过`npm`安装原生模块。大部分步骤和安装普通模块时一样,除了以下一些系统环境变量你需要自己操作: +你当然也可以通过 `npm` 安装原生模块。大部分步骤和安装普通模块时一样,除了以下一些系统环境变量你需要自己操作: ```bash export npm_config_disturl=https://atom.io/download/atom-shell @@ -38,16 +38,19 @@ export npm_config_runtime=electron HOME=~/.electron-gyp npm install module-name ``` -### 通过node-gyp安装 +### 通过 node-gyp 安装 -你需要告诉`node-gyp`去哪下载Electron的headers,以及下载什么版本: +你需要告诉 `node-gyp` 去哪下载 Electron 的 headers,以及下载什么版本: ```bash $ cd /path-to-module/ $ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell ``` -`HOME=~/.electron-gyp`设置了去哪找开发时的headers。 -`--target=0.29.1`设置了Electron的版本 -`--dist-url=...`设置了Electron的headers的下载地址 -`--arch=x64`设置了该模块为适配64bit操作系统而编译 +`HOME=~/.electron-gyp` 设置去哪找开发时的 headers。 + +`--target=0.29.1` 设置了 Electron 的版本 + +`--dist-url=...` 设置了 Electron 的 headers 的下载地址 + +`--arch=x64` 设置了该模块为适配64位操作系统而编译 diff --git a/docs-translations/zh-CN/tutorial/using-pepper-flash-plugin.md b/docs-translations/zh-CN/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 00000000000..ce1f210c5c9 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,48 @@ +# 使用 Pepper Flash 插件 + +Electron 现在支持 Pepper Flash 插件。要在 Electron 里面使用 Pepper Flash 插件,你需 +要手动设置 Pepper Flash 的路径和在你的应用里启用 Pepper Flash。 + +## 保留一份 Flash 插件的副本 + +在 OS X 和 Linux 上,你可以在 Chrome 浏览器的 `chrome://plugins` 页面上找到 Pepper +Flash 的插件信息。插件的路径和版本会对 Election 对其的支持有帮助。你也可以把插件 +复制到另一个路径以保留一份副本。 + +## 添加插件在 Electron 里的开关 + +你可以直接在命令行中用 `--ppapi-flash-path` 和 `ppapi-flash-version` 或者 +在 app 的准备事件前调用 `app.commandLine.appendSwitch` 这个 method。同时, +添加 `browser-window` 的插件开关。 +例如: + +```javascript +// Specify flash path. 设置 flash 路径 +// On Windows, it might be /path/to/pepflashplayer.dll +// On OS X, /path/to/PepperFlashPlayer.plugin +// On Linux, /path/to/libpepflashplayer.so +app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); + +// Specify flash version, for example, v17.0.0.169 设置版本号 +app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169'); + +app.on('ready', function() { + mainWindow = new BrowserWindow({ + 'width': 800, + 'height': 600, + 'web-preferences': { + 'plugins': true + } + }); + mainWindow.loadURL('file://' + __dirname + '/index.html'); + // Something else +}); +``` + +## 使用 `` 标签启用插件 + +在 `` 标签里添加 `plugins` 属性。 + +```html + +``` 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 c05a3190eea..0a0b29c854f 100644 --- a/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md +++ b/docs-translations/zh-CN/tutorial/using-selenium-and-webdriver.md @@ -1,18 +1,18 @@ -# 使用Selenium和WebDriver +# 使用 Selenium 和 WebDriver 引自[ChromeDriver - WebDriver for Chrome][chrome-driver]: -> WebDriver是一款开源的支持多浏览器的自动化测试工具。它提供了操作网页、用户输入、JavaScript执行等能力。ChromeDriver是一个实现了WebDriver与Chromium联接协议的独立服务。它也是由开发了Chromium和WebDriver的团队开发的。 +> WebDriver 是一款开源的支持多浏览器的自动化测试工具。它提供了操作网页、用户输入、JavaScript 执行等能力。ChromeDriver 是一个实现了 WebDriver 与 Chromium 联接协议的独立服务。它也是由开发了 Chromium 和 WebDriver 的团队开发的。 -为了能够使`chromedriver`和Electron一起正常工作,我们需要告诉它Electron在哪,并且让它相信Electron就是Chrome浏览器。 +为了能够使 `chromedriver` 和 Electron 一起正常工作,我们需要告诉它 Electron 在哪,并且让它相信 Electron 就是 Chrome 浏览器。 -## 通过WebDriverJs配置 +## 通过 WebDriverJs 配置 -[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) 是一个可以配合WebDriver做测试的node模块,我们会用它来做个演示。 +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) 是一个可以配合 WebDriver 做测试的 node 模块,我们会用它来做个演示。 -### 1. 启动ChromeDriver +### 1. 启动 ChromeDriver -首先,你要下载`chromedriver`,然后运行以下命令: +首先,你要下载 `chromedriver`,然后运行以下命令: ```bash $ ./chromedriver @@ -20,17 +20,17 @@ Starting ChromeDriver (v2.10.291558) on port 9515 Only local connections are allowed. ``` -记住`9515`这个端口号,我们后面会用到 +记住 `9515` 这个端口号,我们后面会用到 -### 2. 安装WebDriverJS +### 2. 安装 WebDriverJS ```bash $ npm install selenium-webdriver ``` -### 3. 联接到ChromeDriver +### 3. 联接到 ChromeDriver -在Electron下使用`selenium-webdriver`和其平时的用法并没有大的差异,只是你需要手动设置连接ChromeDriver,以及Electron的路径: +在 Electron 下使用 `selenium-webdriver` 和其平时的用法并没有大的差异,只是你需要手动设置连接 ChromeDriver,以及 Electron 的路径: ```javascript const webdriver = require('selenium-webdriver'); @@ -59,13 +59,13 @@ driver.wait(function() { driver.quit(); ``` -## 通过WebdriverIO配置 +## 通过 WebdriverIO 配置 -[WebdriverIO](http://webdriver.io/)也是一个配合WebDriver用来测试的node模块 +[WebdriverIO](http://webdriver.io/) 也是一个配合 WebDriver 用来测试的 node 模块 -### 1. 启动ChromeDriver +### 1. 启动 ChromeDriver -首先,下载`chromedriver`,然后运行以下命令: +首先,下载 `chromedriver`,然后运行以下命令: ```bash $ chromedriver --url-base=wd/hub --port=9515 @@ -73,15 +73,15 @@ Starting ChromeDriver (v2.10.291558) on port 9515 Only local connections are allowed. ``` -记住`9515`端口,后面会用到 +记住 `9515` 端口,后面会用到 -### 2. 安装WebdriverIO +### 2. 安装 WebdriverIO ```bash $ npm install webdriverio ``` -### 3. 连接到ChromeDriver +### 3. 连接到 ChromeDriver ```javascript const webdriverio = require('webdriverio'); @@ -112,8 +112,8 @@ client ## 工作流程 -无需重新编译Electron,只要把app的源码放到[Electron的资源目录](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md)里就可直接开始测试了。 +无需重新编译 Electron,只要把 app 的源码放到 [Electron的资源目录](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) 里就可直接开始测试了。 -当然,你也可以在运行Electron时传入参数指定你app的所在文件夹。这步可以免去你拷贝-粘贴你的app到Electron的资源目录。 +当然,你也可以在运行 Electron 时传入参数指定你 app 的所在文件夹。这步可以免去你拷贝-粘贴你的 app 到 Electron 的资源目录。 [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ diff --git a/docs-translations/zh-CN/tutorial/using-widevine-cdm-plugin.md b/docs-translations/zh-CN/tutorial/using-widevine-cdm-plugin.md new file mode 100644 index 00000000000..d5df1646c48 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/using-widevine-cdm-plugin.md @@ -0,0 +1,67 @@ +# 使用 Widevine CDM 插件 + +在 Electron ,你可以使用 Widevine CDM 插件装载 Chrome 浏览器 . + +## 获取插件 + +Electron 没有为 Widevine CDM 插件 配制许可 reasons, 为了获得它,首先需要安装官方的 chrome 浏览器,这匹配了体系架构和 Electron 构建使用的 chrome 版本 . + +__注意:__ Chrome 浏览器的主要版本必须和 Electron 使用的版本一样,否则插件不会有效,虽然 `navigator.plugins` 会显示你已经安装了它 . + +### Windows & OS X + +在 Chrome 浏览器中打开 `chrome://components/` ,找到 `WidevineCdm` 并且确定它更新到最新版本,然后你可以从 `APP_DATA/Google/Chrome/WidevineCDM/VERSION/_platform_specific/PLATFORM_ARCH/` 路径找到所有的插件二进制文件 . + +`APP_DATA` 是系统存放数据的地方,在 Windows 上它是 +`%LOCALAPPDATA%`, 在 OS X 上它是 `~/Library/Application Support`. `VERSION` 是 +Widevine CDM 插件的版本字符串, 类似 `1.4.8.866`. `PLATFORM` 是 `mac` 或 +`win`. `ARCH` 是 `x86` 或 `x64`. + +在 Windows,必要的二进制文件是 `widevinecdm.dll` and +`widevinecdmadapter.dll`, 在 OS X ,它们是 `libwidevinecdm.dylib` 和 +`widevinecdmadapter.plugin`. 你可以将它们复制到任何你喜欢的地方,但是它们必须要放在一起. + +### Linux + +在 Linux ,Chrome 浏览器将插件的二进制文件装载在一起 , 你可以在 `/opt/google/chrome` 下找到,文件名是 `libwidevinecdm.so` 和 +`libwidevinecdmadapter.so`. + +## 使用插件 + +在获得了插件文件后,你可以使用 `--widevine-cdm-path` 命令行开关来将 `widevinecdmadapter` 的路径传递给 Electron , 插件版本使用 `--widevine-cdm-version` 开关. + +__注意:__ 虽然只有 `widevinecdmadapter` 的二进制文件传递给了 Electron, `widevinecdm` 二进制文件应当放在它的旁边. + +必须在 `app` 模块的 `ready` 事件触发之前使用命令行开关,并且 page 使用的插件必须激活. + +示例代码 : + +```javascript +// You have to pass the filename of `widevinecdmadapter` here, it is +// * `widevinecdmadapter.plugin` on OS X, +// * `libwidevinecdmadapter.so` on Linux, +// * `widevinecdmadapter.dll` on Windows. +app.commandLine.appendSwitch('widevine-cdm-path', '/path/to/widevinecdmadapter.plugin'); +// The version of plugin can be got from `chrome://plugins` page in Chrome. +app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866'); + +var mainWindow = null; +app.on('ready', function() { + mainWindow = new BrowserWindow({ + webPreferences: { + // The `plugins` have to be enabled. + plugins: true + } + }) +}); +``` + +## 验证插件 + +为了验证插件是否工作,你可以使用下面的方法 : + +* 打开开发者工具查看是否 `navigator.plugins` 包含了 Widevine +CDM 插件. +* 打开 `https://shaka-player-demo.appspot.com/` 加载一个使用 +`Widevine` 的 manifest. +* 打开 http://www.dash-player.com/demo/drm-test-area/, 检查是否界面输出 `bitdash uses Widevine in your browser`, 然后播放 video. \ No newline at end of file diff --git a/docs-translations/zh-TW/README.md b/docs-translations/zh-TW/README.md index 1e69c974438..29ed827c668 100644 --- a/docs-translations/zh-TW/README.md +++ b/docs-translations/zh-TW/README.md @@ -8,7 +8,7 @@ * [主行程 Debug](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) +* [使用 Pepper Flash 外掛](tutorial/using-pepper-flash-plugin.md) ## 教學 diff --git a/docs-translations/zh-TW/tutorial/application-distribution.md b/docs-translations/zh-TW/tutorial/application-distribution.md new file mode 100644 index 00000000000..53561ecfda6 --- /dev/null +++ b/docs-translations/zh-TW/tutorial/application-distribution.md @@ -0,0 +1,108 @@ +# 應用程式部署 + +要部屬你的 Electron 應用程式,你需要把你的應用程式資料夾命名為 `app`,並放置於 Electron 的資源目錄下 (在 OS X 中位在 `Electron.app/Contents/Resources/` 而 Linux 和 Windows 的是在 `resources/`),例如: + +OS X: + +```text +electron/Electron.app/Contents/Resources/app/ +├── package.json +├── main.js +└── index.html +``` + +Windows 和 Linux: + +```text +electron/resources/app +├── package.json +├── main.js +└── index.html +``` + +然後執行 `Electron.app` (或者在 Linux 中是 `electron`, Windows 中是 `electron.exe`),然後 Electron 將會啟動你的應用程式,目錄 `electron` 會接著被部署給最後使用者。 + +## 打包你的應用程式成一個檔案 + +除了透過複製所有原始檔案來發布你的應用程式,你也可以使用 [asar](https://github.com/atom/asar) 來打包你的應用程式為一個壓縮檔,來避免暴露你的原始碼給使用者。 + +要使用 `asar` 壓縮檔來取代 `app` 資料夾,你需要重新命名該壓縮檔為 `app.asar`,然後如下所示把它放到 Electron 的資源目錄中,接著 Electron 就會試著讀取壓縮檔並從它開始執行。 + +OS X: + +```text +electron/Electron.app/Contents/Resources/ +└── app.asar +``` + +Windows 和 Linux: + +```text +electron/resources/ +└── app.asar +``` + +更多詳細的介紹請參閱 [應用程式打包](application-packaging.md). + +## 重新塑造你的下載執行檔品牌形象 + +當你完成 Electron 的應用程式打包後,在發布給使用者前,你會想想要重新塑造你的 Electron。 + +### Windows + +你可以重新命名 `electron.exe` 為任何你喜歡的名稱,然後透過像是 [rcedit](https://github.com/atom/rcedit) +的工具來編輯它的圖示(icon)和其他資訊。 + +### OS X + +你可以重新命名 `Electron.app` 為任何你喜歡的名稱,另外你也需要重新命名下列檔案中的 `CFBundleDisplayName`、`CFBundleIdentifier` 和 `CFBundleName` 欄位: + +* `Electron.app/Contents/Info.plist` +* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist` + +你也可以重新命名 helper 應用程式來避免在活動監視器中秀出 `Electron Helper` +,但請確認你有重新命名 helper 應用程式的可執行檔名稱。 + +重新命名後的應用程式檔案結構可能長得相這樣: + +``` +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` 可執行檔為任何你想要的名字。 + +## 透過重新建置 Electron 原始碼重塑品牌形象 + +你也可以透過改變產品名稱和重新建置原始碼來重塑 Electron 的品牌形象,要這麼做的話,你需要修改 `atom.gyp` 檔案並在重新建置前清理已建置的所有檔案。 + +### grunt-build-atom-shell + +手動取得 Electron 的原始碼和重新建置可能事件複雜的事,因此有一個建立好的 Grunt 任務(task)可以自動化的處理這些事情: +[grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). + +這個任務將會自動的處理編輯 `.gyp` 檔,建置原始碼,然後重新建置你的應用程式原生 Node 模組來符合新的可執行檔名稱。 + +## 打包工具 + +除了手動打包你的應用程式,你也可以選擇使用第三方打包工具來幫助你: + +* [electron-packager](https://github.com/maxogden/electron-packager) +* [electron-builder](https://github.com/loopline-systems/electron-builder) diff --git a/docs-translations/zh-TW/tutorial/application-packaging.md b/docs-translations/zh-TW/tutorial/application-packaging.md new file mode 100644 index 00000000000..438d513e2bb --- /dev/null +++ b/docs-translations/zh-TW/tutorial/application-packaging.md @@ -0,0 +1,150 @@ +# 應用程式打包 + +為了減少圍繞著 Windows 上長路徑名稱問題的 [issues](https://github.com/joyent/node/issues/6960) ,稍微地加速 `require` 和隱藏你的原始碼避免不小心被看到,你可以選擇把你的應用程式打包成一個 [asar][asar] 壓縮檔,只需要改變一點點你的原始碼就好。 +## 產生 `asar` 壓縮檔 + +一個 [asar][asar] 壓縮檔是一個簡單的類 tar 格式的檔案,它會把幾個檔案串接成一個檔案, Electron 可以不需要解壓縮整個檔案就從中讀取任意檔案。 + +把你的應用程式打包成 `asar` 壓縮檔的步驟: + +### 1. 安裝 asar 工具包 + +```bash +$ npm install -g asar +``` + +### 2. 用 `asar pack` 打包 + +```bash +$ asar pack your-app app.asar +``` + +## 使用 `asar` 壓縮檔 + +在 Electron 中有兩組 API:Node.js 提供的 Node APIs 和 Chromium 提供的 Web +APIs,兩組 API 都支援從 `asar` 壓縮檔中讀取檔案。 + +### Node API + +因為 Electron 中有一些特別的補釘,像是 `fs.readFile` 和 `require` 這樣的 Node API 會將 `asar` 壓縮檔視為許多虛擬目錄,將裡頭的檔案視為在檔案系統上的一般檔案。 + +例如,假設我們有一個 `example.asar` 壓縮檔在 `/path/to` 中: + +```bash +$ asar list /path/to/example.asar +/app.js +/file.txt +/dir/module.js +/static/index.html +/static/main.css +/static/jquery.min.js +``` + +讀取一個在 `asar` 壓縮檔中的檔案: + +```javascript +const fs = require('fs'); +fs.readFileSync('/path/to/example.asar/file.txt'); +``` + +列出所有在壓縮檔根目錄下的檔案: + +```javascript +const fs = require('fs'); +fs.readdirSync('/path/to/example.asar'); +``` + +使用一個壓縮檔中的模組: + +```javascript +require('/path/to/example.asar/dir/module.js'); +``` + +你也可以利用 `BrowserWindow` 在 `asar` 壓縮檔中呈現一個網頁: + +```javascript +const BrowserWindow = require('electron').BrowserWindow; +var win = new BrowserWindow({width: 800, height: 600}); +win.loadURL('file:///path/to/example.asar/static/index.html'); +``` + +### Web API + +在一個網頁中,壓縮檔中的檔案都可以透過 `file:` 這個協定被存取,如同 Node API,`asar` 壓縮檔都被視為目錄。 + +例如,要透過 `$.get` 取得一個檔案: + +```html + +``` + +### 把一個 `asar` 壓縮檔視為一般檔案 + +在一些像是驗證 `asar` 壓縮檔檢查碼(checksum)的例子中,我們需要以檔案的方式讀取 `asar` 壓縮檔中的內容,為了達到這個目的,你可以使用內建的 +`original-fs` 模組,它提供了沒有 `asar` 支援的原生 `fs` API: + +```javascript +var originalFs = require('original-fs'); +originalFs.readFileSync('/path/to/example.asar'); +``` + +你也可以設定 `process.noAsar` 為 `true` 來關掉在 `fs` 模組中的 `asar` 支援: + +```javascript +process.noAsar = true; +fs.readFileSync('/path/to/example.asar'); +``` + +## Node API 上的限制 + +儘管我們盡可能的努力嘗試著使 Node API 中的 `asar` 壓縮檔像目錄一樣運作,還是有一些基於 Node API 低階本質的限制存在。 + +### 壓縮檔都是唯讀的 + +所有壓縮檔都無法被修改,因此所有可以修改檔案的 Node API 都無法與 `asar ` 壓縮檔一起運作。 + +### 使用中的目錄無法被設為壓縮檔中的目錄 + +儘管 `asar` 壓縮檔被視為目錄,卻並沒有真正的目錄在檔案系統中,所以你永遠無法將使用中的目錄設定成 `asar` 壓縮檔中的目錄,把他們以 `cwd` 選項的方式傳遞,對某些 API 也會造成錯誤。 + +### 更多透過 API 拆封的方法 + +大部分 `fs` API 可以讀取一個檔案,或是不用拆封就從 `asar` 壓縮檔中取得一個檔案的資訊,但對一些需要傳遞真實檔案路徑給現行系統呼叫的 API ,Electron 將會解開需要的檔案到一個暫時的檔案,然後傳遞該暫時檔案的路徑給那些 API 以便使他們可以運作,這會增加這些 API 一點負擔。 + + +需要額外拆封的 API : + +* `child_process.execFile` +* `child_process.execFileSync` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - 在原生模組中被 `require` 使用 + +### `fs.stat` 的不真實的狀態資訊 + +`fs.stat` 回傳的 `Stats` 物件和它在 `asar` 壓縮檔中的檔案朋友都是以猜測的方式產生的,因為那些檔案不存在檔案系統,所以你不應該信任 `Stats` 物件,除了取得檔案大小和確認檔案型態之外。 + +### 執行 `asar` 壓縮檔中的二進位檔 + +有像是 `child_process.exec`、`child_process.spawn` 和 `child_process.execFile` 的 Node APIs 可以執行二進位檔,但只有 `execFile` 是 `asar` 壓縮檔中可以執行二進位檔的。 + +這是因為 `exec` 和 `spawn` 接受的輸入是 `command` 而不是 `file`,而 `command` 們都是在 shell 底下執行,我們找不到可靠的方法來決定是否一個命令使用一個在 `asar` 壓縮檔中的檔案,而儘管我們找得到,我們也無法確定是否我們可以在沒有外部影響(side effect)的情況下替換掉命令中的路徑。 + +## 加入拆封檔案到 `asar` 壓縮檔中 + +如前述,一些 Node API 再被呼叫時會拆封檔案到檔案系統,除了效能議題外,它也會導致掃毒軟體發出 false alerts。 + +要繞過這個問題,你可以透過使用 `--unpack` 選向來拆封一些建立壓縮檔的檔案,以下是一個不包含共享原生模組的函式庫的例子: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +執行這個命令以後,除了 `app.asar` 以外,還有一個帶有拆封檔案的 `app.asar.unpacked` 資料夾被產生出來,當你要發布給使用者時,你應該把它和 `app.asar` 一起複。 + +[asar]: https://github.com/atom/asar diff --git a/docs-translations/zh-TW/tutorial/debugging-main-process.md b/docs-translations/zh-TW/tutorial/debugging-main-process.md new file mode 100644 index 00000000000..f1a7e840951 --- /dev/null +++ b/docs-translations/zh-TW/tutorial/debugging-main-process.md @@ -0,0 +1,71 @@ +# 主行程 Debug + +瀏覽器視窗開發工具 DevTools 只可以用來幫渲染器的行程腳本(renderer process script,網頁畫面)除錯(debug),為了提供一個方法在主行程中除錯,Electron 有提供 `--debug` 和 `--debug-brk` 開關。 + +## 命令列開關 + +使用以下命令列切換來為 Electron 的主行程除錯: + +### `--debug=[port]` + +當這個開關被使用,Electron 將會透過 `port` 來監聽 V8 的除錯協定訊息,預設的 `port` 是 `5858`。 + +### `--debug-brk=[port]` + +同 `--debug` 但暫停在腳本的第一行。 + +## 使用 node-inspector 來除錯 + +__Note:__ Electron 近期沒有跟 node-inspector 處的很好,而且如果你透過 node-inspector's console 查看 `process` 物件,主行程將會爆掉。 + +### 1. 確保你有安裝 [node-gyp required tools][node-gyp-required-tools] + +### 2. 安裝 [node-inspector][node-inspector] + +```bash +$ npm install node-inspector +``` + +### 3. 安裝一個修補過版本的 `node-pre-gyp` + +```bash +$ npm install git+https://git@github.com/enlight/node-pre-gyp.git#detect-electron-runtime-in-find +``` + +### 4. 除心編譯 `node-inspector` `v8` 模組給 Electron (變更 target 為你的 Electron 編號) + +```bash +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +``` + +參閱 [如何安裝原生模組](how-to-install-native-modules). + +### 5. 給 Electron 啟用除錯模式 + +你可以啟動 Electron 並帶有一個除錯 flag ,例如: + +```bash +$ electron --debug=5858 your/app +``` + +或者,讓你的腳本暫停在第一行: + +```bash +$ electron --debug-brk=5858 your/app +``` + +### 6. 使用啟動 [node-inspector][node-inspector] 伺服器 + +```bash +$ ELECTRON_RUN_AS_NODE=true path/to/electron.exe node_modules/node-inspector/bin/inspector.js +``` + +### 7. 載入除錯介面 + +在你的 Chrome 瀏覽器打開 http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858,你可能需要點擊暫停假如你是透過使用 debug-brk 啟動並停在進入行(entry line)的話。 + +[node-inspector]: https://github.com/node-inspector/node-inspector +[node-gyp-required-tools]: https://github.com/nodejs/node-gyp#installation +[how-to-install-native-modules]: using-native-node-modules.md#how-to-install-native-modules + diff --git a/docs-translations/zh-TW/tutorial/devtools-extension.md b/docs-translations/zh-TW/tutorial/devtools-extension.md new file mode 100644 index 00000000000..1a419fb8375 --- /dev/null +++ b/docs-translations/zh-TW/tutorial/devtools-extension.md @@ -0,0 +1,46 @@ +# DevTools 擴充套件 + +要使除錯更簡單,Electron 有對 [Chrome DevTools(開發人員工具) Extension][devtools-extension] 基本的支援。 + +多數的 DevTools 擴充可以簡單地透過下載原始碼然後使用 `BrowserWindow.addDevToolsExtension` API 來載入它們,已載入的擴充套件會被記住,如此一來你就不用每次建立一個視窗的時候就要呼叫 API。 + +** 注意: React DevTools 無法使用,參考 [issue](https://github.com/atom/electron/issues/915) ** + +例如使用 [React DevTools Extension](https://github.com/facebook/react-devtools),首先你需要下載它的原始碼: + +```bash +$ cd /some-directory +$ git clone --recursive https://github.com/facebook/react-devtools.git +``` + +照著 [`react-devtools/shells/chrome/Readme.md`](https://github.com/facebook/react-devtools/blob/master/shells/chrome/Readme.md) 的指示來建置擴充套件。 + +接著你就可以在 Electron 中打開任何視窗來載入擴充套件了,然後在 DevTools console 執行以下的程式碼: + +```javascript +const BrowserWindow = require('electron').remote.BrowserWindow; +BrowserWindow.addDevToolsExtension('/some-directory/react-devtools/shells/chrome'); +``` + +要卸載擴充套件,你可以呼叫 `BrowserWindow.removeDevToolsExtension` +API,並帶入套件的名稱,則套件就不會在下次起打開 DevTools 的時候載入了: + +```javascript +BrowserWindow.removeDevToolsExtension('React Developer Tools'); +``` + +## DevTools 擴充套件的格式 + +理想上,所有寫給 Chrome 瀏覽器用的 DevTools 擴充套件都要能夠被 Electron 載入,但它們需要是在一個普通的目錄下,那些以 `crx` 包裝的擴充套件,Electron 沒有辦法載入它們除非你找到一個解壓縮他們到目錄的方法。 + +## Background Pages + +目前 Electron 沒有支援 Chrome 擴充套件的 background pages,所以一些會用到 background pages 的 DevTools 擴充套件可能會無法在 Electron 中運作。 + +## `chrome.*` APIs + +一些 Chrome 擴充套件可能使用到 `chrome.*` API,而儘管在 Electron 中有一些這種 API 的實作,還是沒有所有的部分都被實作到。 + +因為並非所有 `chrome.*` API 都有實作,如果一個 DevTools 擴充套件有使用到而非 `chrome.devtools.*`,這個套件非常有可能無法運作,你可以回報失敗的擴充套件到 issue tracker,那我們就可以把這些 API 加入支援。 + +[devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs-translations/zh-TW/tutorial/mac-app-store-submission-guide.md b/docs-translations/zh-TW/tutorial/mac-app-store-submission-guide.md new file mode 100644 index 00000000000..6deaa8295ef --- /dev/null +++ b/docs-translations/zh-TW/tutorial/mac-app-store-submission-guide.md @@ -0,0 +1,104 @@ +# Mac App Store 提交指引 + +自從版本 v0.34.0 開始,Electron 允許提交打包好的應用程式到 Mac App Store(MAS),這個指引提供了以下資訊:如何提交你的應用程式和 MAS 的建置限制。 + +__Note:__ 提交一個應用程式到 Mac App Store 需要註冊要付費的 [Apple Developer +Program][developer-program]. + +## 如何提交你的應用程式 + +以下步驟介紹了一個簡單的方法提交你的應用程式到 Mac App Store,然而這些步驟不保證你的應用程式會被 Apple 批准,你仍然需要閱讀 Apple 的 [Submitting Your App][submitting-your-app] 指引來達到 Mac App Store 的要求。 + +### 取得認證 + +要提交你的應用程式到 Mac App Store,你首先必須取得 Apple 的認證,你可以遵循這些網路上的 [existing guides][nwjs-guide]。 + +### 簽署你的應用程式 + +取得了 Apple 的認證以後,你可以遵照 [Application Distribution](application-distribution.md) 來打包你的應用程式,然後進行你應用程式的簽署,這個步驟基本上與其他程式相同,但重點在於你要一一為每個 Electron 的相依套件做簽署。 + +首先,你需要準備兩個管理權限用的檔案。 + +`child.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + +``` + +`parent.plist`: + +```xml + + + + + com.apple.security.app-sandbox + + + +``` + +接著遵照下面的腳本簽署你的應用程式: + +```bash +#!/bin/bash + +# Name of your app. +APP="YourApp" +# The path of you app to sign. +APP_PATH="/path/to/YouApp.app" +# The path to the location you want to put the signed package. +RESULT_PATH="~/Desktop/$APP.pkg" +# The name of certificates you requested. +APP_KEY="3rd Party Mac Developer Application: Company Name (APPIDENTITY)" +INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" + +FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" + +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Libraries/libnode.dylib" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Electron Framework" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" +codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" +``` + +如果你是第一次使用 OS X 下的應用程式沙盒(app sandboxing),你應該也要閱讀 Apple 的 [Enabling App Sandbox][enable-app-sandbox] 以舉被基本概念,然後把你的應用程式會用到的 key 的權限都加入管理權現的檔案中。 + +### 上傳你的應用程式和提交檢視 + +當你簽署好你的應用程式後,你可以使用應用程式載入器(Application Loader)把他上傳到 iTunes,處理中請保持連線順暢,在上傳以前請確保你已經 [建立一個紀錄][create-record],機著你就可以提交你的應用程式去檢視了。 + +## MAS 建置的限制 + +為了滿足應用程式沙盒的所有的要求,以下模組需要在 MAS 建置過程中被停用: + +* `crash-reporter` +* `auto-updater` + +然後以下的動作已經被變更: + +* 在某些機器上影像捕捉可能不能運作 +* 特定的存取特性可能無法運作 +* 應用程式將不管 DNS 的改變 + +此外,由於使用了應用程式沙盒,那些可以被應用程式存取的資源會被嚴格限制,你可以閱讀 [App Sandboxing][app-sandboxing] 以取得更多資訊。 + +[developer-program]: https://developer.apple.com/support/compare-memberships/ +[submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html +[nwjs-guide]: https://github.com/nwjs/nw.js/wiki/Mac-App-Store-%28MAS%29-Submission-Guideline#first-steps +[enable-app-sandbox]: https://developer.apple.com/library/ios/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html +[create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html +[submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html +[app-sandboxing]: https://developer.apple.com/app-sandboxing/ \ No newline at end of file diff --git a/docs-translations/zh-TW/tutorial/supported-platforms.md b/docs-translations/zh-TW/tutorial/supported-platforms.md new file mode 100644 index 00000000000..225bc9b2b55 --- /dev/null +++ b/docs-translations/zh-TW/tutorial/supported-platforms.md @@ -0,0 +1,26 @@ +# 支援的平台 + +Electron 已支援以下平台: + +### OS X + +OS X 系統只有 64 位元的執行檔,且 OS X 的最低版本要求為 OS X 10.8。 + +### Windows + +Windows 7 和更新的版本都有支援,早期的作業系統都不支援(且無法運作)。 + + + +`x86` 和 `amd64` (x64) 的執行檔都有提供,請注意,`ARM` 版本的 Electron 還沒有支援。 + +### Linux + +已經建置好的 `ia32`(`i686`) 和 `x64`(`amd64`) Electron 執行檔都是在 Ubuntu 12.04 的環境下編譯,`arm` 執行檔是在 hard-float ABI 和 +Debian Wheezy 的 NEON 的 ARM v7 下編譯的。 + +已建置好的執行檔是否能夠成功在 Linux 發行版執行,要看該發行版在建置的平台上是否含有 Electron 會連結的函式庫,所以只有 Ubuntu 12.04 是已確定可以運行的,而以下平台也都有驗證過可以運行已建置好的 Electron 執行檔: + +* Ubuntu 12.04 and later +* Fedora 21 +* Debian 8 diff --git a/docs-translations/zh-TW/tutorial/using-native-node-modules.md b/docs-translations/zh-TW/tutorial/using-native-node-modules.md new file mode 100644 index 00000000000..82febb6d5ab --- /dev/null +++ b/docs-translations/zh-TW/tutorial/using-native-node-modules.md @@ -0,0 +1,50 @@ +# 使用原生 node 模組 + +原生的 Node 模組 Electron 都有支援,但自從 Electron 使用了與 Node 官方不同的 V8 版本後,當你要建置原生模組的時候,你需要手動指定 Electron 標頭的位置。 + +## 原生 Node 模組的相容性 + +原生模組可能在 Node 開始使用一個新版本的 V8 時毀損,為了確保你想要用的模組能正確與 Electron 一起運行,你應該檢查是否支援 Electron 內部 Node 版本,你可以查看 [releases](https://github.com/atom/electron/releases) 或是使用 `process.version` (範例請見 [Quick Start](https://github.com/atom/electron/blob/master/docs/tutorial/quick-start.md)) 來檢查哪個 Node 版本是現在的 Electron 使用的。 + +你可以考慮給你自己的模組使用 [NAN](https://github.com/nodejs/nan/),因為它可以較輕易的支援多種版本的 Node,它對於移植舊的模組到新版本的 Node 以便與 Electron 一起運作也是很有用的。 + +## 如何安裝原生模組 + +三種安裝原生模組的方式: + +### 簡單的方法 + +最直接重新建置原生模組的方法是透過 [`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) 套件,這個套件處理了手動下載標頭和建制原生模組的步驟: + +```sh +npm install --save-dev electron-rebuild + +# 每次你執行 "npm install", 執行這句 +./node_modules/.bin/electron-rebuild + +# 在 Windows 上如果你有狀況,試試看: +.\node_modules\.bin\electron-rebuild.cmd +``` + +### 使用 npm + +你也可以使用 `npm` 安裝模組,步驟與 Node 模組的安裝相同,除了你需要設定一些環境變數: + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.33.1 +export npm_config_arch=x64 +export npm_config_runtime=electron +HOME=~/.electron-gyp npm install module-name +``` + +### 使用 node-gyp + +要把 Node 模組與 Eletron 的標頭一起建置,你需要告訴 `node-gyp` 下載標頭到哪裡,以及要使用哪個版本: + +```bash +$ cd /path-to-module/ +$ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell +``` + +`HOME=~/.electron-gyp` 改變了尋找開發標頭的地方,`--target=0.29.1` 是 Eletron 的版本, `--dist-url=...` 指定了下載標頭到哪, `--arch=x64` 指出模組要建置在 64 位元系統。 diff --git a/docs-translations/zh-TW/tutorial/using-pepper-flash-plugin.md b/docs-translations/zh-TW/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 00000000000..e27554c5168 --- /dev/null +++ b/docs-translations/zh-TW/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,45 @@ +# 使用 Pepper Flash 外掛 + +Electron 現在支援 Pepper Flash 外掛,要在 Electron 中使用 Pepper Flash 外掛,你應該手動指定 Pepper Flash 外掛的位置,並在你的應用程式中啟用它。 + +## 準備一份 Flash 外掛 + +在 OS X 和 Linux 上,Pepper Flash 外掛的細節可以透過查看 Chrome 瀏覽器中的 `chrome://plugins` 來得知,它的位置和版本對於 Electron 的 Pepper Flash 支援都有很大的幫助,你可以把它複製一份到別的位置。 + +## 加入 Electron 開關 + +你可以直接加入 `--ppapi-flash-path` 和 `ppapi-flash-version` 到 +Electron 命定列或是在應用程式的 ready 事件之前使用 `app.commandLine.appendSwitch` 方法,並且加入 `browser-window` 的 `plugins` 開關。 + +例如: + +```javascript +// 指定 Flash 路徑 +// Windows 中可能是 /path/to/pepflashplayer.dll +// OS X 中 /path/to/PepperFlashPlayer.plugin +// Linux 中 /path/to/libpepflashplayer.so +app.commandLine.appendSwitch('ppapi-flash-path', '/path/to/libpepflashplayer.so'); + +// 指定 Flash 版本, 例如 v17.0.0.169 +app.commandLine.appendSwitch('ppapi-flash-version', '17.0.0.169'); + +app.on('ready', function() { + mainWindow = new BrowserWindow({ + 'width': 800, + 'height': 600, + 'web-preferences': { + 'plugins': true + } + }); + mainWindow.loadURL('file://' + __dirname + '/index.html'); + // Something else +}); +``` + +## 在一個 `` Tag 中啟用 Flash 外掛 + +把 `plugins` 屬性加入 `` tag。 + +```html + +``` diff --git a/docs-translations/zh-TW/tutorial/using-selenium-and-webdriver.md b/docs-translations/zh-TW/tutorial/using-selenium-and-webdriver.md new file mode 100644 index 00000000000..8f90597ddae --- /dev/null +++ b/docs-translations/zh-TW/tutorial/using-selenium-and-webdriver.md @@ -0,0 +1,123 @@ +# 使用 Selenium 和 WebDriver + +根據 [ChromeDriver - WebDriver for Chrome][chrome-driver]: + +> WebDriver is an open source tool for automated testing of web apps across many +> browsers. It provides capabilities for navigating to web pages, user input, +> JavaScript execution, and more. ChromeDriver is a standalone server which +> implements WebDriver's wire protocol for Chromium. It is being developed by +> members of the Chromium and WebDriver teams. + +為了與 Electron 一起使用 `chromedriver`,你需要告訴 `chromedriver` 去哪找 Electron 並讓他知道 Electron 是 Chrome 瀏覽器。 + +## 透過 WebDriverJs 設定 + +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) 提供一個 Node 套件來透過 web driver 做測試,我們將使用它作為例子。 + +### 1. 啟動 ChromeDriver + +首先你需要下載 `chromedriver` 執行檔,然後執行它: + +```bash +$ ./chromedriver +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +記住埠號(port number) `9515`,等等會使用到它 + +### 2. 安裝 WebDriverJS + +```bash +$ npm install selenium-webdriver +``` + +### 3. 連接到 ChromeDriver + +與 Electron 一起使用 `selenium-webdriver` 的方法基本上與 upstream 相同,除了你需要手動指定如何連接 chrome driver 和去哪找 Electron 的執行檔: + +```javascript +const webdriver = require('selenium-webdriver'); + +var driver = new webdriver.Builder() + // The "9515" is the port opened by chrome driver. + .usingServer('http://localhost:9515') + .withCapabilities({ + chromeOptions: { + // Here is the path to your Electron binary. + binary: '/Path-to-Your-App.app/Contents/MacOS/Atom', + } + }) + .forBrowser('electron') + .build(); + +driver.get('http://www.google.com'); +driver.findElement(webdriver.By.name('q')).sendKeys('webdriver'); +driver.findElement(webdriver.By.name('btnG')).click(); +driver.wait(function() { + return driver.getTitle().then(function(title) { + return title === 'webdriver - Google Search'; + }); +}, 1000); + +driver.quit(); +``` + +## 透過 WebdriverIO 設定 + +[WebdriverIO](http://webdriver.io/) 提供一個 Node 套件來透過 web driver 做測試。 + +### 1. 啟動 ChromeDriver + +首先你需要下載 `chromedriver` 執行檔,然後執行它: + +```bash +$ chromedriver --url-base=wd/hub --port=9515 +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +記住埠號(port number) `9515`,等等會用到它 + +### 2. 安裝 WebdriverIO + +```bash +$ npm install webdriverio +``` + +### 3. 連接到 chrome driver + +```javascript +const webdriverio = require('webdriverio'); +var options = { + host: "localhost", // Use localhost as chrome driver server + port: 9515, // "9515" is the port opened by chrome driver. + desiredCapabilities: { + browserName: 'chrome', + chromeOptions: { + binary: '/Path-to-Your-App/electron', // Path to your Electron binary. + args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/ + } + } +}; + +var client = webdriverio.remote(options); + +client + .init() + .url('http://google.com') + .setValue('#q', 'webdriverio') + .click('#btnG') + .getTitle().then(function(title) { + console.log('Title was: ' + title); + }) + .end(); +``` + +## 運作流程 + +要在不重新建置 Electron 的情況下測試你的應用程式,只需要 [放置](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) 你的應用程式到 Electron 的資源目錄中即可。 + +或者,傳遞一個指向你應用程式資料夾的參數來透過你的 Electron 執行檔運行,這會減少複製你應用程式到 Electron 資源資料夾的需求。 + +[chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ diff --git a/docs/README.md b/docs/README.md index 9d2b36eb6c1..19ec51343d4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,6 +26,7 @@ an issue: * [DevTools Extension](tutorial/devtools-extension.md) * [Using Pepper Flash Plugin](tutorial/using-pepper-flash-plugin.md) * [Using Widevine CDM Plugin](tutorial/using-widevine-cdm-plugin.md) +* [Testing on Headless CI Systems (Travis, Jenkins)](tutorial/testing-on-headless-ci.md) ## Tutorials diff --git a/docs/api/accelerator.md b/docs/api/accelerator.md index 8858d18e856..f952d0b1ba8 100644 --- a/docs/api/accelerator.md +++ b/docs/api/accelerator.md @@ -14,6 +14,9 @@ On Linux and Windows, the `Command` key does not have any effect so use `CommandOrControl` which represents `Command` on OS X and `Control` on Linux and Windows to define some accelerators. +Use `Alt` instead of `Option`. The `Option` key only exists on OS X, whereas +the `Alt` key is available on all platforms. + The `Super` key is mapped to the `Windows` key on Windows and Linux and `Cmd` on OS X. @@ -23,6 +26,8 @@ The `Super` key is mapped to the `Windows` key on Windows and Linux and * `Control` (or `Ctrl` for short) * `CommandOrControl` (or `CmdOrCtrl` for short) * `Alt` +* `Option` +* `AltGr` * `Shift` * `Super` @@ -44,3 +49,4 @@ The `Super` key is mapped to the `Windows` key on Windows and Linux and * `Escape` (or `Esc` for short) * `VolumeUp`, `VolumeDown` and `VolumeMute` * `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` and `MediaPlayPause` +* `PrintScreen` diff --git a/docs/api/app.md b/docs/api/app.md index 13cb66e5f5a..84eeed51d6c 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -87,7 +87,7 @@ handle this case (even before the `ready` event is emitted). You should call `event.preventDefault()` if you want to handle this event. -On Windows, you have to parse `process.argv` to get the filepath. +On Windows, you have to parse `process.argv` (in the main process) to get the filepath. ### Event: 'open-url' _OS X_ @@ -156,7 +156,7 @@ certificate you should prevent the default behavior with `event.preventDefault()` and call `callback(true)`. ```javascript -session.on('certificate-error', function(event, webContents, url, error, certificate, callback) { +app.on('certificate-error', function(event, webContents, url, error, certificate, callback) { if (url == "https://github.com") { // Verification logic. event.preventDefault(); @@ -228,6 +228,10 @@ app.on('login', function(event, webContents, request, authInfo, callback) { Emitted when the gpu process crashes. +### Event: 'platform-theme-changed' _OS X_ + +Emitted when the system's Dark Mode theme is toggled. + ## Methods The `app` object has the following methods: @@ -253,6 +257,19 @@ Exits immediately with `exitCode`. All windows will be closed immediately without asking user and the `before-quit` and `will-quit` events will not be emitted. +### `app.focus()` + +On Linux, focuses on the first visible window. On OS X, makes the application +the active app. On Windows, focuses on the application's first window. + +### `app.hide()` _OS X_ + +Hides all application windows without minimizing them. + +### `app.show()` _OS X_ + +Shows application windows after they were hidden. Does not automatically focus them. + ### `app.getAppPath()` Returns the current application directory. @@ -314,10 +331,21 @@ to the npm modules spec. You should usually also specify a `productName` field, which is your application's full capitalized name, and which will be preferred over `name` by Electron. +### `app.setName(name)` + +* `name` String + +Overrides the current application's name. + ### `app.getLocale()` Returns the current application locale. +**Note:** When distributing your packaged app, you have to also ship the +`locales` folder. + +**Note:** On Windows you have to call it after the `ready` events gets emitted. + ### `app.addRecentDocument(path)` _OS X_ _Windows_ * `path` String @@ -331,6 +359,35 @@ bar, and on OS X you can visit it from dock menu. Clears the recent documents list. +### `app.setAsDefaultProtocolClient(protocol)` _OS X_ _Windows_ + +* `protocol` String - The name of your protocol, without `://`. If you want your + app to handle `electron://` links, call this method with `electron` as the + parameter. + +This method sets the current executable as the default handler for a protocol +(aka URI scheme). It allows you to integrate your app deeper into the operating +system. Once registered, all links with `your-protocol://` will be openend with +the current executable. The whole link, including protocol, will be passed to +your application as a parameter. + +**Note:** On OS X, you can only register protocols that have been added to +your app's `info.plist`, which can not be modified at runtime. You can however +change the file with a simple text editor or script during build time. +Please refer to [Apple's documentation][CFBundleURLTypes] for details. + +The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally. + +### `app.removeAsDefaultProtocolClient(protocol)` _Windows_ + +* `protocol` String - The name of your protocol, without `://`. + +This method checks if the current executable as the default handler for a protocol +(aka URI scheme). If so, it will remove the app as the default handler. + +**Note:** On OS X, removing the app will automatically remove the app as the +default protocol handler. + ### `app.setUserTasks(tasks)` _Windows_ * `tasks` Array - Array of `Task` objects @@ -390,7 +447,7 @@ quit. On OS X the system enforces single instance automatically when users try to open a second instance of your app in Finder, and the `open-file` and `open-url` events will be emitted for that. However when users start your app in command -line the system's single instance machanism will be bypassed and you have to +line the system's single instance mechanism will be bypassed and you have to use this method to ensure single instance. An example of activating the window of primary instance when a second instance @@ -405,7 +462,6 @@ var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) if (myWindow.isMinimized()) myWindow.restore(); myWindow.focus(); } - return true; }); if (shouldQuit) { @@ -454,6 +510,10 @@ if (browserOptions.transparent) { } ``` +### `app.isDarkMode()` _OS X_ + +This method returns `true` if the system is in Dark Mode, and `false` otherwise. + ### `app.commandLine.appendSwitch(switch[, value])` Append a switch (with optional `value`) to Chromium's command line. @@ -508,10 +568,17 @@ Shows the dock icon. ### `app.dock.setMenu(menu)` _OS X_ -* `menu` Menu +* `menu` [Menu](menu.md) Sets the application's [dock menu][dock-menu]. +### `app.dock.setIcon(image)` _OS X_ + +* `image` [NativeImage](native-image.md) + +Sets the `image` associated with this dock icon. + [dock-menu]:https://developer.apple.com/library/mac/documentation/Carbon/Conceptual/customizing_docktile/concepts/dockconcepts.html#//apple_ref/doc/uid/TP30000986-CH2-TPXREF103 [tasks]:http://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx +[CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115 diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index 0a5ca2d2d3d..5987f1a215e 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -2,6 +2,9 @@ This module provides an interface for the `Squirrel` auto-updater framework. +You can quickly launch a multi-platform release server for distributing your +application by forking [electron-release-server][electron-release-server]. + ## Platform notices Though `autoUpdater` provides a uniform API for different platforms, there are @@ -99,3 +102,4 @@ should only be called after `update-downloaded` has been emitted. [squirrel-windows]: https://github.com/Squirrel/Squirrel.Windows [installer]: https://github.com/atom/grunt-electron-installer [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 diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 0bb47f17087..e42b2d9bd09 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -47,13 +47,21 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `maxWidth` Integer - Window's maximum width. Default is no limit. * `maxHeight` Integer - Window's maximum height. Default is no limit. * `resizable` Boolean - Whether window is resizable. Default is `true`. - * `movable` Boolean - Whether window is movable. This is only implemented - on OS X. Default is `true`. + * `movable` Boolean - Whether window is movable. This is not implemented + on Linux. Default is `true`. + * `minimizable` Boolean - Whether window is minimizable. This is not + implemented on Linux. Default is `true`. + * `maximizable` Boolean - Whether window is maximizable. This is not + implemented on Linux. Default is `true`. + * `closable` Boolean - Whether window is closable. This is not implemented + on Linux. Default is `true`. * `alwaysOnTop` Boolean - Whether the window should always stay on top of other windows. Default is `false`. * `fullscreen` Boolean - Whether the window should show in fullscreen. When - set to `false` the fullscreen button will be hidden or disabled on OS X. - Default is `false`. + explicity set to `false` the fullscreen button will be hidden or disabled + on OS X. Default is `false`. + * `fullscreenable` Boolean - Whether the maximize/zoom button on OS X should + toggle full screen mode or maximize window. Default is `true`. * `skipTaskbar` Boolean - Whether to show the window in taskbar. Default is `false`. * `kiosk` Boolean - The kiosk mode. Default is `false`. @@ -74,8 +82,11 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `enableLargerThanScreen` Boolean - Enable the window to be resized larger than screen. Default is `false`. * `backgroundColor` String - Window's background color as Hexadecimal value, - like `#66CD00` or `#FFF`. Default is `#000` (black) for Linux and Windows, - `#FFF` for Mac (or clear if transparent). + like `#66CD00` or `#FFF` or `#80FFFFFF` (alpha is supported). Default is + `#000` (black) for Linux and Windows, `#FFF` for Mac (or clear if + transparent). + * `hasShadow` Boolean - Whether window should have a shadow. This is only + implemented on OS X. Default is `true`. * `darkTheme` Boolean - Forces using dark theme for the window, only works on some GTK+3 desktop environments. Default is `false`. * `transparent` Boolean - Makes the window [transparent](frameless-window.md). @@ -234,6 +245,14 @@ Emitted when the window loses focus. Emitted when the window gains focus. +### Event: 'show' + +Emitted when the window is shown. + +### Event: 'hide' + +Emitted when the window is hidden. + ### Event: 'maximize' Emitted when window is maximized. @@ -282,10 +301,18 @@ Emitted when the window leaves full screen state triggered by html api. ### Event: 'app-command' _Windows_ +Returns: + +* `event` Event +* `command` String + Emitted when an [App Command](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx) is invoked. These are typically related to keyboard media keys or browser commands, as well as the "Back" button built into some mice on Windows. +Commands are lowercased with underscores replaced with hyphens and the `APPCOMMAND_` prefix stripped off. +e.g. `APPCOMMAND_BROWSER_BACKWARD` is emitted as `browser-backward`. + ```js someWindow.on('app-command', function(e, cmd) { // Navigate the window back when the user hits their mouse back button @@ -295,6 +322,23 @@ someWindow.on('app-command', function(e, cmd) { }); ``` +### Event: 'scroll-touch-begin' _OS X_ + +Emitted when scroll wheel event phase has begun. + +### Event: 'scroll-touch-end' _OS X_ + +Emitted when scroll wheel event phase has ended. + +### Event: 'swipe' _OS X_ + +Returns: + +* `event` Event +* `direction` String + +Emitted on 3-finger swipe. Possible directions are `up`, `right`, `down`, `left`. + ## Methods The `BrowserWindow` object has the following methods: @@ -378,6 +422,10 @@ the [close event](#event-close). Focus on the window. +### `win.blur()` + +Remove focus on the window. + ### `win.isFocused()` Returns a boolean, whether the window is focused. @@ -438,7 +486,7 @@ Returns a boolean, whether the window is in fullscreen mode. * `aspectRatio` The aspect ratio we want to maintain for some portion of the content view. * `extraSize` Object (optional) - The extra size not to be included while -maintaining the aspect ratio. Properties: +maintaining the aspect ratio. * `width` Integer * `height` Integer @@ -458,13 +506,11 @@ height areas you have within the overall content view. ### `win.setBounds(options[, animate])` -* `options` Object, properties: - +* `options` Object * `x` Integer * `y` Integer * `width` Integer * `height` Integer - * `animate` Boolean (optional) _OS X_ Resizes and moves the window to `width`, `height`, `x`, `y`. @@ -529,6 +575,64 @@ Sets whether the window can be manually resized by user. Returns whether the window can be manually resized by user. +### `win.setMovable(movable)` _OS X_ _Windows_ + +* `movable` Boolean + +Sets whether the window can be moved by user. On Linux does nothing. + +### `win.isMovable()` _OS X_ _Windows_ + +Returns whether the window can be moved by user. On Linux always returns +`true`. + +### `win.setMinimizable(minimizable)` _OS X_ _Windows_ + +* `minimizable` Boolean + +Sets whether the window can be manually minimized by user. On Linux does +nothing. + +### `win.isMinimizable()` _OS X_ _Windows_ + +Returns whether the window can be manually minimized by user. On Linux always +returns `true`. + +### `win.setMaximizable(maximizable)` _OS X_ _Windows_ + +* `maximizable` Boolean + +Sets whether the window can be manually maximized by user. On Linux does +nothing. + +### `win.isMaximizable()` _OS X_ _Windows_ + +Returns whether the window can be manually maximized by user. On Linux always +returns `true`. + +### `win.setFullScreenable(fullscreenable)` + +* `fullscreenable` Boolean + +Sets whether the maximize/zoom window button toggles fullscreen mode or +maximizes the window. + +### `win.isFullScreenable()` + +Returns whether the maximize/zoom window button toggles fullscreen mode or +maximizes the window. + +### `win.setClosable(closable)` _OS X_ _Windows_ + +* `closable` Boolean + +Sets whether the window can be manually closed by user. On Linux does nothing. + +### `win.isClosable()` _OS X_ _Windows_ + +Returns whether the window can be manually closed by user. On Linux always +returns `true`. + ### `win.setAlwaysOnTop(flag)` * `flag` Boolean @@ -639,7 +743,7 @@ Returns the pathname of the file the window represents. * `edited` Boolean Specifies whether the window’s document has been edited, and the icon in title -bar will become grey when set to `true`. +bar will become gray when set to `true`. ### `win.isDocumentEdited()` _OS X_ @@ -651,7 +755,7 @@ Whether the window's document has been edited. ### `win.capturePage([rect, ]callback)` -* `rect` Object (optional)- The area of page to be captured, properties: +* `rect` Object (optional) - The area of page to be captured * `x` Integer * `y` Integer * `width` Integer @@ -707,33 +811,24 @@ cleared * `description` String - a description that will be provided to Accessibility screen readers -Sets a 16px overlay onto the current taskbar icon, usually used to convey some +Sets a 16 x 16 pixel overlay onto the current taskbar icon, usually used to convey some sort of application status or to passively notify the user. +### `win.setHasShadow(hasShadow)` _OS X_ + +* `hasShadow` (Boolean) + +Sets whether the window should have a shadow. On Windows and Linux does +nothing. + +### `win.hasShadow()` _OS X_ + +Returns whether the window has a shadow. On Windows and Linux always returns +`true`. ### `win.setThumbarButtons(buttons)` _Windows 7+_ -`buttons` Array of `button` Objects: - -`button` Object, properties: - -* `icon` [NativeImage](native-image.md) - The icon showing in thumbnail - toolbar. -* `tooltip` String (optional) - The text of the button's tooltip. -* `flags` Array (optional) - Control specific states and behaviors - of the button. By default, it uses `enabled`. It can include following - Strings: - * `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 taskbar button's - flyout 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. -* `click` - Function +* `buttons` Array Add a thumbnail toolbar with a specified set of buttons to the thumbnail image of a window in a taskbar button layout. Returns a `Boolean` object indicates @@ -744,6 +839,29 @@ the limited room. Once you setup the thumbnail toolbar, the toolbar cannot be removed due to the platform's limitation. But you can call the API with an empty array to clean the buttons. +The `buttons` is an array of `Button` objects: + +* `Button` 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` Array (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. + ### `win.showDefinitionForSelection()` _OS X_ Shows pop-up dictionary that searches the selected word on the page. diff --git a/docs/api/clipboard.md b/docs/api/clipboard.md index 7cb5b840bc1..7f95a1af26d 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -61,6 +61,19 @@ Returns the content in the clipboard as a [NativeImage](native-image.md). Writes `image` to the clipboard. +### `clipboard.readRtf([type])` + +* `type` String (optional) + +Returns the content in the clipboard as RTF. + +### `clipboard.writeRtf(text[, type])` + +* `text` String +* `type` String (optional) + +Writes the `text` into the clipboard in RTF. + ### `clipboard.clear([type])` * `type` String (optional) diff --git a/docs/api/content-tracing.md b/docs/api/content-tracing.md index aae5306523a..0b83c2759c6 100644 --- a/docs/api/content-tracing.md +++ b/docs/api/content-tracing.md @@ -155,7 +155,7 @@ called. * `eventName` String * `callback` Function -`callback` will will be called every time the given event occurs on any +`callback` will be called every time the given event occurs on any process. ### `contentTracing.cancelWatchEvent()` diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 1b092826356..64e5602474a 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -16,25 +16,28 @@ crashReporter.start({ }); ``` +For setting up a server to accept and process crash reports, you can use +following projects: + +* [socorro](https://github.com/mozilla/socorro) +* [mini-breakpad-server](https://github.com/atom/mini-breakpad-server) + ## Methods The `crash-reporter` module has the following methods: ### `crashReporter.start(options)` -`options` Object, properties: - -* `productName` String, default: Electron. -* `companyName` String (**required**) -* `submitURL` String, (**required**) - * URL that crash reports will be sent to as POST. -* `autoSubmit` Boolean, default: `true`. - * Send the crash report without user interaction. -* `ignoreSystemCrashHandler` Boolean, default: `false`. -* `extra` Object - * An object you can define that will be sent along with the report. - * Only string properties are sent correctly. - * Nested objects are not supported. +* `options` Object + * `companyName` String + * `submitURL` String - URL that crash reports will be sent to as POST. + * `productName` String (optional) - Default is `Electron`. + * `autoSubmit` Boolean - Send the crash report without user interaction. + Default is `true`. + * `ignoreSystemCrashHandler` Boolean - Default is `false`. + * `extra` Object - An object you can define that will be sent along with the + report. Only string properties are sent correctly, Nested objects are not + supported. You are required to call this method before using other `crashReporter` APIs. @@ -57,7 +60,7 @@ ID. ## crash-reporter Payload -The crash reporter will send the following data to the `submitURL` as `POST`: +The crash reporter will send the following data to the `submitURL` as a `multipart/form-data` `POST`: * `ver` String - The version of Electron. * `platform` String - e.g. 'win32'. @@ -69,6 +72,6 @@ The crash reporter will send the following data to the `submitURL` as `POST`: * `prod` String - Name of the underlying product. In this case Electron. * `_companyName` String - The company name in the `crashReporter` `options` object. -* `upload_file_minidump` File - The crash report as file. +* `upload_file_minidump` File - The crash report in the format of `minidump`. * All level one properties of the `extra` object in the `crashReporter`. `options` object diff --git a/docs/api/dialog.md b/docs/api/dialog.md index 84a22ef692b..9fbc20dd830 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -12,6 +12,12 @@ const dialog = require('electron').dialog; console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); ``` +The Dialog is opened from Electron's main thread. If you want to use the dialog object from a renderer process, remember to access it using the remote: + +```javascript +const dialog = require('electron').remote.dialog; +``` + **Note for OS X**: If you want to present dialogs as sheets, the only thing you have to do is provide a `BrowserWindow` reference in the `browserWindow` parameter. diff --git a/docs/api/environment-variables.md b/docs/api/environment-variables.md index 6b000aaa108..3483c19d643 100644 --- a/docs/api/environment-variables.md +++ b/docs/api/environment-variables.md @@ -25,6 +25,12 @@ Starts the process as a normal Node.js process. Prints Chrome's internal logging to console. +## `ELECTRON_LOG_ASAR_READS` + +When Electron reads from an ASAR file, log the read offset and file path to +the system `tmpdir`. The resulting file can be provided to the ASAR module +to optimize file ordering. + ## `ELECTRON_ENABLE_STACK_DUMPING` When Electron crashed, prints the stack trace to console. diff --git a/docs/api/ipc-main.md b/docs/api/ipc-main.md index 337d86be217..84fbcfd5a72 100644 --- a/docs/api/ipc-main.md +++ b/docs/api/ipc-main.md @@ -9,7 +9,7 @@ module. ## Sending Messages It is also possible to send messages from the main process to the renderer -process, see [webContents.send](web-contents.md#webcontentssendchannel-arg1-arg2-) for more information. +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`. @@ -48,37 +48,37 @@ ipcRenderer.send('asynchronous-message', 'ping'); The `ipcMain` module has the following method to listen for events: -### `ipcMain.on(channel, callback)` +### `ipcMain.on(channel, listener)` -* `channel` String - The event name. -* `callback` Function +* `channel` String +* `listener` Function -When the event occurs the `callback` is called with an `event` object and -arbitrary arguments. +Listens to `channel`, when a new message arrives `listener` would be called with +`listener(event, args...)`. -### `ipcMain.removeListener(channel, callback)` +### `ipcMain.once(channel, listener)` -* `channel` String - The event name. -* `callback` Function - The reference to the same function that you used for - `ipcMain.on(channel, callback)` +* `channel` String +* `listener` Function -Once done listening for messages, if you no longer want to activate this -callback and for whatever reason can't merely stop sending messages on the -channel, this function will remove the callback handler for the specified -channel. +Adds a one time `listener` function for the event. This `listener` is invoked +only the next time a message is sent to `channel`, after which it is removed. -### `ipcMain.removeAllListeners(channel)` +### `ipcMain.removeListener(channel, listener)` -* `channel` String - The event name. +* `channel` String +* `listener` Function -This removes *all* handlers to this ipc channel. +Removes the specified `listener` from the listener array for the specified +`channel`. -### `ipcMain.once(channel, callback)` +### `ipcMain.removeAllListeners([channel])` -Use this in place of `ipcMain.on()` to fire handlers meant to occur only once, -as in, they won't be activated after one call of `callback` +* `channel` String (optional) -## IPC Event +Removes all listeners, or those of the specified `channel`. + +## Event object The `event` object passed to the `callback` has the following methods: @@ -90,4 +90,6 @@ Set this to the value to be returned in a synchronous message. Returns the `webContents` that sent the message, you can call `event.sender.send` to reply to the asynchronous message, see -[webContents.send](web-contents.md#webcontentssendchannel-arg1-arg2-) for more information. +[webContents.send][web-contents-send] for more information. + +[web-contents-send]: web-contents.md#webcontentssendchannel-arg1-arg2- diff --git a/docs/api/ipc-renderer.md b/docs/api/ipc-renderer.md index 090fd1a9119..46a2331af43 100644 --- a/docs/api/ipc-renderer.md +++ b/docs/api/ipc-renderer.md @@ -12,35 +12,35 @@ See [ipcMain](ipc-main.md) for code examples. The `ipcRenderer` module has the following method to listen for events: -### `ipcRenderer.on(channel, callback)` +### `ipcRenderer.on(channel, listener)` -* `channel` String - The event name. -* `callback` Function +* `channel` String +* `listener` Function -When the event occurs the `callback` is called with an `event` object and -arbitrary arguments. +Listens to `channel`, when a new message arrives `listener` would be called with +`listener(event, args...)`. -### `ipcRenderer.removeListener(channel, callback)` +### `ipcRenderer.once(channel, listener)` -* `channel` String - The event name. -* `callback` Function - The reference to the same function that you used for - `ipcRenderer.on(channel, callback)` +* `channel` String +* `listener` Function -Once done listening for messages, if you no longer want to activate this -callback and for whatever reason can't merely stop sending messages on the -channel, this function will remove the callback handler for the specified -channel. +Adds a one time `listener` function for the event. This `listener` is invoked +only the next time a message is sent to `channel`, after which it is removed. -### `ipcRenderer.removeAllListeners(channel)` +### `ipcRenderer.removeListener(channel, listener)` -* `channel` String - The event name. +* `channel` String +* `listener` Function -This removes *all* handlers to this ipc channel. +Removes the specified `listener` from the listener array for the specified +`channel`. -### `ipcMain.once(channel, callback)` +### `ipcRenderer.removeAllListeners([channel])` -Use this in place of `ipcMain.on()` to fire handlers meant to occur only once, -as in, they won't be activated after one call of `callback` +* `channel` String (optional) + +Removes all listeners, or those of the specified `channel`. ## Sending Messages @@ -48,30 +48,33 @@ The `ipcRenderer` module has the following methods for sending messages: ### `ipcRenderer.send(channel[, arg1][, arg2][, ...])` -* `channel` String - The event name. +* `channel` String * `arg` (optional) -Send an event to the main process asynchronously via a `channel`, you can also -send arbitrary arguments. The main process handles it by listening for the -`channel` event with `ipcMain`. +Send a message to the main process asynchronously via `channel`, you can also +send arbitrary arguments. Arguments will be serialized in JSON internally and +hence no functions or prototype chain will be included. + +The main process handles it by listening for `channel` with `ipcMain` module. ### `ipcRenderer.sendSync(channel[, arg1][, arg2][, ...])` -* `channel` String - The event name. +* `channel` String * `arg` (optional) -Send an event to the main process synchronously via a `channel`, you can also -send arbitrary arguments. +Send a message to the main process synchronously via `channel`, you can also +send arbitrary arguments. Arguments will be serialized in JSON internally and +hence no functions or prototype chain will be included. -The main process handles it by listening for the `channel` event with -`ipcMain` and replies by setting `event.returnValue`. +The main process handles it by listening for `channel` with `ipcMain` module, +and replies by setting `event.returnValue`. -__Note:__ Sending a synchronous message will block the whole renderer process, +**Note:** Sending a synchronous message will block the whole renderer process, unless you know what you are doing you should never use it. ### `ipcRenderer.sendToHost(channel[, arg1][, arg2][, ...])` -* `channel` String - The event name. +* `channel` String * `arg` (optional) Like `ipcRenderer.send` but the event will be sent to the `` element in diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index af945e8aca4..fb12e251153 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -14,7 +14,7 @@ Create a new `MenuItem` with the following method: * `options` Object * `click` Function - Will be called with `click(menuItem, browserWindow)` when the menu item is clicked - * `role` String - Define the action of the menu item, when specified the + * `role` String - Define the action of the menu item; when specified the `click` property will be ignored * `type` String - Can be `normal`, `separator`, `submenu`, `checkbox` or `radio` @@ -22,21 +22,22 @@ Create a new `MenuItem` with the following method: * `sublabel` String * `accelerator` [Accelerator](accelerator.md) * `icon` [NativeImage](native-image.md) - * `enabled` Boolean - * `visible` Boolean - * `checked` Boolean - * `submenu` Menu - Should be specified for `submenu` type menu item, when - it's specified the `type: 'submenu'` can be omitted for the menu item. - If the value is not a `Menu` then it will be automatically converted to one - using `Menu.buildFromTemplate`. + * `enabled` Boolean - If false, the menu item will be greyed out and unclickable. + * `visible` Boolean - If false, the menu item will be entirely hidden. + * `checked` Boolean - Should only be specified for `checkbox` or `radio` type + menu items. + * `submenu` Menu - Should be specified for `submenu` type menu items. If + `submenu` is specified, the `type: 'submenu'` can be omitted. If the value + is not a `Menu` then it will be automatically converted to one using + `Menu.buildFromTemplate`. * `id` String - Unique within a single menu. If defined then it can be used as a reference to this item by the position attribute. * `position` String - This field allows fine-grained definition of the specific location within a given menu. -When creating menu items, it is recommended to specify `role` instead of -manually implementing the behavior if there is matching action, so menu can have -best native experience. +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 `role` property can have following values: @@ -59,3 +60,21 @@ On OS X `role` can also have following additional values: * `window` - The submenu is a "Window" menu * `help` - The submenu is a "Help" menu * `services` - The submenu is a "Services" menu + +## Instance Properties + +The following properties (and no others) can be updated on an existing `MenuItem`: + + * `enabled` Boolean + * `visible` Boolean + * `checked` Boolean + +Their meanings are as described above. + +A `checkbox` menu item will toggle its `checked` property on and off when +selected. You can add a `click` function to do additional work. + +A `radio` menu item will turn on its `checked` property when clicked, and +will turn off that property for all adjacent items in the same menu. Again, +you can add a `click` function for additional behavior. + diff --git a/docs/api/menu.md b/docs/api/menu.md index 38069140ad3..d7f32c787fd 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -143,7 +143,7 @@ var template = [ ]; if (process.platform == 'darwin') { - var name = require('electron').app.getName(); + var name = require('electron').remote.app.getName(); template.unshift({ label: name, submenu: [ @@ -169,7 +169,7 @@ if (process.platform == 'darwin') { }, { label: 'Hide Others', - accelerator: 'Command+Shift+H', + accelerator: 'Command+Alt+H', role: 'hideothers' }, { @@ -227,6 +227,9 @@ 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`. +See the [OS X 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 OS X's native actions. + ### `Menu.buildFromTemplate(template)` * `template` Array @@ -237,30 +240,41 @@ Generally, the `template` is just an array of `options` for constructing a You can also attach other fields to the element of the `template` and they will become properties of the constructed menu items. -### `Menu.popup([browserWindow, x, y])` +## Instance Methods -* `browserWindow` BrowserWindow (optional) -* `x` Number (optional) -* `y` Number (**required** if `x` is used) +The `menu` object has the following instance methods: -Pops up this menu as a context menu in the `browserWindow`. You -can optionally provide a `x,y` coordinate to place the menu at, otherwise it -will be placed at the current mouse cursor position. +### `menu.popup([browserWindow, x, y, positioningItem])` -### `Menu.append(menuItem)` +* `browserWindow` BrowserWindow (optional) - Default is `null`. +* `x` Number (optional) - Default is -1. +* `y` Number (**required** if `x` is used) - Default is -1. +* `positioningItem` Number (optional) _OS X_ - 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`. You can optionally +provide a `x, y` coordinate to place the menu at, otherwise it will be placed +at the current mouse cursor position. + +### `menu.append(menuItem)` * `menuItem` MenuItem Appends the `menuItem` to the menu. -### `Menu.insert(pos, menuItem)` +### `menu.insert(pos, menuItem)` * `pos` Integer * `menuItem` MenuItem Inserts the `menuItem` to the `pos` position of the menu. -### `Menu.items()` +## Instance Properties + +`menu` objects also have the following properties: + +### `menu.items` Get an array containing the menu's items. @@ -293,13 +307,18 @@ no matter what label you set. To change it you have to change your app's name by modifying your app bundle's `Info.plist` file. See [About Information Property List Files][AboutInformationPropertyListFiles] for more information. +## Setting Menu for Specific Browser Window (*Linux* *Windows*) + +The [`setMenu` method][setMenu] of browser windows can set the menu of certain +browser window. + ## Menu Item Position You can make use of `position` and `id` to control how the item will be placed when building a menu with `Menu.buildFromTemplate`. The `position` attribute of `MenuItem` has the form `[placement]=[id]`, where -placement is one of `before`, `after`, or `endof` and `id` is the unique ID of +`placement` is one of `before`, `after`, or `endof` and `id` is the unique ID of an existing item in the menu: * `before` - Inserts this item before the id referenced item. If the @@ -368,3 +387,4 @@ Menu: ``` [AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html +[setMenu]: https://github.com/atom/electron/blob/master/docs/api/browser-window.md#winsetmenumenu-linux-windows diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 515e89fba1a..e0c3ae46624 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -133,6 +133,15 @@ Returns a [Buffer][buffer] that contains the image's `JPEG` encoded data. Returns the data URL of the image. +### `image.getNativeHandle()` _OS X_ + +Returns a [Buffer][buffer] that stores C pointer to underlying native handle of +the image. On OS X, a pointer to `NSImage` instance would be returned. + +Notice that the returned pointer is a weak pointer to the underlying native +image instead of a copy, so you _must_ ensure that the associated +`nativeImage` instance is kept around. + ### `image.isEmpty()` Returns a boolean whether the image is empty. diff --git a/docs/api/power-monitor.md b/docs/api/power-monitor.md index 5313aad9c97..4465b253a75 100644 --- a/docs/api/power-monitor.md +++ b/docs/api/power-monitor.md @@ -26,10 +26,10 @@ Emitted when the system is suspending. Emitted when system is resuming. -### Event: 'on-ac' +### Event: 'on-ac' _Windows_ Emitted when the system changes to AC power. -### Event: 'on-battery' +### Event: 'on-battery' _Windows_ Emitted when system changes to battery power. diff --git a/docs/api/protocol.md b/docs/api/protocol.md index cac811265a3..e57a34ef89b 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -54,6 +54,19 @@ going to be created with `scheme`. `completion` will be called with `completion(null)` when `scheme` is successfully registered or `completion(error)` when failed. +* `request` Object + * `url` String + * `referrer` String + * `method` String + * `uploadData` Array (optional) +* `callback` Function + +The `uploadData` is an array of `data` objects: + +* `data` Object + * `bytes` Buffer - Content being sent. + * `file` String - Path of file being uploaded. + To handle the `request`, the `callback` should be called with either the file's path or an object that has a `path` property, e.g. `callback(filePath)` or `callback({path: filePath})`. @@ -61,7 +74,7 @@ path or an object that has a `path` property, e.g. `callback(filePath)` or When `callback` is called with nothing, a number, or an object that has an `error` property, the `request` will fail with the `error` number you specified. For the available error numbers you can use, please see the -[net error list](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h). +[net error list][net-error]. By default the `scheme` is treated like `http:`, which is parsed differently than protocols that follow the "generic URI syntax" like `file:`, so you @@ -74,9 +87,11 @@ treated as a standard scheme. * `handler` Function * `completion` Function (optional) -Registers a protocol of `scheme` that will send a `Buffer` as a response. The -`callback` should be called with either a `Buffer` object or an object that -has the `data`, `mimeType`, and `chart` properties. +Registers a protocol of `scheme` that will send a `Buffer` as a response. + +The usage is the same with `registerFileProtocol`, except that the `callback` +should be called with either a `Buffer` object or an object that has the `data`, +`mimeType`, and `charset` properties. Example: @@ -95,9 +110,11 @@ protocol.registerBufferProtocol('atom', function(request, callback) { * `handler` Function * `completion` Function (optional) -Registers a protocol of `scheme` that will send a `String` as a response. The -`callback` should be called with either a `String` or an object that has the -`data`, `mimeType`, and `chart` properties. +Registers a protocol of `scheme` that will send a `String` as a response. + +The usage is the same with `registerFileProtocol`, except that the `callback` +should be called with either a `String` or an object that has the `data`, +`mimeType`, and `charset` properties. ### `protocol.registerHttpProtocol(scheme, handler[, completion])` @@ -106,16 +123,25 @@ Registers a protocol of `scheme` that will send a `String` as a response. The * `completion` Function (optional) Registers a protocol of `scheme` that will send an HTTP request as a response. -The `callback` should be called with an object that has the `url`, `method`, + +The usage is the same with `registerFileProtocol`, except that the `callback` +should be called with a `redirectRequest` object that has the `url`, `method`, `referrer`, `uploadData` and `session` properties. +* `redirectRequest` Object + * `url` String + * `method` String + * `session` Object (optional) + * `uploadData` Object (optional) + By default the HTTP request will reuse the current session. If you want the request to have a different session you should set `session` to `null`. -POST request should provide an `uploadData` object. +For POST requests the `uploadData` object must be provided. + * `uploadData` object * `contentType` String - MIME type of the content. - * `data` String - Content to be sent. + * `data` String - Content to be sent. ### `protocol.unregisterProtocol(scheme[, completion])` @@ -174,3 +200,5 @@ which sends a new HTTP request as a response. * `completion` Function Remove the interceptor installed for `scheme` and restore its original handler. + +[net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h diff --git a/docs/api/session.md b/docs/api/session.md index ffed58797c4..cc90a0b542c 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -60,7 +60,8 @@ The following events are available on instances of `Session`: Emitted when Electron is about to download `item` in `webContents`. -Calling `event.preventDefault()` will cancel the download. +Calling `event.preventDefault()` will cancel the download and `item` will not be +available from next tick of the process. ```javascript session.defaultSession.on('will-download', function(event, item, webContents) { @@ -213,6 +214,7 @@ proxyURL = ["://"][":"] ``` For example: + * `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and HTTP proxy `foopy2:80` for `ftp://` URLs. * `foopy:80` - Use HTTP proxy `foopy:80` for all URLs. @@ -289,6 +291,35 @@ myWindow.webContents.session.setCertificateVerifyProc(function(hostname, cert, c }); ``` +#### `ses.setPermissionRequestHandler(handler)` + +* `handler` Function + * `webContents` Object - [WebContents](web-contents.md) requesting the permission. + * `permission` String - Enum of 'media', 'geolocation', 'notifications', 'midiSysex', 'pointerLock', 'fullscreen'. + * `callback` Function - Allow or deny the permission. + +Sets the handler which can be used to respond to permission requests for the `session`. +Calling `callback(true)` will allow the permission and `callback(false)` will reject it. + +```javascript +session.fromPartition(partition).setPermissionRequestHandler(function(webContents, permission, callback) { + if (webContents.getURL() === host) { + if (permission == "notifications") { + callback(false); // denied. + return; + } + } + + callback(true); +}); +``` + +#### `ses.clearHostResolverCache([callback])` + +* `callback` Function (optional) - Called when operation is done. + +Clears the host resolver cache. + #### `ses.webRequest` The `webRequest` API set allows to intercept and modify contents of a request at @@ -332,6 +363,14 @@ is about to occur. * `method` String * `resourceType` String * `timestamp` Double + * `uploadData` Array (optional) +* `callback` Function + +The `uploadData` is an array of `data` objects: + +* `data` Object + * `bytes` Buffer - Content being sent. + * `file` String - Path of file being uploaded. The `callback` has to be called with an `response` object: @@ -356,6 +395,7 @@ TCP connection is made to the server, but before any http data is sent. * `resourceType` String * `timestamp` Double * `requestHeaders` Object +* `callback` Function The `callback` has to be called with an `response` object: @@ -398,6 +438,7 @@ response headers of a request have been received. * `statusLine` String * `statusCode` Integer * `responseHeaders` Object +* `callback` Function The `callback` has to be called with an `response` object: diff --git a/docs/api/shell.md b/docs/api/shell.md index e6678a95536..823dc481bb0 100644 --- a/docs/api/shell.md +++ b/docs/api/shell.md @@ -26,12 +26,16 @@ Show the given file in a file manager. If possible, select the file. Open the given file in the desktop's default manner. -### `shell.openExternal(url)` +### `shell.openExternal(url[, options])` * `url` String +* `options` Object (optional) _OS X_ + * `activate` Boolean - `true` to bring the opened application to the + foreground. The default is `true`. Open the given external protocol URL in the desktop's default manner. (For -example, mailto: URLs in the user's default mail agent.) +example, mailto: URLs in the user's default mail agent.) Returns true if an +application was available to open the URL, false otherwise. ### `shell.moveItemToTrash(fullPath)` diff --git a/docs/api/synopsis.md b/docs/api/synopsis.md index d14d52a39b3..f2009e75520 100644 --- a/docs/api/synopsis.md +++ b/docs/api/synopsis.md @@ -49,7 +49,7 @@ To run your app, read [Run your app](../tutorial/quick-start.md#run-your-app). ## Destructuring assignment If you are using CoffeeScript or Babel, you can also use -[destructuring assignment][desctructuring-assignment] to make it easier to use +[destructuring assignment][destructuring-assignment] to make it easier to use built-in modules: ```javascript @@ -79,5 +79,5 @@ require('electron').hideInternalModules() ``` [gui]: https://en.wikipedia.org/wiki/Graphical_user_interface -[desctructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +[destructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment [issue-387]: https://github.com/atom/electron/issues/387 diff --git a/docs/api/tray.md b/docs/api/tray.md index 08a43638be1..aa972c57cd9 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -32,6 +32,13 @@ __Platform limitations:__ install `libappindicator1` to make the tray icon work. * App indicator will only be shown when it has a context menu. * When app indicator is used on Linux, the `click` event is ignored. +* On Linux in order for changes made to individual `MenuItem`s to take effect, + you have to call `setContextMenu` again. For example: + +```javascript +contextMenu.items[2].checked = false; +appIcon.setContextMenu(contextMenu); +``` If you want to keep exact same behaviors on all platforms, you should not rely on the `click` event and always attach a context menu to the tray icon. @@ -68,7 +75,7 @@ labeled as such. Emitted when the tray icon is clicked. -__Note:__ The `bounds` payload is only implemented on OS X and Windows. +**Note:** The `bounds` payload is only implemented on OS X and Windows. ### Event: 'right-click' _OS X_ _Windows_ diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 48658a29459..5295b79ceb3 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -259,8 +259,9 @@ Returns: * `result` Object * `requestId` Integer * `finalUpdate` Boolean - Indicates if more responses are to follow. - * `matches` Integer (Optional) - Number of Matches. - * `selectionArea` Object (Optional) - Coordinates of first match region. + * `activeMatchOrdinal` Integer (optional) - Position of the active match. + * `matches` Integer (optional) - Number of Matches. + * `selectionArea` Object (optional) - Coordinates of first match region. Emitted when a result is available for [`webContents.findInPage`](web-contents.md#webcontentsfindinpage) request. @@ -281,6 +282,28 @@ Emitted when a page's theme color changes. This is usually due to encountering a ``` +### Event: 'cursor-changed' + +Returns: + +* `event` Event +* `type` String +* `image` NativeImage (optional) +* `scale` Float (optional) + +Emitted when the cursor's type changes. The `type` parameter can be `default`, +`crosshair`, `pointer`, `text`, `wait`, `help`, `e-resize`, `n-resize`, +`ne-resize`, `nw-resize`, `s-resize`, `se-resize`, `sw-resize`, `w-resize`, +`ns-resize`, `ew-resize`, `nesw-resize`, `nwse-resize`, `col-resize`, +`row-resize`, `m-panning`, `e-panning`, `n-panning`, `ne-panning`, `nw-panning`, +`s-panning`, `se-panning`, `sw-panning`, `w-panning`, `move`, `vertical-text`, +`cell`, `context-menu`, `alias`, `progress`, `nodrop`, `copy`, `none`, +`not-allowed`, `zoom-in`, `zoom-out`, `grab`, `grabbing`, `custom`. + +If the `type` parameter is `custom`, the `image` parameter will hold the custom +cursor image in a `NativeImage`, and the `scale` will hold scaling information +for the image. + ## Instance Methods The `webContents` object has the following instance methods: @@ -288,7 +311,7 @@ The `webContents` object has the following instance methods: ### `webContents.loadURL(url[, options])` * `url` URL -* `options` Object (optional), properties: +* `options` Object (optional) * `httpReferrer` String - A HTTP Referrer url. * `userAgent` String - A user agent originating the request. * `extraHeaders` String - Extra headers separated by "\n" @@ -403,10 +426,12 @@ Returns a `String` representing the user agent for this web page. Injects CSS into the current web page. -### `webContents.executeJavaScript(code[, userGesture])` +### `webContents.executeJavaScript(code[, userGesture, callback])` * `code` String * `userGesture` Boolean (optional) +* `callback` Function (optional) - Called after script has been executed. + * `result` Evaluates `code` in page. @@ -481,7 +506,7 @@ Inserts `text` to the focused element. ### `webContents.findInPage(text[, options])` * `text` String - Content to be searched, must not be empty. -* `options` Object (Optional) +* `options` Object (optional) * `forward` Boolean - Whether to search forward or backward, defaults to `true`. * `findNext` Boolean - Whether the operation is first request or a follow up, defaults to `false`. @@ -534,11 +559,10 @@ when the JS promise is rejected. ### `webContents.print([options])` -`options` Object (optional), properties: - -* `silent` Boolean - Don't ask user for print settings, defaults to `false` -* `printBackground` Boolean - Also prints the background color and image of - the web page, defaults to `false`. +* `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 window's web page. When `silent` is set to `false`, Electron will pick up system's default printer and default settings for printing. @@ -552,31 +576,22 @@ size. ### `webContents.printToPDF(options, callback)` -`options` Object, properties: - -* `marginsType` Integer - Specify the type of margins to use - * 0 - default - * 1 - none - * 2 - minimum -* `pageSize` String - Specify page size of the generated PDF. - * `A5` - * `A4` - * `A3` - * `Legal` - * `Letter` - * `Tabloid` -* `printBackground` Boolean - Whether to print CSS backgrounds. -* `printSelectionOnly` Boolean - Whether to print selection only. -* `landscape` Boolean - `true` for landscape, `false` for portrait. - -`callback` Function - `function(error, data) {}` - -* `error` Error -* `data` Buffer - PDF file content. +* `options` Object + * `marginsType` Integer - Specifies the type of margins to use. Uses 0 for + default margin, 1 for no margin, and 2 for minimum margin. + * `pageSize` String - Specify page size of the generated PDF. Can be `A3`, + `A4`, `A5`, `Legal`, `Letter` and `Tabloid`. + * `printBackground` Boolean - Whether to print CSS backgrounds. + * `printSelectionOnly` Boolean - Whether to print selection only. + * `landscape` Boolean - `true` for landscape, `false` for portrait. +* `callback` Function Prints window's web page as PDF with Chromium's preview printing custom settings. +The `callback` will be called with `callback(error, data)` on completion. The +`data` is a `Buffer` that contains the generated PDF data. + By default, an empty `options` will be regarded as: ```javascript @@ -629,7 +644,7 @@ Removes the specified path from DevTools workspace. ### `webContents.openDevTools([options])` -* `options` Object (optional). Properties: +* `options` Object (optional) * `detach` Boolean - opens DevTools in a new window Opens the devtools. @@ -650,10 +665,6 @@ Returns whether the devtools view is focused . Toggles the developer tools. -### `webContents.isDevToolsFocused()` - -Returns whether the developer tools is focused. - ### `webContents.inspectElement(x, y)` * `x` Integer @@ -671,8 +682,11 @@ Opens the developer tools for the service worker context. * `arg` (optional) Send an asynchronous message to renderer process via `channel`, you can also -send arbitrary arguments. The renderer process can handle the message by -listening to the `channel` event with the `ipcRenderer` module. +send arbitrary arguments. Arguments will be serialized in JSON internally and +hence no functions or prototype chain will be included. + +The renderer process can handle the message by listening to `channel` with the +`ipcRenderer` module. An example of sending messages from the main process to the renderer process: @@ -751,11 +765,9 @@ Sends an input `event` to the page. For keyboard events, the `event` object also have following properties: -* `keyCode` Char or String (**required**) - The character that will be sent - as the keyboard event. Can be a single UTF-8 character, or the name of the - key that generates the event. Accepted key names are `enter`, `backspace`, - `delete`, `tab`, `escape`, `control`, `alt`, `shift`, `end`, `home`, `insert`, - `left`, `up`, `right`, `down`, `pageUp`, `pageDown`, `printScreen` +* `keyCode` String (**required**) - The character that will be sent + as the keyboard event. Should only use the valid key codes in + [Accelerator](accelerator.md). For mouse events, the `event` object also have following properties: @@ -827,9 +839,83 @@ win.webContents.on('did-finish-load', function() { Returns the [session](session.md) object used by this webContents. +### `webContents.hostWebContents` + +Returns the `WebContents` that might own this `WebContents`. + ### `webContents.devToolsWebContents` Get the `WebContents` of DevTools for this `WebContents`. **Note:** Users should never store this object because it may become `null` when the DevTools has been closed. + +### `webContents.debugger` + +Debugger API serves as an alternate transport for [remote debugging protocol][rdp]. + +```javascript +try { + win.webContents.debugger.attach("1.1"); +} catch(err) { + console.log("Debugger attach failed : ", err); +}; + +win.webContents.debugger.on('detach', function(event, reason) { + console.log("Debugger detached due to : ", reason); +}); + +win.webContents.debugger.on('message', function(event, method, params) { + if (method == "Network.requestWillBeSent") { + if (params.request.url == "https://www.github.com") + win.webContents.debugger.detach(); + } +}) + +win.webContents.debugger.sendCommand("Network.enable"); +``` + +#### `webContents.debugger.attach([protocolVersion])` + +* `protocolVersion` String (optional) - Requested debugging protocol version. + +Attaches the debugger to the `webContents`. + +#### `webContents.debugger.isAttached()` + +Returns a boolean indicating whether a debugger is attached to the `webContents`. + +#### `webContents.debugger.detach()` + +Detaches the debugger from the `webContents`. + +#### `webContents.debugger.sendCommand(method[, commandParams, callback])` + +* `method` String - Method name, should be one of the methods defined by the + remote debugging protocol. +* `commandParams` Object (optional) - JSON object with request parameters. +* `callback` Function (optional) - Response + * `error` Object - Error message indicating the failure of the command. + * `result` Object - Response defined by the 'returns' attribute of + the command description in the remote debugging protocol. + +Send given command to the debugging target. + +#### Event: 'detach' + +* `event` Event +* `reason` String - Reason for detaching debugger. + +Emitted when debugging session is terminated. This happens either when +`webContents` is closed or devtools is invoked for the attached `webContents`. + +#### Event: 'message' + +* `event` Event +* `method` String - Method name. +* `params` Object - Event parameters defined by the 'parameters' + attribute in the remote debugging protocol. + +Emitted whenever debugging target issues instrumentation event. + +[rdp]: https://developer.chrome.com/devtools/docs/debugger-protocol diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 9cb8f49af35..47e23dc9943 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -84,6 +84,9 @@ than the minimum values or greater than the maximum. If "on", the guest page in `webview` will have node integration and can use node APIs like `require` and `process` to access low level system resources. +**Note:** Node integration will always be disabled in the `webview` if it is +disabled on the parent window. + ### `plugins` ```html @@ -184,7 +187,7 @@ webview.addEventListener("dom-ready", function() { ### `.loadURL(url[, options])` * `url` URL -* `options` Object (optional), properties: +* `options` Object (optional) * `httpReferrer` String - A HTTP Referrer url. * `userAgent` String - A user agent originating the request. * `extraHeaders` String - Extra headers separated by "\n" @@ -279,10 +282,12 @@ Returns a `String` representing the user agent for guest page. Injects CSS into the guest page. -### `.executeJavaScript(code, userGesture)` +### `.executeJavaScript(code, userGesture, callback)` * `code` String * `userGesture` Boolean - Default `false`. +* `callback` Function (optional) - Called after script has been executed. + * `result` Evaluates `code` in page. If `userGesture` is set, it will create the user gesture context in the page. HTML APIs like `requestFullScreen`, which require @@ -361,7 +366,7 @@ Executes editing command `selectAll` in page. Executes editing command `unselect` in page. -### `.replace(text)` * `text` String @@ -382,7 +387,7 @@ Inserts `text` to the focused element. ### `.findInPage(text[, options])` * `text` String - Content to be searched, must not be empty. -* `options` Object (Optional) +* `options` Object (optional) * `forward` Boolean - Whether to search forward or backward, defaults to `true`. * `findNext` Boolean - Whether the operation is first request or a follow up, defaults to `false`. @@ -438,6 +443,10 @@ Sends an input `event` to the page. See [webContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent) for detailed description of `event` object. +### `.getWebContents()` + +Returns the [WebContents](web-contents.md) associated with this `webview`. + ## DOM events The following DOM events are available to the `webview` tag: @@ -522,7 +531,7 @@ Returns: * `explicitSet` Boolean Fired when page title is set during navigation. `explicitSet` is false when -title is synthesised from file url. +title is synthesized from file url. ### Event: 'page-favicon-updated' @@ -567,8 +576,9 @@ Returns: * `result` Object * `requestId` Integer * `finalUpdate` Boolean - Indicates if more responses are to follow. - * `matches` Integer (Optional) - Number of Matches. - * `selectionArea` Object (Optional) - Coordinates of first match region. + * `activeMatchOrdinal` Integer (optional) - Position of the active match. + * `matches` Integer (optional) - Number of Matches. + * `selectionArea` Object (optional) - Coordinates of first match region. Fired when a result is available for [`webview.findInPage`](web-view-tag.md#webviewtagfindinpage) request. @@ -599,7 +609,10 @@ The following example code opens the new url in system's default browser. ```javascript webview.addEventListener('new-window', function(e) { - require('electron').shell.openExternal(e.url); + var protocol = require('url').parse(e.url).protocol; + if (protocol === 'http:' || protocol === 'https:') { + require('electron').shell.openExternal(e.url); + } }); ``` diff --git a/docs/api/window-open.md b/docs/api/window-open.md index 5d298e61e75..46e74327741 100644 --- a/docs/api/window-open.md +++ b/docs/api/window-open.md @@ -23,6 +23,9 @@ Creates a new window and returns an instance of `BrowserWindowProxy` class. The `features` string follows the format of standard browser, but each feature has to be a field of `BrowserWindow`'s options. +**Note:** Node integration will always be disabled in the opened `window` if it +is disabled on the parent window. + ### `window.opener.postMessage(message, targetOrigin)` * `message` String diff --git a/docs/development/build-instructions-linux.md b/docs/development/build-instructions-linux.md index 9ddeed9809b..df0943050f7 100644 --- a/docs/development/build-instructions-linux.md +++ b/docs/development/build-instructions-linux.md @@ -19,7 +19,7 @@ On Ubuntu, install the following libraries: $ sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ libnotify-dev libgnome-keyring-dev libgconf2-dev \ libasound2-dev libcap-dev libcups2-dev libxtst-dev \ - libxss1 libnss3-dev gcc-multilib g++-multilib + libxss1 libnss3-dev gcc-multilib g++-multilib curl ``` On Fedora, install the following libraries: diff --git a/docs/development/build-instructions-osx.md b/docs/development/build-instructions-osx.md index ac51bf0dc22..598aa01c440 100644 --- a/docs/development/build-instructions-osx.md +++ b/docs/development/build-instructions-osx.md @@ -22,7 +22,7 @@ $ git clone https://github.com/atom/electron.git ## Bootstrapping The bootstrap script will download all necessary build dependencies and create -the build project files. Notice that we're using `ninja` to build Electron so +the build project files. Notice that we're using [ninja](https://ninja-build.org/) to build Electron so there is no Xcode project generated. ```bash diff --git a/docs/development/build-system-overview.md b/docs/development/build-system-overview.md index 04067ce8c79..61b88e14078 100644 --- a/docs/development/build-system-overview.md +++ b/docs/development/build-system-overview.md @@ -1,6 +1,6 @@ # Build System Overview -Electron uses `gyp` for project generation and `ninja` for building. Project +Electron uses [gyp](https://gyp.gsrc.io/) for project generation and [ninja](https://ninja-build.org/) for building. Project configurations can be found in the `.gyp` and `.gypi` files. ## Gyp Files diff --git a/docs/development/coding-style.md b/docs/development/coding-style.md index a86ee32417d..422a4226df6 100644 --- a/docs/development/coding-style.md +++ b/docs/development/coding-style.md @@ -2,6 +2,9 @@ These are the style guidelines for coding in Electron. +You can run `npm run lint` to show any style issues detected by `cpplint` and +`eslint`. + ## C++ and Python For C++ and Python, we follow Chromium's [Coding @@ -17,17 +20,24 @@ document. The document mentions some special types, scoped types (that automatically release their memory when going out of scope), logging mechanisms etc. -## CoffeeScript - -For CoffeeScript, we follow GitHub's [Style -Guide](https://github.com/styleguide/javascript) and the following rules: +## JavaScript +* Write [standard](http://npm.im/standard) JavaScript style. * Files should **NOT** end with new line, because we want to match Google's styles. * File names should be concatenated with `-` instead of `_`, e.g. - `file-name.coffee` rather than `file_name.coffee`, because in + `file-name.js` rather than `file_name.js`, because in [github/atom](https://github.com/github/atom) module names are usually in - the `module-name` form. This rule only applies to `.coffee` files. + the `module-name` form. This rule only applies to `.js` files. +* Use newer ES6/ES2015 syntax where appropriate + * [`const`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) + for requires and other constants + * [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) + for defining variables + * [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) + instead of `function () { }` + * [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) + instead of string concatenation using `+` ## API Names diff --git a/docs/development/setting-up-symbol-server.md b/docs/development/setting-up-symbol-server.md index 1dc31f7056d..073c752cbf4 100644 --- a/docs/development/setting-up-symbol-server.md +++ b/docs/development/setting-up-symbol-server.md @@ -25,7 +25,7 @@ appropriate cache directory on your machine. The Windbg symbol path is configured with a string value delimited with asterisk characters. To use only the Electron symbol server, add the following entry to -your symbol path (__Note:__ you can replace `c:\code\symbols` with any writable +your symbol path (**Note:** you can replace `c:\code\symbols` with any writable directory on your computer, if you'd prefer a different location for downloaded symbols): diff --git a/docs/development/source-code-directory-structure.md b/docs/development/source-code-directory-structure.md index cbca7108526..998280a3705 100644 --- a/docs/development/source-code-directory-structure.md +++ b/docs/development/source-code-directory-structure.md @@ -11,35 +11,36 @@ to understand the source code better. ``` Electron -├──atom - Source code of Electron. -| ├── app - System entry code. -| ├── browser - The frontend including the main window, UI, and all of the -| | main process things. This talks to the renderer to manage web pages. -| |   ├── lib - Javascript part of the main process initialization code. -| | ├── ui - Implementation of UI stuff for different platforms. -| | | ├── cocoa - Cocoa specific source code. -| | | ├── gtk - GTK+ specific source code. -| | | └── win - Windows GUI specific source code. -| | ├── default_app - The default page to show when Electron is started -| | | without providing an app. -| | ├── api - The implementation of the main process APIs. -| | | └── lib - Javascript part of the API implementation. -| | ├── net - Network related code. -| | ├── mac - Mac specific Objective-C source code. -| | └── resources - Icons, platform-dependent files, etc. -| ├── renderer - Code that runs in renderer process. -| | ├── lib - Javascript part of renderer initialization code. -| | └── api - The implementation of renderer process APIs. -| | └── lib - Javascript part of the API implementation. -| └── common - Code that used by both the main and renderer processes, -| including some utility functions and code to integrate node's message -| loop into Chromium's message loop. -| ├── lib - Common Javascript initialization code. -| └── api - The implementation of common APIs, and foundations of -| Electron's built-in modules. -| └── lib - Javascript part of the API implementation. +├── atom - C++ source code. +| ├── app - System entry code. +| ├── browser - The frontend including the main window, UI, and all of the +| | main process things. This talks to the renderer to manage web pages. +| | ├── ui - Implementation of UI stuff for different platforms. +| | | ├── cocoa - Cocoa specific source code. +| | | ├── gtk - GTK+ specific source code. +| | | └── win - Windows GUI specific source code. +| | ├── api - The implementation of the main process APIs. +| | ├── net - Network related code. +| | ├── mac - Mac specific Objective-C source code. +| | └── resources - Icons, platform-dependent files, etc. +| ├── renderer - Code that runs in renderer process. +| | └── api - The implementation of renderer process APIs. +| └── common - Code that used by both the main and renderer processes, +| including some utility functions and code to integrate node's message +| loop into Chromium's message loop. +| └── api - The implementation of common APIs, and foundations of +| Electron's built-in modules. ├── chromium_src - Source code that copied from Chromium. +├── default_app - The default page to show when Electron is started without +| providing an app. ├── docs - Documentations. +├── lib - JavaScript source code. +| ├── browser - Javascript main process initialization code. +| | └── api - Javascript API implementation. +| ├── common - JavaScript used by both the main and renderer processes +| | └── api - Javascript API implementation. +| └── renderer - Javascript renderer process initialization code. +| └── api - Javascript API implementation. ├── spec - Automatic tests. ├── atom.gyp - Building rules of Electron. └── common.gypi - Compiler specific settings and building rules for other @@ -58,6 +59,6 @@ Electron * **node_modules** - Third party node modules used for building. * **out** - Temporary output directory of `ninja`. * **dist** - Temporary directory created by `script/create-dist.py` script - when creating an distribution. + when creating a distribution. * **external_binaries** - Downloaded binaries of third-party frameworks which do not support building with `gyp`. diff --git a/docs/faq/electron-faq.md b/docs/faq/electron-faq.md index e8f5b191d7a..1782d387310 100644 --- a/docs/faq/electron-faq.md +++ b/docs/faq/electron-faq.md @@ -5,7 +5,7 @@ The Chrome version of Electron is usually bumped within one or two weeks after a new stable Chrome version gets released. -Also we only use stable channel of Chrome, if an important fix is in beta or dev +Also we only use stable channel of Chrome. If an important fix is in beta or dev channel, we will back-port it. ## When will Electron upgrade to latest Node.js? @@ -18,13 +18,40 @@ New features of Node.js are usually brought by V8 upgrades, since Electron is using the V8 shipped by Chrome browser, the shiny new JavaScript feature of a new Node.js version is usually already in Electron. +## How to share data between web pages? + +To share data between web pages (the renderer processes) the simplest way is to +use HTML5 APIs which are already available in browsers. Good candidates are +[Storage API][storage], [`localStorage`][local-storage], +[`sessionStorage`][session-storage], and [IndexedDB][indexed-db]. + +Or you can use the IPC system, which is specific to Electron, to store objects +in the main process as a global variable, and then to access them from the +renderers through the `remote` module: + +```javascript +// In the main process. +global.sharedObject = { + someProperty: 'default value' +}; +``` + +```javascript +// In page 1. +require('remote').getGlobal('sharedObject').someProperty = 'new value'; +``` + +```javascript +// In page 2. +console.log(require('remote').getGlobal('sharedObject').someProperty); +``` + ## My app's window/tray disappeared after a few minutes. This happens when the variable which is used to store the window/tray gets garbage collected. -It is recommended to have a reading of following articles you encountered this -problem: +If you encounter this problem, the following articles may prove helpful: * [Memory Management][memory-management] * [Variable Scope][variable-scope] @@ -50,8 +77,8 @@ app.on('ready', function() { ## I can not use jQuery/RequireJS/Meteor/AngularJS in Electron. Due to the Node.js integration of Electron, there are some extra symbols -inserted into DOM, like `module`, `exports`, `require`. This causes troubles for -some libraries since they want to insert the symbols with same names. +inserted into the DOM like `module`, `exports`, `require`. This causes problems for +some libraries since they want to insert the symbols with the same names. To solve this, you can turn off node integration in Electron: @@ -79,5 +106,61 @@ delete window.module; ``` +## `require('electron').xxx` is undefined. + +When using Electron's built-in module you might encounter an error like this: + +``` +> require('electron').webFrame.setZoomFactor(1.0); +Uncaught TypeError: Cannot read property 'setZoomLevel' of undefined +``` + +This is because you have the [npm `electron` module][electron-module] installed +either locally or globally, which overrides Electron's built-in module. + +To verify whether you are using the correct built-in module, you can print the +path of the `electron` module: + +```javascript +console.log(require.resolve('electron')); +``` + +and then check if it is in the following form: + +``` +"/path/to/Electron.app/Contents/Resources/atom.asar/renderer/api/lib/exports/electron.js" +``` + +If it is something like `node_modules/electron/index.js`, then you have to +either remove the npm `electron` module, or rename it. + +```bash +npm uninstall electron +npm uninstall -g electron +``` + +However if your 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. + +## Why is my app's background transparent? + +Since Electron `0.37.3`, the default user-agent background color is `transparent`. +Simply specify a background color in HTML: + +```html + +``` + [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/tutorial/application-distribution.md b/docs/tutorial/application-distribution.md index e8b944e7f6b..313c924e881 100644 --- a/docs/tutorial/application-distribution.md +++ b/docs/tutorial/application-distribution.md @@ -61,8 +61,7 @@ before distributing it to users. ### Windows You can rename `electron.exe` to any name you like, and edit its icon and other -information with tools like [rcedit](https://github.com/atom/rcedit) or -[ResEdit](http://www.resedit.net). +information with tools like [rcedit](https://github.com/atom/rcedit). ### OS X diff --git a/docs/tutorial/debugging-main-process.md b/docs/tutorial/debugging-main-process.md index 6fbb858bc28..ee7fc4c5fa4 100644 --- a/docs/tutorial/debugging-main-process.md +++ b/docs/tutorial/debugging-main-process.md @@ -19,7 +19,7 @@ Like `--debug` but pauses the script on the first line. ## Use node-inspector for Debugging -__Note:__ Electron doesn't currently work very well +**Note:** Electron doesn't currently work very well with node-inspector, and the main process will crash if you inspect the `process` object under node-inspector's console. @@ -40,11 +40,11 @@ $ npm install git+https://git@github.com/enlight/node-pre-gyp.git#detect-electro ### 4. Recompile the `node-inspector` `v8` modules for electron (change the target to your electron version number) ```bash -$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall -$ node_modules/.bin/node-pre-gyp --target=0.36.2 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.11 --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall +$ node_modules/.bin/node-pre-gyp --target=0.36.11 --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall ``` -See also [How to install native modules](how-to-install-native-modules). +See also [How to install native modules][how-to-install-native-modules]. ### 5. Enable debug mode for Electron diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 7d7eb44ecb6..1186918799b 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -15,6 +15,8 @@ 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 var myNotification = new Notification('Title', { body: 'Lorem Ipsum Dolor Sit Amet' @@ -246,12 +248,14 @@ __Launcher shortcuts of Audacious:__ ![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) -## Progress Bar in Taskbar (Windows & Unity) +## Progress Bar in Taskbar (Windows, OS X, Unity) On Windows a taskbar button can be used to display a progress bar. This enables a window to provide progress information to the user without the user having to switch to the window itself. +On OS X the progress bar will be displayed as a part of the dock icon. + The Unity DE also has a similar feature that allows you to specify the progress bar in the launcher. @@ -259,10 +263,6 @@ __Progress bar in taskbar button:__ ![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) -__Progress bar in Unity launcher:__ - -![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) - To set the progress bar for a Window, you can use the [BrowserWindow.setProgressBar][setprogressbar] API: @@ -271,6 +271,33 @@ var window = new BrowserWindow({...}); window.setProgressBar(0.5); ``` +## Icon Overlays in Taskbar (Windows) + +On Windows a taskbar button can use a small overlay to display application +status, as quoted from MSDN: + +> Icon overlays serve as a contextual notification of status, and are intended +> to negate the need for a separate notification area status icon to communicate +> that information to the user. For instance, the new mail status in Microsoft +> Outlook, currently shown in the notification area, can now be indicated +> through an overlay on the taskbar button. Again, you must decide during your +> development cycle which method is best for your application. Overlay icons are +> intended to supply important, long-standing status or notifications such as +> network status, messenger status, or new mail. The user should not be +> presented with constantly changing overlays or animations. + +__Overlay on taskbar button:__ + +![Overlay on taskbar button](https://i-msdn.sec.s-msft.com/dynimg/IC420441.png) + +To set the overlay icon for a window, you can use the +[BrowserWindow.setOverlayIcon][setoverlayicon] API: + +```javascript +var window = new BrowserWindow({...}); +window.setOverlayIcon('path/to/overlay.png', 'Description for overlay'); +``` + ## Represented File of Window (OS X) On OS X a window can set its represented file, so the file's icon can show in @@ -294,15 +321,16 @@ window.setRepresentedFilename('/etc/passwd'); window.setDocumentEdited(true); ``` -[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath -[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments -[setusertaskstasks]: ../api/app.md#appsetusertaskstasks -[setprogressbar]: ../api/browser-window.md#browserwindowsetprogressbarprogress -[setrepresentedfilename]: ../api/browser-window.md#browserwindowsetrepresentedfilenamefilename -[setdocumentedited]: ../api/browser-window.md#browserwindowsetdocumenteditededited +[addrecentdocument]: ../api/app.md#appaddrecentdocumentpath-os-x-windows +[clearrecentdocuments]: ../api/app.md#appclearrecentdocuments-os-x-windows +[setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows +[setprogressbar]: ../api/browser-window.md#winsetprogressbarprogress +[setoverlayicon]: ../api/browser-window.md#winsetoverlayiconoverlay-description-windows-7 +[setrepresentedfilename]: ../api/browser-window.md#winsetrepresentedfilenamefilename-os-x +[setdocumentedited]: ../api/browser-window.md#winsetdocumenteditededited-os-x [app-registration]: http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx [unity-launcher]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher -[setthumbarbuttons]: ../api/browser-window.md#browserwindowsetthumbarbuttonsbuttons +[setthumbarbuttons]: ../api/browser-window.md#winsetthumbarbuttonsbuttons-windows-7 [tray-balloon]: ../api/tray.md#traydisplayballoonoptions-windows [app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx [notification-spec]: https://developer.gnome.org/notification-spec/ diff --git a/docs/tutorial/mac-app-store-submission-guide.md b/docs/tutorial/mac-app-store-submission-guide.md index 036676fe853..91a10d40eb1 100644 --- a/docs/tutorial/mac-app-store-submission-guide.md +++ b/docs/tutorial/mac-app-store-submission-guide.md @@ -4,7 +4,11 @@ Since v0.34.0, Electron allows submitting packaged apps to the Mac App Store (MAS). This guide provides information on: how to submit your app and the limitations of the MAS build. -__Note:__ Submitting an app to Mac App Store requires enrolling [Apple Developer +**Note:** From v0.36.0 there was a bug preventing GPU process to start after +the app being sandboxed, so it is recommended to use v0.35.x before this bug +gets fixed. You can find more about this in [issue #3871][issue-3871]. + +**Note:** Submitting an app to Mac App Store requires enrolling [Apple Developer Program][developer-program], which costs money. ## How to Submit Your App @@ -73,13 +77,18 @@ INSTALLER_KEY="3rd Party Mac Developer Installer: Company Name (APPIDENTITY)" FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Libraries/libnode.dylib" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Electron Framework" -codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/" +codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A" codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper.app/" codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper EH.app/" codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/$APP Helper NP.app/" -codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" +if [ -d "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" ]; then + # Signing a non-MAS build. + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Mantle.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/ReactiveCocoa.framework/Versions/A" + codesign --deep -fs "$APP_KEY" --entitlements child.plist "$FRAMEWORKS_PATH/Squirrel.framework/Versions/A" +fi +codesign -fs "$APP_KEY" --entitlements parent.plist "$APP_PATH" + productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH" ``` @@ -98,8 +107,8 @@ before uploading. Then you can [submit your app for review][submit-for-review]. In order to satisfy all requirements for app sandboxing, the following modules have been disabled in the MAS build: -* `crash-reporter` -* `auto-updater` +* `crashReporter` +* `autoUpdater` and the following behaviors have been changed: @@ -108,8 +117,44 @@ and the following behaviors have been changed: * Apps will not be aware of DNS changes. Also, due to the usage of app sandboxing, the resources which can be accessed by - the app are strictly limited; you can read [App Sandboxing][app-sandboxing] for - more information. +the app are strictly limited; you can read [App Sandboxing][app-sandboxing] for +more information. + +## Cryptographic Algorithms Used by Electron + +Depending on the country and region you are located, Mac App Store may require +documenting the cryptographic algorithms used in your app, and even ask you to +submit a copy of U.S. Encryption Registration (ERN) approval. + +Electron uses following cryptographic algorithms: + +* AES - [NIST SP 800-38A](http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf), [NIST SP 800-38D](http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf), [RFC 3394](http://www.ietf.org/rfc/rfc3394.txt) +* HMAC - [FIPS 198-1](http://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf) +* ECDSA - ANS X9.62–2005 +* ECDH - ANS X9.63–2001 +* HKDF - [NIST SP 800-56C](http://csrc.nist.gov/publications/nistpubs/800-56C/SP-800-56C.pdf) +* PBKDF2 - [RFC 2898](https://tools.ietf.org/html/rfc2898) +* RSA - [RFC 3447](http://www.ietf.org/rfc/rfc3447) +* SHA - [FIPS 180-4](http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf) +* Blowfish - https://www.schneier.com/cryptography/blowfish/ +* CAST - [RFC 2144](https://tools.ietf.org/html/rfc2144), [RFC 2612](https://tools.ietf.org/html/rfc2612) +* DES - [FIPS 46-3](http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf) +* DH - [RFC 2631](https://tools.ietf.org/html/rfc2631) +* DSA - [ANSI X9.30](http://webstore.ansi.org/RecordDetail.aspx?sku=ANSI+X9.30-1%3A1997) +* EC - [SEC 1](http://www.secg.org/sec1-v2.pdf) +* IDEA - "On the Design and Security of Block Ciphers" book by X. Lai +* MD2 - [RFC 1319](http://tools.ietf.org/html/rfc1319) +* MD4 - [RFC 6150](https://tools.ietf.org/html/rfc6150) +* MD5 - [RFC 1321](https://tools.ietf.org/html/rfc1321) +* MDC2 - [ISO/IEC 10118-2](https://www.openssl.org/docs/manmaster/crypto/mdc2.html) +* RC2 - [RFC 2268](https://tools.ietf.org/html/rfc2268) +* RC4 - [RFC 4345](https://tools.ietf.org/html/rfc4345) +* RC5 - http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf +* RIPEMD - [ISO/IEC 10118-3](http://webstore.ansi.org/RecordDetail.aspx?sku=ISO%2FIEC%2010118-3:2004) + +On how to get the ERN approval, you can reference the article: [How to legally +submit an app to Apple’s App Store when it uses encryption (or how to obtain an +ERN)][ern-tutorial]. [developer-program]: https://developer.apple.com/support/compare-memberships/ [submitting-your-app]: https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html @@ -118,3 +163,5 @@ Also, due to the usage of app sandboxing, the resources which can be accessed by [create-record]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/CreatingiTunesConnectRecord.html [submit-for-review]: https://developer.apple.com/library/ios/documentation/LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/SubmittingTheApp.html [app-sandboxing]: https://developer.apple.com/app-sandboxing/ +[issue-3871]: https://github.com/atom/electron/issues/3871 +[ern-tutorial]: https://carouselapps.com/2015/12/15/legally-submit-app-apples-app-store-uses-encryption-obtain-ern/ diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index ebf907e070f..684c72c6669 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -34,8 +34,8 @@ The main process creates web pages by creating `BrowserWindow` instances. Each 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. +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 @@ -43,9 +43,11 @@ 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 provided the [ipc](../api/ipc-renderer.md) module for -communication between the main process and renderer process. There is also a -[remote](../api/remote.md) module for RPC style communication. +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 @@ -205,3 +207,5 @@ $ cd electron-quick-start # Install dependencies and run the app $ npm install && npm start ``` + +[share-data]: ../faq/electron-faq.md#how-to-share-data-between-web-pages diff --git a/docs/tutorial/supported-platforms.md b/docs/tutorial/supported-platforms.md index 9d1a293d5f5..a7f736995d2 100644 --- a/docs/tutorial/supported-platforms.md +++ b/docs/tutorial/supported-platforms.md @@ -5,7 +5,7 @@ Following platforms are supported by Electron: ### OS X Only 64bit binaries are provided for OS X, and the minimum OS X version -supported is OS X 10.8. +supported is OS X 10.9. ### Windows diff --git a/docs/tutorial/testing-on-headless-ci.md b/docs/tutorial/testing-on-headless-ci.md new file mode 100644 index 00000000000..ec1f4635c90 --- /dev/null +++ b/docs/tutorial/testing-on-headless-ci.md @@ -0,0 +1,60 @@ +# Testing Electron with headless CI Systems (Travis CI, Jenkins) + +Being based on Chromium, Electron requires a display driver to function. +If Chromium can't find a display driver, Electron will simply fail to launch - +and therefore not executing any of your tests, regardless of how you are running +them. Testing Electron-based apps on Travis, Circle, Jenkins or similar Systems +requires therefore a little bit of configuration. In essence, we need to use +a virtual display driver. + +## Configuring the Virtual Display Server + +First, install [Xvfb](https://en.wikipedia.org/wiki/Xvfb). +It's a virtual framebuffer, implementing the X11 display server protocol - +it performs all graphical operations in memory without showing any screen output, +which is exactly what we need. + +Then, create a virtual xvfb screen and export an environment variable +called DISPLAY that points to it. Chromium in Electron will automatically look +for `$DISPLAY`, so no further configuration of your app is required. +This step can be automated with Paul Betts's +[xfvb-maybe](https://github.com/paulcbetts/xvfb-maybe): Prepend your test +commands with `xfvb-maybe` and the little tool will automatically configure +xfvb, if required by the current system. On Windows or Mac OS X, it will simply +do nothing. + +``` +## On Windows or OS X, this just invokes electron-mocha +## On Linux, if we are in a headless environment, this will be equivalent +## to xvfb-run electron-mocha ./test/*.js +xvfb-maybe electron-mocha ./test/*.js +``` + +### Travis CI + +On Travis, your `.travis.yml` should look roughly like this: + +``` +addons: + apt: + packages: + - xvfb + +install: + - export DISPLAY=':99.0' + - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & +``` + +### Jenkins + +For Jenkins, a [Xfvb plugin is available](https://wiki.jenkins-ci.org/display/JENKINS/Xvfb+Plugin). + +### Circle CI + +Circle CI is awesome and has xvfb and `$DISPLAY` +[already setup, so no further configuration is required](https://circleci.com/docs/environment#browsers). + +### AppVeyor + +AppVeyor runs on Windows, supporting Selenium, Chromium, Electron and similar +tools out of the box - no configuration is required. diff --git a/docs/tutorial/using-selenium-and-webdriver.md b/docs/tutorial/using-selenium-and-webdriver.md index 035dabdfe79..2d296548dd9 100644 --- a/docs/tutorial/using-selenium-and-webdriver.md +++ b/docs/tutorial/using-selenium-and-webdriver.md @@ -49,7 +49,7 @@ var driver = new webdriver.Builder() .withCapabilities({ chromeOptions: { // Here is the path to your Electron binary. - binary: '/Path-to-Your-App.app/Contents/MacOS/Atom', + binary: '/Path-to-Your-App.app/Contents/MacOS/Electron', } }) .forBrowser('electron') diff --git a/docs/tutorial/using-widevine-cdm-plugin.md b/docs/tutorial/using-widevine-cdm-plugin.md index 340dad343c8..fa288bab0ff 100644 --- a/docs/tutorial/using-widevine-cdm-plugin.md +++ b/docs/tutorial/using-widevine-cdm-plugin.md @@ -8,6 +8,10 @@ Electron doesn't ship with the Widevine CDM plugin for license reasons, to get it, you need to install the official Chrome browser first, which should match the architecture and Chrome version of the Electron build you use. +**Note:** The major version of Chrome browser has to be the same with the Chrome +version used by Electron, otherwise the plugin will not work even though +`navigator.plugins` would show it has been loaded. + ### Windows & OS X Open `chrome://components/` in Chrome browser, find `WidevineCdm` and make @@ -37,7 +41,7 @@ After getting the plugin files, you should pass the `widevinecdmadapter`'s path to Electron with `--widevine-cdm-path` command line switch, and the plugin's version with `--widevine-cdm-version` switch. -__Note:__ Though only the `widevinecdmadapter` binary is passed to Electron, the +**Note:** Though only the `widevinecdmadapter` binary is passed to Electron, the `widevinecdm` binary has to be put aside it. The command line switches have to be passed before the `ready` event of `app` diff --git a/atom.gyp b/electron.gyp similarity index 93% rename from atom.gyp rename to electron.gyp index 0960572dac2..9652bd662c7 100644 --- a/atom.gyp +++ b/electron.gyp @@ -4,7 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.36.4', + 'version%': '0.37.3', }, 'includes': [ 'filenames.gypi', @@ -29,6 +29,7 @@ 'type': 'executable', 'dependencies': [ 'js2asar', + 'app2asar', '<(project_name)_lib', ], 'sources': [ @@ -66,12 +67,6 @@ '<(PRODUCT_DIR)/<(product_name) Framework.framework', ], }, - { - 'destination': '<(PRODUCT_DIR)/<(product_name).app/Contents/Resources', - 'files': [ - 'atom/browser/default_app', - ], - }, ], 'postbuilds': [ { @@ -92,7 +87,7 @@ }, # The application doesn't have real localizations, it just has # empty .lproj directories, which is enough to convince Cocoa - # atom-shell supports those languages. + # that Electron supports those languages. { 'postbuild_name': 'Make Empty Localizations', 'variables': { @@ -143,7 +138,7 @@ ], }, { 'copied_libraries': [ - '<(libchromiumcontent_dir)/pdf.dll', + '<(libchromiumcontent_dir)/ffmpeg.dll', ], }], ], @@ -167,12 +162,6 @@ 'external_binaries/vccorlib120.dll', ], }, - { - 'destination': '<(PRODUCT_DIR)/resources', - 'files': [ - 'atom/browser/default_app', - ] - }, ], }, { 'dependencies': [ @@ -193,6 +182,7 @@ }, { 'copied_libraries': [ '<(PRODUCT_DIR)/lib/libnode.so', + '<(libchromiumcontent_dir)/libffmpeg.so', ], }], ], @@ -207,12 +197,6 @@ '<(libchromiumcontent_dir)/snapshot_blob.bin', ], }, - { - 'destination': '<(PRODUCT_DIR)/resources', - 'files': [ - 'atom/browser/default_app', - ] - }, ], }], # OS=="linux" ], @@ -369,17 +353,49 @@ '<@(js_sources)', ], 'outputs': [ - '<(resources_path)/atom.asar', + '<(resources_path)/electron.asar', ], 'action': [ 'python', 'tools/js2asar.py', '<@(_outputs)', + 'lib', '<@(_inputs)', ], } ], }, # target js2asar + { + 'target_name': 'app2asar', + 'type': 'none', + 'actions': [ + { + 'action_name': 'app2asar', + 'variables': { + 'conditions': [ + ['OS=="mac"', { + 'resources_path': '<(PRODUCT_DIR)/<(product_name).app/Contents/Resources', + },{ + 'resources_path': '<(PRODUCT_DIR)/resources', + }], + ], + }, + 'inputs': [ + '<@(default_app_sources)', + ], + 'outputs': [ + '<(resources_path)/default_app.asar', + ], + 'action': [ + 'python', + 'tools/js2asar.py', + '<@(_outputs)', + 'default_app', + '<@(_inputs)', + ], + } + ], + }, # target app2asar { 'target_name': 'atom_js2c', 'type': 'none', @@ -461,6 +477,7 @@ }, { 'copied_libraries': [ '<(PRODUCT_DIR)/libnode.dylib', + '<(libchromiumcontent_dir)/libffmpeg.dylib', ], }], ], @@ -487,7 +504,7 @@ 'action': [ 'install_name_tool', '-change', - '@loader_path/libffmpeg.dylib', + '/usr/local/lib/libffmpeg.dylib', '@rpath/libffmpeg.dylib', '${BUILT_PRODUCTS_DIR}/<(product_name) Framework.framework/Versions/A/<(product_name) Framework', ], diff --git a/filenames.gypi b/filenames.gypi index 61aa3d43d4a..2c39a8dccef 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -5,66 +5,73 @@ 'atom/app/atom_main.h', ], 'bundle_sources': [ - 'atom/browser/resources/mac/atom.icns', + 'atom/browser/resources/mac/electron.icns', ], 'js_sources': [ - 'atom/browser/api/lib/app.js', - 'atom/browser/api/lib/auto-updater.js', - 'atom/browser/api/lib/auto-updater/auto-updater-native.js', - 'atom/browser/api/lib/auto-updater/auto-updater-win.js', - 'atom/browser/api/lib/auto-updater/squirrel-update-win.js', - 'atom/browser/api/lib/browser-window.js', - 'atom/browser/api/lib/content-tracing.js', - 'atom/browser/api/lib/dialog.js', - 'atom/browser/api/lib/exports/electron.js', - 'atom/browser/api/lib/global-shortcut.js', - 'atom/browser/api/lib/ipc.js', - 'atom/browser/api/lib/ipc-main.js', - 'atom/browser/api/lib/menu.js', - 'atom/browser/api/lib/menu-item.js', - 'atom/browser/api/lib/navigation-controller.js', - 'atom/browser/api/lib/power-monitor.js', - 'atom/browser/api/lib/power-save-blocker.js', - 'atom/browser/api/lib/protocol.js', - 'atom/browser/api/lib/session.js', - 'atom/browser/api/lib/screen.js', - 'atom/browser/api/lib/tray.js', - 'atom/browser/api/lib/web-contents.js', - 'atom/browser/lib/chrome-extension.js', - 'atom/browser/lib/desktop-capturer.js', - 'atom/browser/lib/guest-view-manager.js', - 'atom/browser/lib/guest-window-manager.js', - 'atom/browser/lib/init.js', - 'atom/browser/lib/objects-registry.js', - 'atom/browser/lib/rpc-server.js', - 'atom/common/api/lib/callbacks-registry.js', - 'atom/common/api/lib/clipboard.js', - 'atom/common/api/lib/crash-reporter.js', - 'atom/common/api/lib/deprecate.js', - 'atom/common/api/lib/exports/electron.js', - 'atom/common/api/lib/native-image.js', - 'atom/common/api/lib/shell.js', - 'atom/common/lib/init.js', - 'atom/common/lib/reset-search-paths.js', - 'atom/renderer/lib/chrome-api.js', - 'atom/renderer/lib/init.js', - 'atom/renderer/lib/inspector.js', - 'atom/renderer/lib/override.js', - 'atom/renderer/lib/web-view/guest-view-internal.js', - 'atom/renderer/lib/web-view/web-view.js', - 'atom/renderer/lib/web-view/web-view-attributes.js', - 'atom/renderer/lib/web-view/web-view-constants.js', - 'atom/renderer/api/lib/desktop-capturer.js', - 'atom/renderer/api/lib/exports/electron.js', - 'atom/renderer/api/lib/ipc.js', - 'atom/renderer/api/lib/ipc-renderer.js', - 'atom/renderer/api/lib/remote.js', - 'atom/renderer/api/lib/screen.js', - 'atom/renderer/api/lib/web-frame.js', + 'lib/browser/api/app.js', + 'lib/browser/api/auto-updater.js', + 'lib/browser/api/auto-updater/auto-updater-native.js', + 'lib/browser/api/auto-updater/auto-updater-win.js', + 'lib/browser/api/auto-updater/squirrel-update-win.js', + 'lib/browser/api/browser-window.js', + 'lib/browser/api/content-tracing.js', + 'lib/browser/api/dialog.js', + 'lib/browser/api/exports/electron.js', + 'lib/browser/api/global-shortcut.js', + 'lib/browser/api/ipc.js', + 'lib/browser/api/ipc-main.js', + 'lib/browser/api/menu.js', + 'lib/browser/api/menu-item.js', + 'lib/browser/api/navigation-controller.js', + 'lib/browser/api/power-monitor.js', + 'lib/browser/api/power-save-blocker.js', + 'lib/browser/api/protocol.js', + 'lib/browser/api/session.js', + 'lib/browser/api/screen.js', + 'lib/browser/api/tray.js', + 'lib/browser/api/web-contents.js', + 'lib/browser/chrome-extension.js', + 'lib/browser/desktop-capturer.js', + 'lib/browser/guest-view-manager.js', + 'lib/browser/guest-window-manager.js', + 'lib/browser/init.js', + 'lib/browser/objects-registry.js', + 'lib/browser/rpc-server.js', + 'lib/common/api/callbacks-registry.js', + 'lib/common/api/clipboard.js', + 'lib/common/api/crash-reporter.js', + 'lib/common/api/deprecate.js', + 'lib/common/api/deprecations.js', + 'lib/common/api/exports/electron.js', + 'lib/common/api/native-image.js', + 'lib/common/api/shell.js', + 'lib/common/init.js', + 'lib/common/reset-search-paths.js', + 'lib/renderer/chrome-api.js', + 'lib/renderer/init.js', + 'lib/renderer/inspector.js', + 'lib/renderer/override.js', + 'lib/renderer/web-view/guest-view-internal.js', + 'lib/renderer/web-view/web-view.js', + 'lib/renderer/web-view/web-view-attributes.js', + 'lib/renderer/web-view/web-view-constants.js', + 'lib/renderer/api/desktop-capturer.js', + 'lib/renderer/api/exports/electron.js', + 'lib/renderer/api/ipc.js', + 'lib/renderer/api/ipc-renderer.js', + 'lib/renderer/api/remote.js', + 'lib/renderer/api/screen.js', + 'lib/renderer/api/web-frame.js', ], 'js2c_sources': [ - 'atom/common/lib/asar.js', - 'atom/common/lib/asar_init.js', + 'lib/common/asar.js', + 'lib/common/asar_init.js', + ], + 'default_app_sources': [ + 'default_app/default_app.js', + 'default_app/index.html', + 'default_app/main.js', + 'default_app/package.json', ], 'lib_sources': [ 'atom/app/atom_content_client.cc', @@ -83,6 +90,8 @@ 'atom/browser/api/atom_api_content_tracing.cc', 'atom/browser/api/atom_api_cookies.cc', 'atom/browser/api/atom_api_cookies.h', + 'atom/browser/api/atom_api_debugger.cc', + 'atom/browser/api/atom_api_debugger.h', 'atom/browser/api/atom_api_desktop_capturer.cc', 'atom/browser/api/atom_api_desktop_capturer.h', 'atom/browser/api/atom_api_download_item.cc', @@ -142,6 +151,8 @@ 'atom/browser/atom_browser_main_parts_posix.cc', 'atom/browser/atom_javascript_dialog_manager.cc', 'atom/browser/atom_javascript_dialog_manager.h', + 'atom/browser/atom_permission_manager.cc', + 'atom/browser/atom_permission_manager.h', 'atom/browser/atom_quota_permission_context.cc', 'atom/browser/atom_quota_permission_context.h', 'atom/browser/atom_resource_dispatcher_host_delegate.cc', @@ -254,6 +265,8 @@ 'atom/browser/ui/x/window_state_watcher.h', 'atom/browser/ui/x/x_window_utils.cc', 'atom/browser/ui/x/x_window_utils.h', + 'atom/browser/web_contents_permission_helper.cc', + 'atom/browser/web_contents_permission_helper.h', 'atom/browser/web_contents_preferences.cc', 'atom/browser/web_contents_preferences.h', 'atom/browser/web_dialog_helper.cc', @@ -315,8 +328,10 @@ 'atom/common/google_api_key.h', 'atom/common/id_weak_map.cc', 'atom/common/id_weak_map.h', - 'atom/common/keyboad_util.cc', - 'atom/common/keyboad_util.h', + 'atom/common/keyboard_util.cc', + 'atom/common/keyboard_util.h', + 'atom/common/mouse_util.cc', + 'atom/common/mouse_util.h', 'atom/common/linux/application_info.cc', 'atom/common/native_mate_converters/accelerator_converter.cc', 'atom/common/native_mate_converters/accelerator_converter.h', @@ -525,7 +540,6 @@ 'atom/browser/resources/win/resource.h', 'atom/browser/resources/win/atom.ico', 'atom/browser/resources/win/atom.rc', - '<(libchromiumcontent_src_dir)/content/app/startup_helper_win.cc', # Cursors. '<(libchromiumcontent_src_dir)/ui/resources/cursors/aliasb.cur', '<(libchromiumcontent_src_dir)/ui/resources/cursors/cell.cur', diff --git a/lib/browser/api/app.js b/lib/browser/api/app.js new file mode 100644 index 00000000000..48a27cfc648 --- /dev/null +++ b/lib/browser/api/app.js @@ -0,0 +1,117 @@ +'use strict' + +const deprecate = require('electron').deprecate +const session = require('electron').session +const Menu = require('electron').Menu +const EventEmitter = require('events').EventEmitter + +const bindings = process.atomBinding('app') +const downloadItemBindings = process.atomBinding('download_item') +const app = bindings.app + +Object.setPrototypeOf(app, EventEmitter.prototype) + +app.setApplicationMenu = function (menu) { + return Menu.setApplicationMenu(menu) +} + +app.getApplicationMenu = function () { + return Menu.getApplicationMenu() +} + +app.commandLine = { + appendSwitch: bindings.appendSwitch, + appendArgument: bindings.appendArgument +} + +if (process.platform === 'darwin') { + app.dock = { + bounce: function (type) { + if (type == null) { + type = 'informational' + } + return bindings.dockBounce(type) + }, + cancelBounce: bindings.dockCancelBounce, + setBadge: bindings.dockSetBadgeText, + getBadge: bindings.dockGetBadgeText, + hide: bindings.dockHide, + show: bindings.dockShow, + setMenu: bindings.dockSetMenu, + setIcon: bindings.dockSetIcon + } +} + +var appPath = null + +app.setAppPath = function (path) { + appPath = path +} + +app.getAppPath = function () { + return appPath +} + +// Routes the events to webContents. +var ref1 = ['login', 'certificate-error', 'select-client-certificate'] +var fn = function (name) { + return app.on(name, function (event, webContents, ...args) { + return webContents.emit.apply(webContents, [name, event].concat(args)) + }) +} +var i, len +for (i = 0, len = ref1.length; i < len; i++) { + fn(ref1[i]) +} + +// Deprecated. + +app.getHomeDir = deprecate('app.getHomeDir', 'app.getPath', function () { + return this.getPath('home') +}) + +app.getDataPath = deprecate('app.getDataPath', 'app.getPath', function () { + return this.getPath('userData') +}) + +app.setDataPath = deprecate('app.setDataPath', 'app.setPath', function (path) { + return this.setPath('userData', path) +}) + +app.resolveProxy = deprecate('app.resolveProxy', 'session.defaultSession.resolveProxy', function (url, callback) { + return session.defaultSession.resolveProxy(url, callback) +}) + +deprecate.rename(app, 'terminate', 'quit') + +deprecate.event(app, 'finish-launching', 'ready', function () { + // give default app a chance to setup default menu. + setImmediate(() => { + this.emit('finish-launching') + }) +}) + +deprecate.event(app, 'activate-with-no-open-windows', 'activate', function (event, hasVisibleWindows) { + if (!hasVisibleWindows) { + return this.emit('activate-with-no-open-windows', event) + } +}) + +deprecate.event(app, 'select-certificate', 'select-client-certificate') + +// Wrappers for native classes. +var wrapDownloadItem = function (downloadItem) { + // downloadItem is an EventEmitter. + Object.setPrototypeOf(downloadItem, EventEmitter.prototype) + + // Deprecated. + deprecate.property(downloadItem, 'url', 'getURL') + deprecate.property(downloadItem, 'filename', 'getFilename') + deprecate.property(downloadItem, 'mimeType', 'getMimeType') + return deprecate.rename(downloadItem, 'getUrl', 'getURL') +} + +downloadItemBindings._setWrapDownloadItem(wrapDownloadItem) + +// Only one App object pemitted. +module.exports = app diff --git a/lib/browser/api/auto-updater.js b/lib/browser/api/auto-updater.js new file mode 100644 index 00000000000..2b5133070f4 --- /dev/null +++ b/lib/browser/api/auto-updater.js @@ -0,0 +1,7 @@ +const deprecate = require('electron').deprecate +const autoUpdater = process.platform === 'win32' ? require('./auto-updater/auto-updater-win') : require('./auto-updater/auto-updater-native') + +// Deprecated. +deprecate.rename(autoUpdater, 'setFeedUrl', 'setFeedURL') + +module.exports = autoUpdater diff --git a/lib/browser/api/auto-updater/auto-updater-native.js b/lib/browser/api/auto-updater/auto-updater-native.js new file mode 100644 index 00000000000..1fff05dbd65 --- /dev/null +++ b/lib/browser/api/auto-updater/auto-updater-native.js @@ -0,0 +1,6 @@ +const EventEmitter = require('events').EventEmitter +const autoUpdater = process.atomBinding('auto_updater').autoUpdater + +Object.setPrototypeOf(autoUpdater, EventEmitter.prototype) + +module.exports = autoUpdater diff --git a/lib/browser/api/auto-updater/auto-updater-win.js b/lib/browser/api/auto-updater/auto-updater-win.js new file mode 100644 index 00000000000..896b9ffce9d --- /dev/null +++ b/lib/browser/api/auto-updater/auto-updater-win.js @@ -0,0 +1,62 @@ +'use strict' + +const app = require('electron').app +const EventEmitter = require('events').EventEmitter +const squirrelUpdate = require('./squirrel-update-win') +const util = require('util') + +function AutoUpdater () { + EventEmitter.call(this) +} + +util.inherits(AutoUpdater, EventEmitter) + +AutoUpdater.prototype.quitAndInstall = function () { + squirrelUpdate.processStart() + return app.quit() +} + +AutoUpdater.prototype.setFeedURL = function (updateURL) { + this.updateURL = updateURL +} + +AutoUpdater.prototype.checkForUpdates = function () { + if (!this.updateURL) { + return this.emitError('Update URL is not set') + } + if (!squirrelUpdate.supported()) { + return this.emitError('Can not find Squirrel') + } + this.emit('checking-for-update') + squirrelUpdate.download(this.updateURL, (error, update) => { + if (error != null) { + return this.emitError(error) + } + if (update == null) { + return this.emit('update-not-available') + } + this.emit('update-available') + squirrelUpdate.update(this.updateURL, (error) => { + var date, releaseNotes, version + if (error != null) { + return this.emitError(error) + } + releaseNotes = update.releaseNotes + version = update.version + + // Following information is not available on Windows, so fake them. + date = new Date() + this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => { + this.quitAndInstall() + }) + }) + }) +} + +// Private: Emit both error object and message, this is to keep compatibility +// with Old APIs. +AutoUpdater.prototype.emitError = function (message) { + return this.emit('error', new Error(message), message) +} + +module.exports = new AutoUpdater() diff --git a/lib/browser/api/auto-updater/squirrel-update-win.js b/lib/browser/api/auto-updater/squirrel-update-win.js new file mode 100644 index 00000000000..da27e02aab6 --- /dev/null +++ b/lib/browser/api/auto-updater/squirrel-update-win.js @@ -0,0 +1,95 @@ +const fs = require('fs') +const path = require('path') +const spawn = require('child_process').spawn + +// i.e. my-app/app-0.1.13/ +const appFolder = path.dirname(process.execPath) + +// i.e. my-app/Update.exe +const updateExe = path.resolve(appFolder, '..', 'Update.exe') + +const exeName = path.basename(process.execPath) + +// Spawn a command and invoke the callback when it completes with an error +// and the output from standard out. +var spawnUpdate = function (args, detached, callback) { + var error, errorEmitted, spawnedProcess, stderr, stdout + try { + spawnedProcess = spawn(updateExe, args, { + detached: detached + }) + } catch (error1) { + error = error1 + + // Shouldn't happen, but still guard it. + process.nextTick(function () { + return callback(error) + }) + return + } + stdout = '' + stderr = '' + spawnedProcess.stdout.on('data', function (data) { + stdout += data + }) + spawnedProcess.stderr.on('data', function (data) { + stderr += data + }) + errorEmitted = false + spawnedProcess.on('error', function (error) { + errorEmitted = true + return callback(error) + }) + return spawnedProcess.on('exit', function (code, signal) { + // We may have already emitted an error. + if (errorEmitted) { + return + } + + // Process terminated with error. + if (code !== 0) { + return callback('Command failed: ' + (signal != null ? signal : code) + '\n' + stderr) + } + + // Success. + return callback(null, stdout) + }) +} + +// Start an instance of the installed app. +exports.processStart = function () { + return spawnUpdate(['--processStart', exeName], true, function () {}) +} + +// Download the releases specified by the URL and write new results to stdout. +exports.download = function (updateURL, callback) { + return spawnUpdate(['--download', updateURL], false, function (error, stdout) { + var json, ref, ref1, update + if (error != null) { + return callback(error) + } + try { + // Last line of output is the JSON details about the releases + json = stdout.trim().split('\n').pop() + update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0 + } catch (jsonError) { + return callback('Invalid result:\n' + stdout) + } + return callback(null, update) + }) +} + +// Update the application to the latest remote version specified by URL. +exports.update = function (updateURL, callback) { + return spawnUpdate(['--update', updateURL], false, callback) +} + +// Is the Update.exe installed with the current application? +exports.supported = function () { + try { + fs.accessSync(updateExe, fs.R_OK) + return true + } catch (error) { + return false + } +} diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js new file mode 100644 index 00000000000..33aea5da4d0 --- /dev/null +++ b/lib/browser/api/browser-window.js @@ -0,0 +1,293 @@ +'use strict' + +const ipcMain = require('electron').ipcMain +const deprecate = require('electron').deprecate +const EventEmitter = require('events').EventEmitter +const {BrowserWindow, _setDeprecatedOptionsCheck} = process.atomBinding('window') + +Object.setPrototypeOf(BrowserWindow.prototype, EventEmitter.prototype) + +BrowserWindow.prototype._init = function () { + // avoid recursive require. + var app, menu + app = require('electron').app + + // Simulate the application menu on platforms other than OS X. + if (process.platform !== 'darwin') { + menu = app.getApplicationMenu() + if (menu != null) { + this.setMenu(menu) + } + } + + // Make new windows requested by links behave like "window.open" + this.webContents.on('-new-window', (event, url, frameName, disposition) => { + var options + options = { + show: true, + width: 800, + height: 600 + } + return ipcMain.emit('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, disposition, options) + }) + + // window.resizeTo(...) + // window.moveTo(...) + this.webContents.on('move', (event, size) => { + this.setBounds(size) + }) + + // Hide the auto-hide menu when webContents is focused. + this.webContents.on('activate', () => { + if (process.platform !== 'darwin' && this.isMenuBarAutoHide() && this.isMenuBarVisible()) { + this.setMenuBarVisibility(false) + } + }) + + // Forward the crashed event. + this.webContents.on('crashed', () => { + this.emit('crashed') + }) + + // Change window title to page title. + this.webContents.on('page-title-updated', (event, title) => { + // The page-title-updated event is not emitted immediately (see #3645), so + // when the callback is called the BrowserWindow might have been closed. + if (this.isDestroyed()) return + + // Route the event to BrowserWindow. + this.emit('page-title-updated', event, title) + if (!event.defaultPrevented) this.setTitle(title) + }) + + // Sometimes the webContents doesn't get focus when window is shown, so we have + // to force focusing on webContents in this case. The safest way is to focus it + // when we first start to load URL, if we do it earlier it won't have effect, + // if we do it later we might move focus in the page. + // Though this hack is only needed on OS X when the app is launched from + // Finder, we still do it on all platforms in case of other bugs we don't know. + this.webContents.once('load-url', function () { + this.focus() + }) + + // Redirect focus/blur event to app instance too. + this.on('blur', (event) => { + app.emit('browser-window-blur', event, this) + }) + this.on('focus', (event) => { + app.emit('browser-window-focus', event, this) + }) + + // Evented visibilityState changes + this.on('show', () => { + this.webContents.send('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', this.isVisible(), this.isMinimized()) + }) + this.on('hide', () => { + this.webContents.send('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', this.isVisible(), this.isMinimized()) + }) + this.on('minimize', () => { + this.webContents.send('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', this.isVisible(), this.isMinimized()) + }) + this.on('restore', () => { + this.webContents.send('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', this.isVisible(), this.isMinimized()) + }) + + // Notify the creation of the window. + app.emit('browser-window-created', {}, this) + + // Be compatible with old APIs. + this.webContents.on('devtools-focused', () => { + this.emit('devtools-focused') + }) + this.webContents.on('devtools-opened', () => { + this.emit('devtools-opened') + }) + this.webContents.on('devtools-closed', () => { + this.emit('devtools-closed') + }) + Object.defineProperty(this, 'devToolsWebContents', { + enumerable: true, + configurable: false, + get: function () { + return this.webContents.devToolsWebContents + } + }) +} + +BrowserWindow.getFocusedWindow = function () { + var i, len, window, windows + windows = BrowserWindow.getAllWindows() + for (i = 0, len = windows.length; i < len; i++) { + window = windows[i] + if (window.isFocused()) { + return window + } + } + return null +} + +BrowserWindow.fromWebContents = function (webContents) { + var i, len, ref1, window, windows + windows = BrowserWindow.getAllWindows() + for (i = 0, len = windows.length; i < len; i++) { + window = windows[i] + if ((ref1 = window.webContents) != null ? ref1.equal(webContents) : void 0) { + return window + } + } +} + +BrowserWindow.fromDevToolsWebContents = function (webContents) { + var i, len, ref1, window, windows + windows = BrowserWindow.getAllWindows() + for (i = 0, len = windows.length; i < len; i++) { + window = windows[i] + if ((ref1 = window.devToolsWebContents) != null ? ref1.equal(webContents) : void 0) { + return window + } + } +} + +// Helpers. + +BrowserWindow.prototype.loadURL = function () { + return this.webContents.loadURL.apply(this.webContents, arguments) +} + +BrowserWindow.prototype.getURL = function () { + return this.webContents.getURL() +} + +BrowserWindow.prototype.reload = function () { + return this.webContents.reload.apply(this.webContents, arguments) +} + +BrowserWindow.prototype.send = function () { + return this.webContents.send.apply(this.webContents, arguments) +} + +BrowserWindow.prototype.openDevTools = function () { + return this.webContents.openDevTools.apply(this.webContents, arguments) +} + +BrowserWindow.prototype.closeDevTools = function () { + return this.webContents.closeDevTools() +} + +BrowserWindow.prototype.isDevToolsOpened = function () { + return this.webContents.isDevToolsOpened() +} + +BrowserWindow.prototype.isDevToolsFocused = function () { + return this.webContents.isDevToolsFocused() +} + +BrowserWindow.prototype.toggleDevTools = function () { + return this.webContents.toggleDevTools() +} + +BrowserWindow.prototype.inspectElement = function () { + return this.webContents.inspectElement.apply(this.webContents, arguments) +} + +BrowserWindow.prototype.inspectServiceWorker = function () { + return this.webContents.inspectServiceWorker() +} + +// Deprecated. + +deprecate.member(BrowserWindow, 'undo', 'webContents') + +deprecate.member(BrowserWindow, 'redo', 'webContents') + +deprecate.member(BrowserWindow, 'cut', 'webContents') + +deprecate.member(BrowserWindow, 'copy', 'webContents') + +deprecate.member(BrowserWindow, 'paste', 'webContents') + +deprecate.member(BrowserWindow, 'selectAll', 'webContents') + +deprecate.member(BrowserWindow, 'reloadIgnoringCache', 'webContents') + +deprecate.member(BrowserWindow, 'isLoading', 'webContents') + +deprecate.member(BrowserWindow, 'isWaitingForResponse', 'webContents') + +deprecate.member(BrowserWindow, 'stop', 'webContents') + +deprecate.member(BrowserWindow, 'isCrashed', 'webContents') + +deprecate.member(BrowserWindow, 'print', 'webContents') + +deprecate.member(BrowserWindow, 'printToPDF', 'webContents') + +deprecate.rename(BrowserWindow, 'restart', 'reload') + +deprecate.rename(BrowserWindow, 'loadUrl', 'loadURL') + +deprecate.rename(BrowserWindow, 'getUrl', 'getURL') + +BrowserWindow.prototype.executeJavaScriptInDevTools = deprecate('executeJavaScriptInDevTools', 'devToolsWebContents.executeJavaScript', function (code) { + var ref1 + return (ref1 = this.devToolsWebContents) != null ? ref1.executeJavaScript(code) : void 0 +}) + +BrowserWindow.prototype.getPageTitle = deprecate('getPageTitle', 'webContents.getTitle', function () { + var ref1 + return (ref1 = this.webContents) != null ? ref1.getTitle() : void 0 +}) + +const isDeprecatedKey = function (key) { + return key.indexOf('-') >= 0 +} + +// Map deprecated key with hyphens to camel case key +const getNonDeprecatedKey = function (deprecatedKey) { + return deprecatedKey.replace(/-./g, function (match) { + return match[1].toUpperCase() + }) +} + +// TODO Remove for 1.0 +const checkForDeprecatedOptions = function (options) { + if (!options) return '' + + let keysToCheck = Object.keys(options) + if (options.webPreferences) { + keysToCheck = keysToCheck.concat(Object.keys(options.webPreferences)) + } + + // Check options for keys with hyphens in them + let deprecatedKey = keysToCheck.filter(isDeprecatedKey)[0] + if (deprecatedKey) { + try { + deprecate.warn(deprecatedKey, getNonDeprecatedKey(deprecatedKey)) + } catch (error) { + // Return error message so it can be rethrown via C++ + return error.message + } + } + + let webPreferenceOption + if (options.hasOwnProperty('nodeIntegration')) { + webPreferenceOption = 'nodeIntegration' + } else if (options.hasOwnProperty('preload')) { + webPreferenceOption = 'preload' + } else if (options.hasOwnProperty('zoomFactor')) { + webPreferenceOption = 'zoomFactor' + } + if (webPreferenceOption) { + try { + deprecate.warn(`options.${webPreferenceOption}`, `options.webPreferences.${webPreferenceOption}`) + } catch (error) { + // Return error message so it can be rethrown via C++ + return error.message + } + } + + return '' +} +_setDeprecatedOptionsCheck(checkForDeprecatedOptions) + +module.exports = BrowserWindow diff --git a/lib/browser/api/content-tracing.js b/lib/browser/api/content-tracing.js new file mode 100644 index 00000000000..fbf676236b2 --- /dev/null +++ b/lib/browser/api/content-tracing.js @@ -0,0 +1 @@ +module.exports = process.atomBinding('content_tracing') diff --git a/lib/browser/api/dialog.js b/lib/browser/api/dialog.js new file mode 100644 index 00000000000..6669d8cab89 --- /dev/null +++ b/lib/browser/api/dialog.js @@ -0,0 +1,190 @@ +'use strict' + +const app = require('electron').app +const BrowserWindow = require('electron').BrowserWindow +const binding = process.atomBinding('dialog') +const v8Util = process.atomBinding('v8_util') + +var includes = [].includes + +var fileDialogProperties = { + openFile: 1 << 0, + openDirectory: 1 << 1, + multiSelections: 1 << 2, + createDirectory: 1 << 3 +} + +var messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] + +var messageBoxOptions = { + noLink: 1 << 0 +} + +var parseArgs = function (window, options, callback, ...args) { + if (!(window === null || (window != null ? window.constructor : void 0) === BrowserWindow)) { + // Shift. + callback = options + options = window + window = null + } + + if ((callback == null) && typeof options === 'function') { + // Shift. + callback = options + options = null + } + + // Fallback to using very last argument as the callback function + if ((callback == null) && typeof args[args.length - 1] === 'function') { + callback = args[args.length - 1] + } + + return [window, options, callback] +} + +var checkAppInitialized = function () { + if (!app.isReady()) { + throw new Error('dialog module can only be used after app is ready') + } +} + +module.exports = { + showOpenDialog: function (...args) { + var prop, properties, value, wrappedCallback + checkAppInitialized() + let [window, options, callback] = parseArgs.apply(null, args) + if (options == null) { + options = { + title: 'Open', + properties: ['openFile'] + } + } + if (options.properties == null) { + options.properties = ['openFile'] + } + if (!Array.isArray(options.properties)) { + throw new TypeError('Properties must be an array') + } + properties = 0 + for (prop in fileDialogProperties) { + value = fileDialogProperties[prop] + if (includes.call(options.properties, prop)) { + properties |= value + } + } + if (options.title == null) { + options.title = '' + } else if (typeof options.title !== 'string') { + throw new TypeError('Title must be a string') + } + if (options.defaultPath == null) { + options.defaultPath = '' + } else if (typeof options.defaultPath !== 'string') { + throw new TypeError('Default path must be a string') + } + if (options.filters == null) { + options.filters = [] + } + wrappedCallback = typeof callback === 'function' ? function (success, result) { + return callback(success ? result : void 0) + } : null + return binding.showOpenDialog(String(options.title), String(options.defaultPath), options.filters, properties, window, wrappedCallback) + }, + + showSaveDialog: function (...args) { + var wrappedCallback + checkAppInitialized() + let [window, options, callback] = parseArgs.apply(null, args) + if (options == null) { + options = { + title: 'Save' + } + } + if (options.title == null) { + options.title = '' + } else if (typeof options.title !== 'string') { + throw new TypeError('Title must be a string') + } + if (options.defaultPath == null) { + options.defaultPath = '' + } else if (typeof options.defaultPath !== 'string') { + throw new TypeError('Default path must be a string') + } + if (options.filters == null) { + options.filters = [] + } + wrappedCallback = typeof callback === 'function' ? function (success, result) { + return callback(success ? result : void 0) + } : null + return binding.showSaveDialog(String(options.title), String(options.defaultPath), options.filters, window, wrappedCallback) + }, + + showMessageBox: function (...args) { + var flags, i, j, len, messageBoxType, ref2, ref3, text + checkAppInitialized() + let [window, options, callback] = parseArgs.apply(null, args) + if (options == null) { + options = { + type: 'none' + } + } + if (options.type == null) { + options.type = 'none' + } + messageBoxType = messageBoxTypes.indexOf(options.type) + if (!(messageBoxType > -1)) { + throw new TypeError('Invalid message box type') + } + if (!Array.isArray(options.buttons)) { + throw new TypeError('Buttons must be an array') + } + if (options.title == null) { + options.title = '' + } else if (typeof options.title !== 'string') { + throw new TypeError('Title must be a string') + } + if (options.message == null) { + options.message = '' + } else if (typeof options.message !== 'string') { + throw new TypeError('Message must be a string') + } + if (options.detail == null) { + options.detail = '' + } else if (typeof options.detail !== 'string') { + throw new TypeError('Detail must be a string') + } + if (options.icon == null) { + options.icon = null + } + if (options.defaultId == null) { + options.defaultId = -1 + } + + // Choose a default button to get selected when dialog is cancelled. + if (options.cancelId == null) { + options.cancelId = 0 + ref2 = options.buttons + for (i = j = 0, len = ref2.length; j < len; i = ++j) { + text = ref2[i] + if ((ref3 = text.toLowerCase()) === 'cancel' || ref3 === 'no') { + options.cancelId = i + break + } + } + } + flags = options.noLink ? messageBoxOptions.noLink : 0 + return binding.showMessageBox(messageBoxType, options.buttons, options.defaultId, options.cancelId, flags, options.title, options.message, options.detail, options.icon, window, callback) + }, + + showErrorBox: function (...args) { + return binding.showErrorBox.apply(binding, args) + } +} + +// Mark standard asynchronous functions. +var ref1 = ['showMessageBox', 'showOpenDialog', 'showSaveDialog'] +var j, len, api +for (j = 0, len = ref1.length; j < len; j++) { + api = ref1[j] + v8Util.setHiddenValue(module.exports[api], 'asynchronous', true) +} diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js new file mode 100644 index 00000000000..bd8285401c7 --- /dev/null +++ b/lib/browser/api/exports/electron.js @@ -0,0 +1,110 @@ +const common = require('../../../common/api/exports/electron') + +// 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') + } + }, + Tray: { + enumerable: true, + get: function () { + return require('../tray') + } + }, + + // The internal modules, invisible unless you know their names. + NavigationController: { + get: function () { + return require('../navigation-controller') + } + }, + webContents: { + get: function () { + return require('../web-contents') + } + } +}) diff --git a/atom/browser/api/lib/global-shortcut.js b/lib/browser/api/global-shortcut.js similarity index 51% rename from atom/browser/api/lib/global-shortcut.js rename to lib/browser/api/global-shortcut.js index daca6c23279..a8fa0e241fe 100644 --- a/atom/browser/api/lib/global-shortcut.js +++ b/lib/browser/api/global-shortcut.js @@ -1,5 +1,5 @@ -var globalShortcut; +var globalShortcut -globalShortcut = process.atomBinding('global_shortcut').globalShortcut; +globalShortcut = process.atomBinding('global_shortcut').globalShortcut -module.exports = globalShortcut; +module.exports = globalShortcut diff --git a/lib/browser/api/ipc-main.js b/lib/browser/api/ipc-main.js new file mode 100644 index 00000000000..62e1404731f --- /dev/null +++ b/lib/browser/api/ipc-main.js @@ -0,0 +1,3 @@ +const EventEmitter = require('events').EventEmitter + +module.exports = new EventEmitter() diff --git a/lib/browser/api/ipc.js b/lib/browser/api/ipc.js new file mode 100644 index 00000000000..cdba49c8248 --- /dev/null +++ b/lib/browser/api/ipc.js @@ -0,0 +1,7 @@ +const deprecate = require('electron').deprecate +const ipcMain = require('electron').ipcMain + +// This module is deprecated, we mirror everything from ipcMain. +deprecate.warn('ipc module', 'require("electron").ipcMain') + +module.exports = ipcMain diff --git a/lib/browser/api/menu-item.js b/lib/browser/api/menu-item.js new file mode 100644 index 00000000000..20666b0f36e --- /dev/null +++ b/lib/browser/api/menu-item.js @@ -0,0 +1,112 @@ +'use strict' + +var MenuItem, methodInBrowserWindow, nextCommandId, rolesMap + +nextCommandId = 0 + +// Maps role to methods of webContents +rolesMap = { + undo: 'undo', + redo: 'redo', + cut: 'cut', + copy: 'copy', + paste: 'paste', + selectall: 'selectAll', + minimize: 'minimize', + close: 'close', + delete: 'delete' +} + +// Maps methods that should be called directly on the BrowserWindow instance +methodInBrowserWindow = { + minimize: true, + close: true +} + +MenuItem = (function () { + MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'] + + function MenuItem (options) { + var click, ref + const Menu = require('electron').Menu + click = options.click + this.selector = options.selector + this.type = options.type + this.role = options.role + this.label = options.label + this.sublabel = options.sublabel + this.accelerator = options.accelerator + this.icon = options.icon + this.enabled = options.enabled + this.visible = options.visible + this.checked = options.checked + this.submenu = options.submenu + if ((this.submenu != null) && this.submenu.constructor !== Menu) { + this.submenu = Menu.buildFromTemplate(this.submenu) + } + if ((this.type == null) && (this.submenu != null)) { + this.type = 'submenu' + } + if (this.type === 'submenu' && ((ref = this.submenu) != null ? ref.constructor : void 0) !== Menu) { + throw new Error('Invalid submenu') + } + this.overrideReadOnlyProperty('type', 'normal') + this.overrideReadOnlyProperty('role') + this.overrideReadOnlyProperty('accelerator') + this.overrideReadOnlyProperty('icon') + this.overrideReadOnlyProperty('submenu') + this.overrideProperty('label', '') + this.overrideProperty('sublabel', '') + this.overrideProperty('enabled', true) + this.overrideProperty('visible', true) + this.overrideProperty('checked', false) + if (MenuItem.types.indexOf(this.type) === -1) { + throw new Error('Unknown menu type ' + this.type) + } + this.commandId = ++nextCommandId + this.click = (focusedWindow) => { + // Manually flip the checked flags when clicked. + var methodName, ref1, ref2 + if ((ref1 = this.type) === 'checkbox' || ref1 === 'radio') { + this.checked = !this.checked + } + if (this.role && rolesMap[this.role] && process.platform !== 'darwin' && (focusedWindow != null)) { + methodName = rolesMap[this.role] + if (methodInBrowserWindow[methodName]) { + return focusedWindow[methodName]() + } else { + return (ref2 = focusedWindow.webContents) != null ? ref2[methodName]() : void 0 + } + } else if (typeof click === 'function') { + return click(this, focusedWindow) + } else if (typeof this.selector === 'string' && process.platform === 'darwin') { + return Menu.sendActionToFirstResponder(this.selector) + } + } + } + + MenuItem.prototype.overrideProperty = function (name, defaultValue) { + if (defaultValue == null) { + defaultValue = null + } + return this[name] != null ? this[name] : this[name] = defaultValue + } + + MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) { + if (defaultValue == null) { + defaultValue = null + } + if (this[name] == null) { + this[name] = defaultValue + } + return Object.defineProperty(this, name, { + enumerable: true, + writable: false, + value: this[name] + }) + } + + return MenuItem +})() + +module.exports = MenuItem diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js new file mode 100644 index 00000000000..e05637e79e7 --- /dev/null +++ b/lib/browser/api/menu.js @@ -0,0 +1,305 @@ +'use strict' + +const BrowserWindow = require('electron').BrowserWindow +const MenuItem = require('electron').MenuItem +const EventEmitter = require('events').EventEmitter +const v8Util = process.atomBinding('v8_util') +const bindings = process.atomBinding('menu') + +// Automatically generated radio menu item's group id. +var nextGroupId = 0 + +// Search between separators to find a radio menu item and return its group id, +// otherwise generate a group id. +var generateGroupId = function (items, pos) { + var i, item, j, k, ref1, ref2, ref3 + if (pos > 0) { + for (i = j = ref1 = pos - 1; ref1 <= 0 ? j <= 0 : j >= 0; i = ref1 <= 0 ? ++j : --j) { + item = items[i] + if (item.type === 'radio') { + return item.groupId + } + if (item.type === 'separator') { + break + } + } + } else if (pos < items.length) { + for (i = k = ref2 = pos, ref3 = items.length - 1; ref2 <= ref3 ? k <= ref3 : k >= ref3; i = ref2 <= ref3 ? ++k : --k) { + item = items[i] + if (item.type === 'radio') { + return item.groupId + } + if (item.type === 'separator') { + break + } + } + } + return ++nextGroupId +} + +// Returns the index of item according to |id|. +var indexOfItemById = function (items, id) { + var i, item, j, len + for (i = j = 0, len = items.length; j < len; i = ++j) { + item = items[i] + if (item.id === id) { + return i + } + } + return -1 +} + +// Returns the index of where to insert the item according to |position|. +var indexToInsertByPosition = function (items, position) { + var insertIndex + if (!position) { + return items.length + } + const [query, id] = position.split('=') + insertIndex = indexOfItemById(items, id) + if (insertIndex === -1 && query !== 'endof') { + console.warn("Item with id '" + id + "' is not found") + return items.length + } + switch (query) { + case 'after': + insertIndex++ + break + case 'endof': + + // If the |id| doesn't exist, then create a new group with the |id|. + if (insertIndex === -1) { + items.push({ + id: id, + type: 'separator' + }) + insertIndex = items.length - 1 + } + + // Find the end of the group. + insertIndex++ + while (insertIndex < items.length && items[insertIndex].type !== 'separator') { + insertIndex++ + } + } + return insertIndex +} + +const Menu = bindings.Menu + +Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype) + +Menu.prototype._init = function () { + this.commandsMap = {} + this.groupsMap = {} + this.items = [] + this.delegate = { + isCommandIdChecked: (commandId) => { + var command = this.commandsMap[commandId] + return command != null ? command.checked : undefined + }, + isCommandIdEnabled: (commandId) => { + var command = this.commandsMap[commandId] + return command != null ? command.enabled : undefined + }, + isCommandIdVisible: (commandId) => { + var command = this.commandsMap[commandId] + return command != null ? command.visible : undefined + }, + getAcceleratorForCommandId: (commandId) => { + var command = this.commandsMap[commandId] + return command != null ? command.accelerator : undefined + }, + getIconForCommandId: (commandId) => { + var command = this.commandsMap[commandId] + return command != null ? command.icon : undefined + }, + executeCommand: (commandId) => { + var command = this.commandsMap[commandId] + return command != null ? command.click(BrowserWindow.getFocusedWindow()) : undefined + }, + menuWillShow: () => { + // Make sure radio groups have at least one menu item seleted. + var checked, group, id, j, len, radioItem, ref1 + ref1 = this.groupsMap + for (id in ref1) { + group = ref1[id] + checked = false + for (j = 0, len = group.length; j < len; j++) { + radioItem = group[j] + if (!radioItem.checked) { + continue + } + checked = true + break + } + if (!checked) { + v8Util.setHiddenValue(group[0], 'checked', true) + } + } + } + } +} + +Menu.prototype.popup = function (window, x, y, positioningItem) { + if (typeof window !== 'object' || window.constructor !== BrowserWindow) { + // Shift. + positioningItem = y + y = x + x = window + window = BrowserWindow.getFocusedWindow() + } + + // Default parameters. + if (typeof x !== 'number') x = -1 + if (typeof y !== 'number') y = -1 + if (typeof positioningItem !== 'number') positioningItem = 0 + + this.popupAt(window, x, y, positioningItem) +} + +Menu.prototype.append = function (item) { + return this.insert(this.getItemCount(), item) +} + +Menu.prototype.insert = function (pos, item) { + var base, name + if ((item != null ? item.constructor : void 0) !== MenuItem) { + throw new TypeError('Invalid item') + } + switch (item.type) { + case 'normal': + this.insertItem(pos, item.commandId, item.label) + break + case 'checkbox': + this.insertCheckItem(pos, item.commandId, item.label) + break + case 'separator': + this.insertSeparator(pos) + break + case 'submenu': + this.insertSubMenu(pos, item.commandId, item.label, item.submenu) + break + case 'radio': + // Grouping radio menu items. + item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos)) + if ((base = this.groupsMap)[name = item.groupId] == null) { + base[name] = [] + } + this.groupsMap[item.groupId].push(item) + + // Setting a radio menu item should flip other items in the group. + v8Util.setHiddenValue(item, 'checked', item.checked) + Object.defineProperty(item, 'checked', { + enumerable: true, + get: function () { + return v8Util.getHiddenValue(item, 'checked') + }, + set: () => { + var j, len, otherItem, ref1 + ref1 = this.groupsMap[item.groupId] + for (j = 0, len = ref1.length; j < len; j++) { + otherItem = ref1[j] + if (otherItem !== item) { + v8Util.setHiddenValue(otherItem, 'checked', false) + } + } + return v8Util.setHiddenValue(item, 'checked', true) + } + }) + this.insertRadioItem(pos, item.commandId, item.label, item.groupId) + } + if (item.sublabel != null) { + this.setSublabel(pos, item.sublabel) + } + if (item.icon != null) { + this.setIcon(pos, item.icon) + } + if (item.role != null) { + this.setRole(pos, item.role) + } + + // Make menu accessable to items. + item.overrideReadOnlyProperty('menu', this) + + // Remember the items. + this.items.splice(pos, 0, item) + this.commandsMap[item.commandId] = item +} + +// Force menuWillShow to be called +Menu.prototype._callMenuWillShow = function () { + if (this.delegate != null) { + this.delegate.menuWillShow() + } + this.items.forEach(function (item) { + if (item.submenu != null) { + item.submenu._callMenuWillShow() + } + }) +} + +var applicationMenu = null + +Menu.setApplicationMenu = function (menu) { + if (!(menu === null || menu.constructor === Menu)) { + throw new TypeError('Invalid menu') + } + + // Keep a reference. + applicationMenu = menu + if (process.platform === 'darwin') { + if (menu === null) { + return + } + menu._callMenuWillShow() + bindings.setApplicationMenu(menu) + } else { + BrowserWindow.getAllWindows().forEach(function (window) { + window.setMenu(menu) + }) + } +} + +Menu.getApplicationMenu = function () { + return applicationMenu +} + +Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder + +Menu.buildFromTemplate = function (template) { + var insertIndex, item, j, k, key, len, len1, menu, menuItem, positionedTemplate + if (!Array.isArray(template)) { + throw new TypeError('Invalid template for Menu') + } + positionedTemplate = [] + insertIndex = 0 + for (j = 0, len = template.length; j < len; j++) { + item = template[j] + if (item.position) { + insertIndex = indexToInsertByPosition(positionedTemplate, item.position) + } else { + // If no |position| is specified, insert after last item. + insertIndex++ + } + positionedTemplate.splice(insertIndex, 0, item) + } + menu = new Menu() + for (k = 0, len1 = positionedTemplate.length; k < len1; k++) { + item = positionedTemplate[k] + if (typeof item !== 'object') { + throw new TypeError('Invalid template for MenuItem') + } + menuItem = new MenuItem(item) + for (key in item) { + // Preserve extra fields specified by user + if (!menuItem.hasOwnProperty(key)) { + menuItem[key] = item[key] + } + } + menu.append(menuItem) + } + return menu +} + +module.exports = Menu diff --git a/lib/browser/api/navigation-controller.js b/lib/browser/api/navigation-controller.js new file mode 100644 index 00000000000..d9dedc91e34 --- /dev/null +++ b/lib/browser/api/navigation-controller.js @@ -0,0 +1,178 @@ +'use strict' + +const ipcMain = require('electron').ipcMain + +// The history operation in renderer is redirected to browser. +ipcMain.on('ATOM_SHELL_NAVIGATION_CONTROLLER', function (event, method, ...args) { + var ref + (ref = event.sender)[method].apply(ref, args) +}) + +ipcMain.on('ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', function (event, method, ...args) { + var ref + event.returnValue = (ref = event.sender)[method].apply(ref, args) +}) + +// JavaScript implementation of Chromium's NavigationController. +// Instead of relying on Chromium for history control, we compeletely do history +// control on user land, and only rely on WebContents.loadURL for navigation. +// This helps us avoid Chromium's various optimizations so we can ensure renderer +// process is restarted everytime. +var NavigationController = (function () { + function NavigationController (webContents) { + this.webContents = webContents + this.clearHistory() + + // webContents may have already navigated to a page. + if (this.webContents._getURL()) { + this.currentIndex++ + this.history.push(this.webContents._getURL()) + } + this.webContents.on('navigation-entry-commited', (event, url, inPage, replaceEntry) => { + var currentEntry + if (this.inPageIndex > -1 && !inPage) { + // Navigated to a new page, clear in-page mark. + this.inPageIndex = -1 + } else if (this.inPageIndex === -1 && inPage) { + // Started in-page navigations. + this.inPageIndex = this.currentIndex + } + if (this.pendingIndex >= 0) { + // Go to index. + this.currentIndex = this.pendingIndex + this.pendingIndex = -1 + this.history[this.currentIndex] = url + } else if (replaceEntry) { + // Non-user initialized navigation. + this.history[this.currentIndex] = url + } else { + // Normal navigation. Clear history. + this.history = this.history.slice(0, this.currentIndex + 1) + currentEntry = this.history[this.currentIndex] + if ((currentEntry != null ? currentEntry.url : void 0) !== url) { + this.currentIndex++ + return this.history.push(url) + } + } + }) + } + + NavigationController.prototype.loadURL = function (url, options) { + if (options == null) { + options = {} + } + this.pendingIndex = -1 + this.webContents._loadURL(url, options) + return this.webContents.emit('load-url', url, options) + } + + NavigationController.prototype.getURL = function () { + if (this.currentIndex === -1) { + return '' + } else { + return this.history[this.currentIndex] + } + } + + NavigationController.prototype.stop = function () { + this.pendingIndex = -1 + return this.webContents._stop() + } + + NavigationController.prototype.reload = function () { + this.pendingIndex = this.currentIndex + return this.webContents._loadURL(this.getURL(), {}) + } + + NavigationController.prototype.reloadIgnoringCache = function () { + this.pendingIndex = this.currentIndex + return this.webContents._loadURL(this.getURL(), { + extraHeaders: 'pragma: no-cache\n' + }) + } + + NavigationController.prototype.canGoBack = function () { + return this.getActiveIndex() > 0 + } + + NavigationController.prototype.canGoForward = function () { + return this.getActiveIndex() < this.history.length - 1 + } + + NavigationController.prototype.canGoToIndex = function (index) { + return index >= 0 && index < this.history.length + } + + NavigationController.prototype.canGoToOffset = function (offset) { + return this.canGoToIndex(this.currentIndex + offset) + } + + NavigationController.prototype.clearHistory = function () { + this.history = [] + this.currentIndex = -1 + this.pendingIndex = -1 + this.inPageIndex = -1 + } + + NavigationController.prototype.goBack = function () { + if (!this.canGoBack()) { + return + } + this.pendingIndex = this.getActiveIndex() - 1 + if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { + return this.webContents._goBack() + } else { + return this.webContents._loadURL(this.history[this.pendingIndex], {}) + } + } + + NavigationController.prototype.goForward = function () { + if (!this.canGoForward()) { + return + } + this.pendingIndex = this.getActiveIndex() + 1 + if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { + return this.webContents._goForward() + } else { + return this.webContents._loadURL(this.history[this.pendingIndex], {}) + } + } + + NavigationController.prototype.goToIndex = function (index) { + if (!this.canGoToIndex(index)) { + return + } + this.pendingIndex = index + return this.webContents._loadURL(this.history[this.pendingIndex], {}) + } + + NavigationController.prototype.goToOffset = function (offset) { + var pendingIndex + if (!this.canGoToOffset(offset)) { + return + } + pendingIndex = this.currentIndex + offset + if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) { + this.pendingIndex = pendingIndex + return this.webContents._goToOffset(offset) + } else { + return this.goToIndex(pendingIndex) + } + } + + NavigationController.prototype.getActiveIndex = function () { + if (this.pendingIndex === -1) { + return this.currentIndex + } else { + return this.pendingIndex + } + } + + NavigationController.prototype.length = function () { + return this.history.length + } + + return NavigationController +})() + +module.exports = NavigationController diff --git a/lib/browser/api/power-monitor.js b/lib/browser/api/power-monitor.js new file mode 100644 index 00000000000..02afeb5ba11 --- /dev/null +++ b/lib/browser/api/power-monitor.js @@ -0,0 +1,6 @@ +const EventEmitter = require('events').EventEmitter +const powerMonitor = process.atomBinding('power_monitor').powerMonitor + +Object.setPrototypeOf(powerMonitor, EventEmitter.prototype) + +module.exports = powerMonitor diff --git a/lib/browser/api/power-save-blocker.js b/lib/browser/api/power-save-blocker.js new file mode 100644 index 00000000000..bbcc7e2cdab --- /dev/null +++ b/lib/browser/api/power-save-blocker.js @@ -0,0 +1,5 @@ +var powerSaveBlocker + +powerSaveBlocker = process.atomBinding('power_save_blocker').powerSaveBlocker + +module.exports = powerSaveBlocker diff --git a/lib/browser/api/protocol.js b/lib/browser/api/protocol.js new file mode 100644 index 00000000000..dac679f43e4 --- /dev/null +++ b/lib/browser/api/protocol.js @@ -0,0 +1,31 @@ +const app = require('electron').app + +if (!app.isReady()) { + throw new Error('Can not initialize protocol module before app is ready') +} + +const protocol = process.atomBinding('protocol').protocol + +// Warn about removed APIs. +var logAndThrow = function (callback, message) { + console.error(message) + if (callback) { + return callback(new Error(message)) + } else { + throw new Error(message) + } +} + +protocol.registerProtocol = function (scheme, handler, callback) { + return logAndThrow(callback, 'registerProtocol API has been replaced by the register[File/Http/Buffer/String]Protocol API family, please switch to the new APIs.') +} + +protocol.isHandledProtocol = function (scheme, callback) { + return logAndThrow(callback, 'isHandledProtocol API has been replaced by isProtocolHandled.') +} + +protocol.interceptProtocol = function (scheme, handler, callback) { + return logAndThrow(callback, 'interceptProtocol API has been replaced by the intercept[File/Http/Buffer/String]Protocol API family, please switch to the new APIs.') +} + +module.exports = protocol diff --git a/lib/browser/api/screen.js b/lib/browser/api/screen.js new file mode 100644 index 00000000000..9326ace2c4f --- /dev/null +++ b/lib/browser/api/screen.js @@ -0,0 +1,6 @@ +const EventEmitter = require('events').EventEmitter +const screen = process.atomBinding('screen').screen + +Object.setPrototypeOf(screen, EventEmitter.prototype) + +module.exports = screen diff --git a/lib/browser/api/session.js b/lib/browser/api/session.js new file mode 100644 index 00000000000..47e00bd0462 --- /dev/null +++ b/lib/browser/api/session.js @@ -0,0 +1,33 @@ +const EventEmitter = require('events').EventEmitter +const bindings = process.atomBinding('session') +const PERSIST_PREFIX = 'persist:' + +// Returns the Session from |partition| string. +exports.fromPartition = function (partition) { + if (partition == null) { + partition = '' + } + if (partition === '') { + return exports.defaultSession + } + if (partition.startsWith(PERSIST_PREFIX)) { + return bindings.fromPartition(partition.substr(PERSIST_PREFIX.length), false) + } else { + return bindings.fromPartition(partition, true) + } +} + +// Returns the default session. +Object.defineProperty(exports, 'defaultSession', { + enumerable: true, + get: function () { + return bindings.fromPartition('', false) + } +}) + +var wrapSession = function (session) { + // session is an EventEmitter. + Object.setPrototypeOf(session, EventEmitter.prototype) +} + +bindings._setWrapSession(wrapSession) diff --git a/lib/browser/api/tray.js b/lib/browser/api/tray.js new file mode 100644 index 00000000000..ce67dbe117a --- /dev/null +++ b/lib/browser/api/tray.js @@ -0,0 +1,23 @@ +const deprecate = require('electron').deprecate +const EventEmitter = require('events').EventEmitter +const Tray = process.atomBinding('tray').Tray + +Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype) + +Tray.prototype._init = function () { + // Deprecated. + deprecate.rename(this, 'popContextMenu', 'popUpContextMenu') + deprecate.event(this, 'clicked', 'click') + deprecate.event(this, 'double-clicked', 'double-click') + deprecate.event(this, 'right-clicked', 'right-click') + return deprecate.event(this, 'balloon-clicked', 'balloon-click') +} + +Tray.prototype.setContextMenu = function (menu) { + this._setContextMenu(menu) + + // Keep a strong reference of menu. + this.menu = menu +} + +module.exports = Tray diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js new file mode 100644 index 00000000000..2ec7c14bae1 --- /dev/null +++ b/lib/browser/api/web-contents.js @@ -0,0 +1,231 @@ +'use strict' + +const EventEmitter = require('events').EventEmitter +const deprecate = require('electron').deprecate +const ipcMain = require('electron').ipcMain +const NavigationController = require('electron').NavigationController +const Menu = require('electron').Menu + +const binding = process.atomBinding('web_contents') +const debuggerBinding = process.atomBinding('debugger') + +let nextId = 0 + +let getNextId = function () { + return ++nextId +} + +let PDFPageSize = { + A5: { + custom_display_name: 'A5', + height_microns: 210000, + name: 'ISO_A5', + width_microns: 148000 + }, + A4: { + custom_display_name: 'A4', + height_microns: 297000, + name: 'ISO_A4', + is_default: 'true', + width_microns: 210000 + }, + A3: { + custom_display_name: 'A3', + height_microns: 420000, + name: 'ISO_A3', + width_microns: 297000 + }, + Legal: { + custom_display_name: 'Legal', + height_microns: 355600, + name: 'NA_LEGAL', + width_microns: 215900 + }, + Letter: { + custom_display_name: 'Letter', + height_microns: 279400, + name: 'NA_LETTER', + width_microns: 215900 + }, + Tabloid: { + height_microns: 431800, + name: 'NA_LEDGER', + width_microns: 279400, + custom_display_name: 'Tabloid' + } +} + +// Following methods are mapped to webFrame. +const webFrameMethods = [ + 'insertText', + 'setZoomFactor', + 'setZoomLevel', + 'setZoomLevelLimits' +] + +let wrapWebContents = function (webContents) { + // webContents is an EventEmitter. + var controller, method, name, ref1 + Object.setPrototypeOf(webContents, EventEmitter.prototype) + + // Every remote callback from renderer process would add a listenter to the + // render-view-deleted event, so ignore the listenters warning. + webContents.setMaxListeners(0) + + // WebContents::send(channel, args..) + webContents.send = function (channel, ...args) { + if (channel == null) { + throw new Error('Missing required channel argument') + } + return this._send(channel, args) + } + + // The navigation controller. + controller = new NavigationController(webContents) + ref1 = NavigationController.prototype + for (name in ref1) { + method = ref1[name] + if (method instanceof Function) { + (function (name, method) { + webContents[name] = function () { + return method.apply(controller, arguments) + } + })(name, method) + } + } + + // Mapping webFrame methods. + for (let method of webFrameMethods) { + webContents[method] = function (...args) { + this.send('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, args) + } + } + + const asyncWebFrameMethods = function (requestId, method, callback, ...args) { + this.send('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', requestId, method, args) + ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, function (event, result) { + if (callback) callback(result) + }) + } + + // Make sure webContents.executeJavaScript would run the code only when the + // webContents has been loaded. + webContents.executeJavaScript = function (code, hasUserGesture, callback) { + let requestId = getNextId() + if (typeof hasUserGesture === 'function') { + callback = hasUserGesture + hasUserGesture = false + } + if (this.getURL() && !this.isLoading()) { + return asyncWebFrameMethods.call(this, requestId, 'executeJavaScript', callback, code, hasUserGesture) + } else { + return this.once('did-finish-load', asyncWebFrameMethods.bind(this, requestId, 'executeJavaScript', callback, code, hasUserGesture)) + } + } + + // Dispatch IPC messages to the ipc module. + webContents.on('ipc-message', function (event, [channel, ...args]) { + return ipcMain.emit.apply(ipcMain, [channel, event].concat(args)) + }) + webContents.on('ipc-message-sync', function (event, [channel, ...args]) { + Object.defineProperty(event, 'returnValue', { + set: function (value) { + return event.sendReply(JSON.stringify(value)) + }, + get: function () { + return undefined + } + }) + return ipcMain.emit.apply(ipcMain, [channel, event].concat(args)) + }) + + // Handle context menu action request from pepper plugin. + webContents.on('pepper-context-menu', function (event, params) { + var menu + menu = Menu.buildFromTemplate(params.menu) + return menu.popup(params.x, params.y) + }) + + // This error occurs when host could not be found. + webContents.on('did-fail-provisional-load', function (...args) { + // Calling loadURL during this event might cause crash, so delay the event + // until next tick. + setImmediate(() => { + this.emit.apply(this, ['did-fail-load'].concat(args)) + }) + }) + + // Delays the page-title-updated event to next tick. + webContents.on('-page-title-updated', function (...args) { + setImmediate(() => { + this.emit.apply(this, ['page-title-updated'].concat(args)) + }) + }) + + // Deprecated. + deprecate.rename(webContents, 'loadUrl', 'loadURL') + deprecate.rename(webContents, 'getUrl', 'getURL') + deprecate.event(webContents, 'page-title-set', 'page-title-updated', function (...args) { + return this.emit.apply(this, ['page-title-set'].concat(args)) + }) + webContents.printToPDF = function (options, callback) { + var printingSetting + printingSetting = { + pageRage: [], + mediaSize: {}, + landscape: false, + color: 2, + headerFooterEnabled: false, + marginsType: 0, + isFirstRequest: false, + requestID: getNextId(), + previewModifiable: true, + printToPDF: true, + printWithCloudPrint: false, + printWithPrivet: false, + printWithExtension: false, + deviceName: 'Save as PDF', + generateDraftData: true, + fitToPageEnabled: false, + duplex: 0, + copies: 1, + collate: true, + shouldPrintBackgrounds: false, + shouldPrintSelectionOnly: false + } + if (options.landscape) { + printingSetting.landscape = options.landscape + } + if (options.marginsType) { + printingSetting.marginsType = options.marginsType + } + if (options.printSelectionOnly) { + printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly + } + if (options.printBackground) { + printingSetting.shouldPrintBackgrounds = options.printBackground + } + if (options.pageSize && PDFPageSize[options.pageSize]) { + printingSetting.mediaSize = PDFPageSize[options.pageSize] + } else { + printingSetting.mediaSize = PDFPageSize['A4'] + } + return this._printToPDF(printingSetting, callback) + } +} + +// Wrapper for native class. +let wrapDebugger = function (webContentsDebugger) { + // debugger is an EventEmitter. + Object.setPrototypeOf(webContentsDebugger, EventEmitter.prototype) +} + +binding._setWrapWebContents(wrapWebContents) +debuggerBinding._setWrapDebugger(wrapDebugger) + +module.exports.create = function (options) { + if (options == null) { + options = {} + } + return binding.create(options) +} diff --git a/lib/browser/chrome-extension.js b/lib/browser/chrome-extension.js new file mode 100644 index 00000000000..562e77a1150 --- /dev/null +++ b/lib/browser/chrome-extension.js @@ -0,0 +1,145 @@ +const electron = require('electron') +const app = electron.app +const fs = require('fs') +const path = require('path') +const url = require('url') + +// Mapping between hostname and file path. +var hostPathMap = {} +var hostPathMapNextKey = 0 + +var getHostForPath = function (path) { + var key + key = 'extension-' + (++hostPathMapNextKey) + hostPathMap[key] = path + return key +} + +var getPathForHost = function (host) { + return hostPathMap[host] +} + +// Cache extensionInfo. +var extensionInfoMap = {} + +var getExtensionInfoFromPath = function (srcDirectory) { + var manifest, page + manifest = JSON.parse(fs.readFileSync(path.join(srcDirectory, 'manifest.json'))) + if (extensionInfoMap[manifest.name] == null) { + // We can not use 'file://' directly because all resources in the extension + // will be treated as relative to the root in Chrome. + page = url.format({ + protocol: 'chrome-extension', + slashes: true, + hostname: getHostForPath(srcDirectory), + pathname: manifest.devtools_page + }) + extensionInfoMap[manifest.name] = { + startPage: page, + name: manifest.name, + srcDirectory: srcDirectory, + exposeExperimentalAPIs: true + } + return extensionInfoMap[manifest.name] + } +} + +// The loaded extensions cache and its persistent path. +var loadedExtensions = null +var loadedExtensionsPath = null + +app.on('will-quit', function () { + try { + loadedExtensions = Object.keys(extensionInfoMap).map(function (key) { + return extensionInfoMap[key].srcDirectory + }) + if (loadedExtensions.length > 0) { + try { + fs.mkdirSync(path.dirname(loadedExtensionsPath)) + } catch (error) { + // Ignore error + } + fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions)) + } else { + fs.unlinkSync(loadedExtensionsPath) + } + } catch (error) { + // Ignore error + } +}) + +// We can not use protocol or BrowserWindow until app is ready. +app.once('ready', function () { + var BrowserWindow, chromeExtensionHandler, i, init, len, protocol, srcDirectory + protocol = electron.protocol + BrowserWindow = electron.BrowserWindow + + // Load persisted extensions. + loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions') + try { + loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath)) + if (!Array.isArray(loadedExtensions)) { + loadedExtensions = [] + } + + // Preheat the extensionInfo cache. + for (i = 0, len = loadedExtensions.length; i < len; i++) { + srcDirectory = loadedExtensions[i] + getExtensionInfoFromPath(srcDirectory) + } + } catch (error) { + // Ignore error + } + + // The chrome-extension: can map a extension URL request to real file path. + chromeExtensionHandler = function (request, callback) { + var directory, parsed + parsed = url.parse(request.url) + if (!(parsed.hostname && (parsed.path != null))) { + return callback() + } + if (!/extension-\d+/.test(parsed.hostname)) { + return callback() + } + directory = getPathForHost(parsed.hostname) + if (directory == null) { + return callback() + } + return callback(path.join(directory, parsed.path)) + } + protocol.registerFileProtocol('chrome-extension', chromeExtensionHandler, function (error) { + if (error) { + return console.error('Unable to register chrome-extension protocol') + } + }) + BrowserWindow.prototype._loadDevToolsExtensions = function (extensionInfoArray) { + var ref + return (ref = this.devToolsWebContents) != null ? ref.executeJavaScript('DevToolsAPI.addExtensions(' + (JSON.stringify(extensionInfoArray)) + ');') : void 0 + } + BrowserWindow.addDevToolsExtension = function (srcDirectory) { + var extensionInfo, j, len1, ref, window + extensionInfo = getExtensionInfoFromPath(srcDirectory) + if (extensionInfo) { + ref = BrowserWindow.getAllWindows() + for (j = 0, len1 = ref.length; j < len1; j++) { + window = ref[j] + window._loadDevToolsExtensions([extensionInfo]) + } + return extensionInfo.name + } + } + BrowserWindow.removeDevToolsExtension = function (name) { + return delete extensionInfoMap[name] + } + + // Load persisted extensions when devtools is opened. + init = BrowserWindow.prototype._init + BrowserWindow.prototype._init = function () { + init.call(this) + return this.on('devtools-opened', function () { + return this._loadDevToolsExtensions(Object.keys(extensionInfoMap).map(function (key) { + return extensionInfoMap[key] + })) + }) + } +}) diff --git a/atom/browser/lib/desktop-capturer.js b/lib/browser/desktop-capturer.js similarity index 53% rename from atom/browser/lib/desktop-capturer.js rename to lib/browser/desktop-capturer.js index a4154f0c5b1..5ba64fcddb1 100644 --- a/atom/browser/lib/desktop-capturer.js +++ b/lib/browser/desktop-capturer.js @@ -1,15 +1,17 @@ -const ipcMain = require('electron').ipcMain; -const desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer; +'use strict' -var deepEqual = function(opt1, opt2) { - return JSON.stringify(opt1) === JSON.stringify(opt2); -}; +const ipcMain = require('electron').ipcMain +const desktopCapturer = process.atomBinding('desktop_capturer').desktopCapturer + +var deepEqual = function (opt1, opt2) { + return JSON.stringify(opt1) === JSON.stringify(opt2) +} // A queue for holding all requests from renderer process. -var requestsQueue = []; +var requestsQueue = [] -ipcMain.on('ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function(event, captureWindow, captureScreen, thumbnailSize, id) { - var request; +ipcMain.on('ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, captureWindow, captureScreen, thumbnailSize, id) { + var request request = { id: id, options: { @@ -18,58 +20,58 @@ ipcMain.on('ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function(event, captureW thumbnailSize: thumbnailSize }, webContents: event.sender - }; - requestsQueue.push(request); + } + requestsQueue.push(request) if (requestsQueue.length === 1) { - desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize); + desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize) } // If the WebContents is destroyed before receiving result, just remove the // reference from requestsQueue to make the module not send the result to it. - return event.sender.once('destroyed', function() { - return request.webContents = null; - }); -}); + return event.sender.once('destroyed', function () { + request.webContents = null + }) +}) -desktopCapturer.emit = function(event, name, sources) { +desktopCapturer.emit = function (event, name, sources) { // Receiving sources result from main process, now send them back to renderer. - var captureScreen, captureWindow, handledRequest, i, len, ref, ref1, ref2, request, result, source, thumbnailSize, unhandledRequestsQueue; - handledRequest = requestsQueue.shift(0); - result = (function() { - var i, len, results; - results = []; + var handledRequest, i, len, ref, ref1, request, result, source, unhandledRequestsQueue + handledRequest = requestsQueue.shift(0) + result = (function () { + var i, len, results + results = [] for (i = 0, len = sources.length; i < len; i++) { - source = sources[i]; + source = sources[i] results.push({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataUrl() - }); + }) } - return results; - })(); + return results + })() if ((ref = handledRequest.webContents) != null) { - ref.send("ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_" + handledRequest.id, result); + ref.send('ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_' + handledRequest.id, result) } // Check the queue to see whether there is other same request. If has, handle // it for reducing redunplicated `desktopCaptuer.startHandling` calls. - unhandledRequestsQueue = []; + unhandledRequestsQueue = [] for (i = 0, len = requestsQueue.length; i < len; i++) { - request = requestsQueue[i]; + request = requestsQueue[i] if (deepEqual(handledRequest.options, request.options)) { if ((ref1 = request.webContents) != null) { - ref1.send("ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_" + request.id, result); + ref1.send('ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_' + request.id, result) } } else { - unhandledRequestsQueue.push(request); + unhandledRequestsQueue.push(request) } } - requestsQueue = unhandledRequestsQueue; + requestsQueue = unhandledRequestsQueue // If the requestsQueue is not empty, start a new request handling. if (requestsQueue.length > 0) { - ref2 = requestsQueue[0].options, captureWindow = ref2.captureWindow, captureScreen = ref2.captureScreen, thumbnailSize = ref2.thumbnailSize; - return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize); + const {captureWindow, captureScreen, thumbnailSize} = requestsQueue[0].options + return desktopCapturer.startHandling(captureWindow, captureScreen, thumbnailSize) } -}; +} diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js new file mode 100644 index 00000000000..ee633cf2901 --- /dev/null +++ b/lib/browser/guest-view-manager.js @@ -0,0 +1,234 @@ +'use strict' + +const ipcMain = require('electron').ipcMain +const webContents = require('electron').webContents + +// Doesn't exist in early initialization. +var webViewManager = null + +var supportedWebViewEvents = [ + 'load-commit', + 'did-finish-load', + 'did-fail-load', + 'did-frame-finish-load', + 'did-start-loading', + 'did-stop-loading', + 'did-get-response-details', + 'did-get-redirect-request', + 'dom-ready', + 'console-message', + 'devtools-opened', + 'devtools-closed', + 'devtools-focused', + 'new-window', + 'will-navigate', + 'did-navigate', + 'did-navigate-in-page', + 'close', + 'crashed', + 'gpu-crashed', + 'plugin-crashed', + 'destroyed', + 'page-title-updated', + 'page-favicon-updated', + 'enter-html-full-screen', + 'leave-html-full-screen', + 'media-started-playing', + 'media-paused', + 'found-in-page', + 'did-change-theme-color' +] + +var nextInstanceId = 0 +var guestInstances = {} +var embedderElementsMap = {} +var reverseEmbedderElementsMap = {} + +// Moves the last element of array to the first one. +var moveLastToFirst = function (list) { + return list.unshift(list.pop()) +} + +// Generate guestInstanceId. +var getNextInstanceId = function () { + return ++nextInstanceId +} + +// Create a new guest instance. +var createGuest = function (embedder, params) { + var destroy, destroyEvents, event, fn, guest, i, id, j, len, len1, listeners + if (webViewManager == null) { + webViewManager = process.atomBinding('web_view_manager') + } + id = getNextInstanceId(embedder) + guest = webContents.create({ + isGuest: true, + partition: params.partition, + embedder: embedder + }) + guestInstances[id] = { + guest: guest, + embedder: embedder + } + + // Destroy guest when the embedder is gone or navigated. + destroyEvents = ['will-destroy', 'crashed', 'did-navigate'] + destroy = function () { + if (guestInstances[id] != null) { + return destroyGuest(embedder, id) + } + } + for (i = 0, len = destroyEvents.length; i < len; i++) { + event = destroyEvents[i] + embedder.once(event, destroy) + + // Users might also listen to the crashed event, so We must ensure the guest + // is destroyed before users' listener gets called. It is done by moving our + // listener to the first one in queue. + listeners = embedder._events[event] + if (Array.isArray(listeners)) { + moveLastToFirst(listeners) + } + } + guest.once('destroyed', function () { + var j, len1, results + results = [] + for (j = 0, len1 = destroyEvents.length; j < len1; j++) { + event = destroyEvents[j] + results.push(embedder.removeListener(event, destroy)) + } + return results + }) + + // Init guest web view after attached. + guest.once('did-attach', function () { + var opts + params = this.attachParams + delete this.attachParams + this.viewInstanceId = params.instanceId + this.setSize({ + normal: { + width: params.elementWidth, + height: params.elementHeight + }, + enableAutoSize: params.autosize, + min: { + width: params.minwidth, + height: params.minheight + }, + max: { + width: params.maxwidth, + height: params.maxheight + } + }) + if (params.src) { + opts = {} + if (params.httpreferrer) { + opts.httpReferrer = params.httpreferrer + } + if (params.useragent) { + opts.userAgent = params.useragent + } + this.loadURL(params.src, opts) + } + guest.allowPopups = params.allowpopups + }) + + // Dispatch events to embedder. + fn = function (event) { + return guest.on(event, function (_, ...args) { + return embedder.send.apply(embedder, ['ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + guest.viewInstanceId, event].concat(args)) + }) + } + for (j = 0, len1 = supportedWebViewEvents.length; j < len1; j++) { + event = supportedWebViewEvents[j] + fn(event) + } + + // Dispatch guest's IPC messages to embedder. + guest.on('ipc-message-host', function (_, [channel, ...args]) { + return embedder.send.apply(embedder, ['ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + guest.viewInstanceId, channel].concat(args)) + }) + + // Autosize. + guest.on('size-changed', function (_, ...args) { + return embedder.send.apply(embedder, ['ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + guest.viewInstanceId].concat(args)) + }) + return id +} + +// Attach the guest to an element of embedder. +var attachGuest = function (embedder, elementInstanceId, guestInstanceId, params) { + var guest, key, oldGuestInstanceId, ref1, webPreferences + guest = guestInstances[guestInstanceId].guest + + // Destroy the old guest when attaching. + key = (embedder.getId()) + '-' + elementInstanceId + oldGuestInstanceId = embedderElementsMap[key] + if (oldGuestInstanceId != null) { + // Reattachment to the same guest is not currently supported. + if (oldGuestInstanceId === guestInstanceId) { + return + } + if (guestInstances[oldGuestInstanceId] == null) { + return + } + destroyGuest(embedder, oldGuestInstanceId) + } + webPreferences = { + guestInstanceId: guestInstanceId, + nodeIntegration: (ref1 = params.nodeintegration) != null ? ref1 : false, + plugins: params.plugins, + webSecurity: !params.disablewebsecurity, + blinkFeatures: params.blinkfeatures + } + if (params.preload) { + webPreferences.preloadURL = params.preload + } + webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) + guest.attachParams = params + embedderElementsMap[key] = guestInstanceId + reverseEmbedderElementsMap[guestInstanceId] = key +} + +// Destroy an existing guest instance. +var destroyGuest = function (embedder, id) { + var key + webViewManager.removeGuest(embedder, id) + guestInstances[id].guest.destroy() + delete guestInstances[id] + key = reverseEmbedderElementsMap[id] + if (key != null) { + delete reverseEmbedderElementsMap[id] + return delete embedderElementsMap[key] + } +} + +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, requestId) { + return event.sender.send('ATOM_SHELL_RESPONSE_' + requestId, createGuest(event.sender, params)) +}) + +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, elementInstanceId, guestInstanceId, params) { + return attachGuest(event.sender, elementInstanceId, guestInstanceId, params) +}) + +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, id) { + return destroyGuest(event.sender, id) +}) + +ipcMain.on('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', function (event, id, params) { + var ref1 + return (ref1 = guestInstances[id]) != null ? ref1.guest.setSize(params) : void 0 +}) + +// Returns WebContents from its guest id. +exports.getGuest = function (id) { + var ref1 + return (ref1 = guestInstances[id]) != null ? ref1.guest : void 0 +} + +// Returns the embedder of the guest. +exports.getEmbedder = function (id) { + var ref1 + return (ref1 = guestInstances[id]) != null ? ref1.embedder : void 0 +} diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js new file mode 100644 index 00000000000..7508b6f16a2 --- /dev/null +++ b/lib/browser/guest-window-manager.js @@ -0,0 +1,124 @@ +'use strict' + +const ipcMain = require('electron').ipcMain +const BrowserWindow = require('electron').BrowserWindow + +var hasProp = {}.hasOwnProperty +var frameToGuest = {} + +// Copy attribute of |parent| to |child| if it is not defined in |child|. +var mergeOptions = function (child, parent) { + var key, value + for (key in parent) { + if (!hasProp.call(parent, key)) continue + value = parent[key] + if (!(key in child)) { + if (typeof value === 'object') { + child[key] = mergeOptions({}, value) + } else { + child[key] = value + } + } + } + return child +} + +// Merge |options| with the |embedder|'s window's options. +var mergeBrowserWindowOptions = function (embedder, options) { + if (embedder.browserWindowOptions != null) { + // Inherit the original options if it is a BrowserWindow. + mergeOptions(options, embedder.browserWindowOptions) + } else { + // Or only inherit web-preferences if it is a webview. + if (options.webPreferences == null) { + options.webPreferences = {} + } + mergeOptions(options.webPreferences, embedder.getWebPreferences()) + } + + // Disable node integration on child window if disabled on parent window + if (embedder.getWebPreferences().nodeIntegration === false) { + options.webPreferences.nodeIntegration = false + } + + return options +} + +// Create a new guest created by |embedder| with |options|. +var createGuest = function (embedder, url, frameName, options) { + var closedByEmbedder, closedByUser, guest, guestId, ref1 + guest = frameToGuest[frameName] + if (frameName && (guest != null)) { + guest.loadURL(url) + return guest.id + } + + // Remember the embedder window's id. + if (options.webPreferences == null) { + options.webPreferences = {} + } + options.webPreferences.openerId = (ref1 = BrowserWindow.fromWebContents(embedder)) != null ? ref1.id : void 0 + guest = new BrowserWindow(options) + guest.loadURL(url) + + // When |embedder| is destroyed we should also destroy attached guest, and if + // guest is closed by user then we should prevent |embedder| from double + // closing guest. + guestId = guest.id + closedByEmbedder = function () { + guest.removeListener('closed', closedByUser) + return guest.destroy() + } + closedByUser = function () { + embedder.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId) + return embedder.removeListener('render-view-deleted', closedByEmbedder) + } + embedder.once('render-view-deleted', closedByEmbedder) + guest.once('closed', closedByUser) + if (frameName) { + frameToGuest[frameName] = guest + guest.frameName = frameName + guest.once('closed', function () { + return delete frameToGuest[frameName] + }) + } + return guest.id +} + +// Routed window.open messages. +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function (event, url, frameName, disposition, options) { + options = mergeBrowserWindowOptions(event.sender, options) + event.sender.emit('new-window', event, url, frameName, disposition, options) + if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) { + event.returnValue = null + } else { + event.returnValue = createGuest(event.sender, url, frameName, options) + } +}) + +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) { + var ref1 + return (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1.destroy() : void 0 +}) + +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) { + var ref1 + event.returnValue = (ref1 = BrowserWindow.fromId(guestId)) != null ? ref1[method].apply(ref1, args) : void 0 +}) + +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) { + var guestContents, ref1, ref2, sourceId + sourceId = (ref1 = BrowserWindow.fromWebContents(event.sender)) != null ? ref1.id : void 0 + if (sourceId == null) { + return + } + guestContents = (ref2 = BrowserWindow.fromId(guestId)) != null ? ref2.webContents : void 0 + if ((guestContents != null ? guestContents.getURL().indexOf(targetOrigin) : void 0) === 0 || targetOrigin === '*') { + return guestContents != null ? guestContents.send('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin) : void 0 + } +}) + +ipcMain.on('ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) { + var ref1, ref2 + return (ref1 = BrowserWindow.fromId(guestId)) != null ? (ref2 = ref1.webContents) != null ? ref2[method].apply(ref2, args) : void 0 : void 0 +}) diff --git a/lib/browser/init.js b/lib/browser/init.js new file mode 100644 index 00000000000..fc4f90c399b --- /dev/null +++ b/lib/browser/init.js @@ -0,0 +1,184 @@ +'use strict' + +const fs = require('fs') +const path = require('path') +const util = require('util') +const Module = require('module') +const v8 = require('v8') + +// We modified the original process.argv to let node.js load the atom.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') + +var globalPaths = Module.globalPaths + +if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { + globalPaths.push(path.join(__dirname, 'api')) +} + +// Expose public APIs. +globalPaths.push(path.join(__dirname, 'api', 'exports')) + +if (process.platform === 'win32') { + // Redirect node's console to use our own implementations, since node can not + // handle console output when running as GUI program. + var consoleLog = function (...args) { + return process.log(util.format.apply(util, args) + '\n') + } + var streamWrite = function (chunk, encoding, callback) { + if (Buffer.isBuffer(chunk)) { + chunk = chunk.toString(encoding) + } + process.log(chunk) + if (callback) { + callback() + } + return true + } + console.log = console.error = console.warn = consoleLog + process.stdout.write = process.stderr.write = streamWrite + + // Always returns EOF for stdin stream. + var Readable = require('stream').Readable + var stdin = new Readable() + stdin.push(null) + process.__defineGetter__('stdin', function () { + return stdin + }) +} + +// Don't quit on fatal error. +process.on('uncaughtException', function (error) { + // Do nothing if the user has a custom uncaught exception handler. + var dialog, message, ref, stack + if (process.listeners('uncaughtException').length > 1) { + return + } + + // Show error in GUI. + dialog = require('electron').dialog + stack = (ref = error.stack) != null ? ref : error.name + ': ' + error.message + message = 'Uncaught Exception:\n' + stack + return dialog.showErrorBox('A JavaScript error occurred in the main process', message) +}) + +// Emit 'exit' event on quit. +var app = require('electron').app + +app.on('quit', function (event, exitCode) { + return process.emit('exit', exitCode) +}) + +if (process.platform === 'win32') { + // If we are a Squirrel.Windows-installed app, set app user model ID + // so that users don't have to do this. + // + // Squirrel packages are always of the form: + // + // PACKAGE-NAME + // - Update.exe + // - app-VERSION + // - OUREXE.exe + // + // Squirrel itself will always set the shortcut's App User Model ID to the + // form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call + // app.setAppUserModelId with a matching identifier so that renderer processes + // will inherit this value. + var updateDotExe = path.join( + path.dirname(process.execPath), + '..', + 'update.exe') + + if (fs.statSyncNoException(updateDotExe)) { + var packageDir = path.dirname(path.resolve(updateDotExe)) + var packageName = path.basename(packageDir) + var exeName = path.basename(process.execPath).replace(/\.exe$/i, '') + + app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`) + } +} + +// Map process.exit to app.exit, which quits gracefully. +process.exit = app.exit + +// Load the RPC server. +require('./rpc-server') + +// Load the guest view manager. +require('./guest-view-manager') + +require('./guest-window-manager') + +// Now we try to load app's package.json. +var packageJson = null +var searchPaths = ['app', 'app.asar', 'default_app.asar'] +var i, len, packagePath +for (i = 0, len = searchPaths.length; i < len; i++) { + packagePath = searchPaths[i] + try { + packagePath = path.join(process.resourcesPath, packagePath) + packageJson = JSON.parse(fs.readFileSync(path.join(packagePath, 'package.json'))) + break + } catch (error) { + continue + } +} + +if (packageJson == null) { + process.nextTick(function () { + return process.exit(1) + }) + throw new Error('Unable to find a valid app') +} + +// Set application's version. +if (packageJson.version != null) { + app.setVersion(packageJson.version) +} + +// Set application's name. +if (packageJson.productName != null) { + app.setName(packageJson.productName) +} else if (packageJson.name != null) { + app.setName(packageJson.name) +} + +// Set application's desktop name. +if (packageJson.desktopName != null) { + app.setDesktopName(packageJson.desktopName) +} else { + app.setDesktopName((app.getName()) + '.desktop') +} + +// Set v8 flags +if (packageJson.v8Flags != null) { + v8.setFlagsFromString(packageJson.v8Flags) +} + +// Chrome 42 disables NPAPI plugins by default, reenable them here +app.commandLine.appendSwitch('enable-npapi') + +// Set the user path according to application's name. +app.setPath('userData', path.join(app.getPath('appData'), app.getName())) + +app.setPath('userCache', path.join(app.getPath('cache'), app.getName())) + +app.setAppPath(packagePath) + +// Load the chrome extension support. +require('./chrome-extension') + +// Load internal desktop-capturer module. +require('./desktop-capturer') + +// Set main startup script of the app. +var mainStartupScript = packageJson.main || 'index.js' + +// Finally load app's main.js and transfer control to C++. +Module._load(path.join(packagePath, mainStartupScript), Module, true) diff --git a/lib/browser/objects-registry.js b/lib/browser/objects-registry.js new file mode 100644 index 00000000000..b8aa480a6db --- /dev/null +++ b/lib/browser/objects-registry.js @@ -0,0 +1,94 @@ +'use strict' + +const v8Util = process.atomBinding('v8_util') + +class ObjectsRegistry { + constructor () { + this.nextId = 0 + + // Stores all objects by ref-counting. + // (id) => {object, count} + this.storage = {} + + // Stores the IDs of objects referenced by WebContents. + // (webContentsId) => [id] + this.owners = {} + } + + // Register a new object and return its assigned ID. If the object is already + // registered then the already assigned ID would be returned. + add (webContents, obj) { + // Get or assign an ID to the object. + let id = this.saveToStorage(obj) + + // Add object to the set of referenced objects. + let webContentsId = webContents.getId() + let owner = this.owners[webContentsId] + if (!owner) { + owner = this.owners[webContentsId] = new Set() + // Clear the storage when webContents is reloaded/navigated. + webContents.once('render-view-deleted', (event, id) => { + this.clear(id) + }) + } + if (!owner.has(id)) { + owner.add(id) + // Increase reference count if not referenced before. + this.storage[id].count++ + } + return id + } + + // Get an object according to its ID. + get (id) { + return this.storage[id].object + } + + // Dereference an object according to its ID. + remove (webContentsId, id) { + // Dereference from the storage. + this.dereference(id) + + // Also remove the reference in owner. + this.owners[webContentsId].delete(id) + } + + // Clear all references to objects refrenced by the WebContents. + clear (webContentsId) { + let owner = this.owners[webContentsId] + if (!owner) return + + for (let id of owner) this.dereference(id) + + delete this.owners[webContentsId] + } + + // Private: Saves the object into storage and assigns an ID for it. + saveToStorage (object) { + let id = v8Util.getHiddenValue(object, 'atomId') + if (!id) { + id = ++this.nextId + this.storage[id] = { + count: 0, + object: object + } + v8Util.setHiddenValue(object, 'atomId', id) + } + return id + } + + // Private: Dereference the object from store. + dereference (id) { + let pointer = this.storage[id] + if (pointer == null) { + return + } + pointer.count -= 1 + if (pointer.count === 0) { + v8Util.deleteHiddenValue(pointer.object, 'atomId') + return delete this.storage[id] + } + } +} + +module.exports = new ObjectsRegistry() diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js new file mode 100644 index 00000000000..79a52b13262 --- /dev/null +++ b/lib/browser/rpc-server.js @@ -0,0 +1,370 @@ +'use strict' + +const electron = require('electron') +const ipcMain = electron.ipcMain +const objectsRegistry = require('./objects-registry') +const v8Util = process.atomBinding('v8_util') +const IDWeakMap = process.atomBinding('id_weak_map').IDWeakMap + +// The internal properties of Function. +const FUNCTION_PROPERTIES = [ + 'length', 'name', 'arguments', 'caller', 'prototype' +] + +// The remote functions in renderer processes. +// (webContentsId) => {id: Function} +let rendererFunctions = {} + +// Return the description of object's members: +let getObjectMembers = function (object) { + let names = Object.getOwnPropertyNames(object) + // For Function, we should not override following properties even though they + // are "own" properties. + if (typeof object === 'function') { + names = names.filter((name) => { + return !FUNCTION_PROPERTIES.includes(name) + }) + } + // Map properties to descriptors. + return names.map((name) => { + let descriptor = Object.getOwnPropertyDescriptor(object, name) + let member = {name, enumerable: descriptor.enumerable, writable: false} + if (descriptor.get === undefined && typeof object[name] === 'function') { + member.type = 'method' + } else { + if (descriptor.set || descriptor.writable) member.writable = true + member.type = 'get' + } + return member + }) +} + +// Return the description of object's prototype. +let getObjectPrototype = function (object) { + let proto = Object.getPrototypeOf(object) + if (proto === null || proto === Object.prototype) return null + return { + members: getObjectMembers(proto), + proto: getObjectPrototype(proto) + } +} + +// Convert a real value into meta data. +var valueToMeta = function (sender, value, optimizeSimpleObject) { + var el, i, len, meta + if (optimizeSimpleObject == null) { + optimizeSimpleObject = false + } + meta = { + type: typeof value + } + if (Buffer.isBuffer(value)) { + meta.type = 'buffer' + } + if (value === null) { + meta.type = 'value' + } + if (Array.isArray(value)) { + meta.type = 'array' + } + if (value instanceof Error) { + meta.type = 'error' + } + if (value instanceof Date) { + meta.type = 'date' + } + if ((value != null ? value.constructor.name : void 0) === 'Promise') { + meta.type = 'promise' + } + + // Treat simple objects as value. + if (optimizeSimpleObject && meta.type === 'object' && v8Util.getHiddenValue(value, 'simple')) { + meta.type = 'value' + } + + // Treat the arguments object as array. + if (meta.type === 'object' && (value.hasOwnProperty('callee')) && (value.length != null)) { + meta.type = 'array' + } + if (meta.type === 'array') { + meta.members = [] + for (i = 0, len = value.length; i < len; i++) { + el = value[i] + meta.members.push(valueToMeta(sender, el)) + } + } else if (meta.type === 'object' || meta.type === 'function') { + meta.name = value.constructor.name + + // Reference the original value if it's an object, because when it's + // passed to renderer we would assume the renderer keeps a reference of + // it. + meta.id = objectsRegistry.add(sender, value) + meta.members = getObjectMembers(value) + meta.proto = getObjectPrototype(value) + } else if (meta.type === 'buffer') { + meta.value = Array.prototype.slice.call(value, 0) + } else if (meta.type === 'promise') { + meta.then = valueToMeta(sender, function (v) { value.then(v) }) + } else if (meta.type === 'error') { + meta.members = plainObjectToMeta(value) + + // Error.name is not part of own properties. + meta.members.push({ + name: 'name', + value: value.name + }) + } else if (meta.type === 'date') { + meta.value = value.getTime() + } else { + meta.type = 'value' + meta.value = value + } + return meta +} + +// Convert object to meta by value. +var plainObjectToMeta = function (obj) { + return Object.getOwnPropertyNames(obj).map(function (name) { + return { + name: name, + value: obj[name] + } + }) +} + +// Convert Error into meta data. +var exceptionToMeta = function (error) { + return { + type: 'exception', + message: error.message, + stack: error.stack || error + } +} + +// Convert array of meta data from renderer into array of real values. +var unwrapArgs = function (sender, args) { + var metaToValue + metaToValue = function (meta) { + var i, len, member, ref, returnValue + switch (meta.type) { + case 'value': + return meta.value + case 'remote-object': + return objectsRegistry.get(meta.id) + case 'array': + return unwrapArgs(sender, meta.value) + case 'buffer': + return new Buffer(meta.value) + case 'date': + return new Date(meta.value) + case 'promise': + return Promise.resolve({ + then: metaToValue(meta.then) + }) + case 'object': { + let ret = {} + Object.defineProperty(ret.constructor, 'name', { value: meta.name }) + + ref = meta.members + for (i = 0, len = ref.length; i < len; i++) { + member = ref[i] + ret[member.name] = metaToValue(member.value) + } + return ret + } + case 'function-with-return-value': + returnValue = metaToValue(meta.value) + return function () { + return returnValue + } + case 'function': { + // Cache the callbacks in renderer. + let webContentsId = sender.getId() + let callbacks = rendererFunctions[webContentsId] + if (!callbacks) { + callbacks = rendererFunctions[webContentsId] = new IDWeakMap() + sender.once('render-view-deleted', function (event, id) { + callbacks.clear() + delete rendererFunctions[id] + }) + } + + if (callbacks.has(meta.id)) return callbacks.get(meta.id) + + let callIntoRenderer = function (...args) { + if ((webContentsId in rendererFunctions) && !sender.isDestroyed()) { + sender.send('ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args)) + } else { + throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`) + } + } + v8Util.setDestructor(callIntoRenderer, function () { + if ((webContentsId in rendererFunctions) && !sender.isDestroyed()) { + sender.send('ATOM_RENDERER_RELEASE_CALLBACK', meta.id) + } + }) + callbacks.set(meta.id, callIntoRenderer) + return callIntoRenderer + } + default: + throw new TypeError(`Unknown type: ${meta.type}`) + } + } + return args.map(metaToValue) +} + +// Call a function and send reply asynchronously if it's a an asynchronous +// style function and the caller didn't pass a callback. +var callFunction = function (event, func, caller, args) { + var funcMarkedAsync, funcName, funcPassedCallback, ref, ret + funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous') + funcPassedCallback = typeof args[args.length - 1] === 'function' + try { + if (funcMarkedAsync && !funcPassedCallback) { + args.push(function (ret) { + event.returnValue = valueToMeta(event.sender, ret, true) + }) + return func.apply(caller, args) + } else { + ret = func.apply(caller, args) + event.returnValue = valueToMeta(event.sender, ret, true) + } + } catch (error) { + // Catch functions thrown further down in function invocation and wrap + // them with the function name so it's easier to trace things like + // `Error processing argument -1.` + funcName = ((ref = func.name) != null) ? ref : 'anonymous' + throw new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`) + } +} + +ipcMain.on('ATOM_BROWSER_REQUIRE', function (event, module) { + try { + event.returnValue = valueToMeta(event.sender, process.mainModule.require(module)) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_GET_BUILTIN', function (event, module) { + try { + event.returnValue = valueToMeta(event.sender, electron[module]) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_GLOBAL', function (event, name) { + try { + event.returnValue = valueToMeta(event.sender, global[name]) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_CURRENT_WINDOW', function (event) { + try { + event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow()) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_CURRENT_WEB_CONTENTS', function (event) { + event.returnValue = valueToMeta(event.sender, event.sender) +}) + +ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function (event, id, args) { + try { + args = unwrapArgs(event.sender, args) + let constructor = objectsRegistry.get(id) + + // Call new with array of arguments. + // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible + let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))) + event.returnValue = valueToMeta(event.sender, obj) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function (event, id, args) { + try { + args = unwrapArgs(event.sender, args) + let func = objectsRegistry.get(id) + return callFunction(event, func, global, args) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, args) { + try { + args = unwrapArgs(event.sender, args) + let constructor = objectsRegistry.get(id)[method] + + // Call new with array of arguments. + let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))) + event.returnValue = valueToMeta(event.sender, obj) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function (event, id, method, args) { + try { + args = unwrapArgs(event.sender, args) + let obj = objectsRegistry.get(id) + return callFunction(event, obj[method], obj, args) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_MEMBER_SET', function (event, id, name, value) { + try { + let obj = objectsRegistry.get(id) + obj[name] = value + event.returnValue = null + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_MEMBER_GET', function (event, id, name) { + try { + let obj = objectsRegistry.get(id) + event.returnValue = valueToMeta(event.sender, obj[name]) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_DEREFERENCE', function (event, id) { + return objectsRegistry.remove(event.sender.getId(), id) +}) + +ipcMain.on('ATOM_BROWSER_GUEST_WEB_CONTENTS', function (event, guestInstanceId) { + try { + let guestViewManager = require('./guest-view-manager') + event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId)) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) + +ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, requestId, guestInstanceId, method, ...args) { + try { + let guestViewManager = require('./guest-view-manager') + let guest = guestViewManager.getGuest(guestInstanceId) + if (requestId) { + const responseCallback = function (result) { + event.sender.send(`ATOM_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, result) + } + args.push(responseCallback) + } + guest[method].apply(guest, args) + } catch (error) { + event.returnValue = exceptionToMeta(error) + } +}) diff --git a/lib/common/api/callbacks-registry.js b/lib/common/api/callbacks-registry.js new file mode 100644 index 00000000000..7a9bf534f72 --- /dev/null +++ b/lib/common/api/callbacks-registry.js @@ -0,0 +1,62 @@ +'use strict' + +const v8Util = process.atomBinding('v8_util') + +class CallbacksRegistry { + constructor () { + this.nextId = 0 + this.callbacks = {} + } + + add (callback) { + // The callback is already added. + var filenameAndLine, id, location, match, ref, regexp, stackString + id = v8Util.getHiddenValue(callback, 'callbackId') + if (id != null) { + return id + } + id = ++this.nextId + + // Capture the location of the function and put it in the ID string, + // so that release errors can be tracked down easily. + regexp = /at (.*)/gi + stackString = (new Error()).stack + while ((match = regexp.exec(stackString)) !== null) { + location = match[1] + if (location.indexOf('(native)') !== -1) { + continue + } + if (location.indexOf('electron.asar') !== -1) { + continue + } + ref = /([^\/^\)]*)\)?$/gi.exec(location) + filenameAndLine = ref[1] + break + } + this.callbacks[id] = callback + v8Util.setHiddenValue(callback, 'callbackId', id) + v8Util.setHiddenValue(callback, 'location', filenameAndLine) + return id + } + + get (id) { + var ref + return (ref = this.callbacks[id]) != null ? ref : function () {} + } + + call (id, ...args) { + var ref + return (ref = this.get(id)).call.apply(ref, [global].concat(args)) + } + + apply (id, ...args) { + var ref + return (ref = this.get(id)).apply.apply(ref, [global].concat(args)) + } + + remove (id) { + return delete this.callbacks[id] + } +} + +module.exports = CallbacksRegistry diff --git a/atom/common/api/lib/clipboard.js b/lib/common/api/clipboard.js similarity index 56% rename from atom/common/api/lib/clipboard.js rename to lib/common/api/clipboard.js index cd1cb53e400..7186f8b6341 100644 --- a/atom/common/api/lib/clipboard.js +++ b/lib/common/api/clipboard.js @@ -1,6 +1,6 @@ if (process.platform === 'linux' && process.type === 'renderer') { // On Linux we could not access clipboard in renderer process. - module.exports = require('electron').remote.clipboard; + module.exports = require('electron').remote.clipboard } else { - module.exports = process.atomBinding('clipboard'); + module.exports = process.atomBinding('clipboard') } diff --git a/lib/common/api/crash-reporter.js b/lib/common/api/crash-reporter.js new file mode 100644 index 00000000000..f1088755011 --- /dev/null +++ b/lib/common/api/crash-reporter.js @@ -0,0 +1,98 @@ +'use strict' + +const os = require('os') +const path = require('path') +const spawn = require('child_process').spawn +const electron = require('electron') +const binding = process.atomBinding('crash_reporter') + +var CrashReporter = (function () { + function CrashReporter () {} + + CrashReporter.prototype.start = function (options) { + var app, args, autoSubmit, companyName, deprecate, env, extra, ignoreSystemCrashHandler, start, submitURL + if (options == null) { + options = {} + } + this.productName = options.productName + companyName = options.companyName + submitURL = options.submitURL + autoSubmit = options.autoSubmit + ignoreSystemCrashHandler = options.ignoreSystemCrashHandler + extra = options.extra + + // Deprecated. + deprecate = electron.deprecate + if (options.submitUrl) { + if (submitURL == null) { + submitURL = options.submitUrl + } + deprecate.warn('submitUrl', 'submitURL') + } + app = (process.type === 'browser' ? electron : electron.remote).app + if (this.productName == null) { + this.productName = app.getName() + } + if (autoSubmit == null) { + autoSubmit = true + } + if (ignoreSystemCrashHandler == null) { + ignoreSystemCrashHandler = false + } + if (extra == null) { + extra = {} + } + if (extra._productName == null) { + extra._productName = this.productName + } + if (extra._companyName == null) { + extra._companyName = companyName + } + if (extra._version == null) { + extra._version = app.getVersion() + } + if (companyName == null) { + deprecate.log('companyName is now a required option to crashReporter.start') + return + } + if (submitURL == null) { + deprecate.log('submitURL is now a required option to crashReporter.start') + return + } + start = () => { + binding.start(this.productName, companyName, submitURL, autoSubmit, ignoreSystemCrashHandler, extra) + } + if (process.platform === 'win32') { + args = ['--reporter-url=' + submitURL, '--application-name=' + this.productName, '--v=1'] + env = { + ATOM_SHELL_INTERNAL_CRASH_SERVICE: 1 + } + spawn(process.execPath, args, { + env: env, + detached: true + }) + } + return start() + } + + CrashReporter.prototype.getLastCrashReport = function () { + var reports + reports = this.getUploadedReports() + if (reports.length > 0) { + return reports[0] + } else { + return null + } + } + + CrashReporter.prototype.getUploadedReports = function () { + var log, tmpdir + tmpdir = process.platform === 'win32' ? os.tmpdir() : '/tmp' + log = process.platform === 'darwin' ? path.join(tmpdir, this.productName + ' Crashes') : path.join(tmpdir, this.productName + ' Crashes', 'uploads.log') + return binding._getUploadedReports(log) + } + + return CrashReporter +})() + +module.exports = new CrashReporter() diff --git a/lib/common/api/deprecate.js b/lib/common/api/deprecate.js new file mode 100644 index 00000000000..ff34a2bdfe7 --- /dev/null +++ b/lib/common/api/deprecate.js @@ -0,0 +1,108 @@ +// Deprecate a method. +const deprecate = function (oldName, newName, fn) { + var warned + warned = false + return function () { + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn(oldName, newName) + } + return fn.apply(this, arguments) + } +} + +// The method is renamed. +deprecate.rename = function (object, oldName, newName) { + var newMethod, warned + warned = false + newMethod = function () { + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn(oldName, newName) + } + return this[newName].apply(this, arguments) + } + if (typeof object === 'function') { + object.prototype[oldName] = newMethod + } else { + object[oldName] = newMethod + } +} + +// Forward the method to member. +deprecate.member = function (object, method, member) { + var warned + warned = false + object.prototype[method] = function () { + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn(method, member + '.' + method) + } + return this[member][method].apply(this[member], arguments) + } +} + +// Deprecate a property. +deprecate.property = function (object, property, method) { + return Object.defineProperty(object, property, { + get: function () { + var warned + warned = false + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn(property + ' property', method + ' method') + } + return this[method]() + } + }) +} + +// Deprecate an event. +deprecate.event = function (emitter, oldName, newName, fn) { + var warned + warned = false + return emitter.on(newName, function (...args) { + // there is listeners for old API. + if (this.listenerCount(oldName) > 0) { + if (!(warned || process.noDeprecation)) { + warned = true + deprecate.warn("'" + oldName + "' event", "'" + newName + "' event") + } + if (fn != null) { + return fn.apply(this, arguments) + } else { + return this.emit.apply(this, [oldName].concat(args)) + } + } + }) +} + +// Print deprecation warning. +deprecate.warn = function (oldName, newName) { + return deprecate.log(oldName + ' is deprecated. Use ' + newName + ' instead.') +} + +var deprecationHandler = null + +// Print deprecation message. +deprecate.log = function (message) { + if (typeof deprecationHandler === 'function') { + deprecationHandler(message) + } else if (process.throwDeprecation) { + throw new Error(message) + } else if (process.traceDeprecation) { + return console.trace(message) + } else { + return console.warn('(electron) ' + message) + } +} + +deprecate.setHandler = function (handler) { + deprecationHandler = handler +} + +deprecate.getHandler = function () { + return deprecationHandler +} + +module.exports = deprecate diff --git a/lib/common/api/deprecations.js b/lib/common/api/deprecations.js new file mode 100644 index 00000000000..f4c4d74d6b9 --- /dev/null +++ b/lib/common/api/deprecations.js @@ -0,0 +1,11 @@ +'use strict' + +const deprecate = require('electron').deprecate + +exports.setHandler = function (deprecationHandler) { + deprecate.setHandler(deprecationHandler) +} + +exports.getHandler = function () { + return deprecate.getHandler() +} diff --git a/lib/common/api/exports/electron.js b/lib/common/api/exports/electron.js new file mode 100644 index 00000000000..3bef1f0c50f --- /dev/null +++ b/lib/common/api/exports/electron.js @@ -0,0 +1,63 @@ +// Do not expose the internal modules to `require`. +const hideInternalModules = function () { + var globalPaths = require('module').globalPaths + if (globalPaths.length === 3) { + // Remove the "common/api/lib" and "browser-or-renderer/api/lib". + return globalPaths.splice(0, 2) + } +} + +// Attaches properties to |exports|. +exports.defineProperties = function (exports) { + return Object.defineProperties(exports, { + hideInternalModules: { + enumerable: true, + value: hideInternalModules + }, + + // 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') + } + }, + deprecations: { + enumerable: true, + get: function () { + return require('../deprecations') + } + }, + 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') + } + } + }) +} diff --git a/lib/common/api/native-image.js b/lib/common/api/native-image.js new file mode 100644 index 00000000000..ebb06138210 --- /dev/null +++ b/lib/common/api/native-image.js @@ -0,0 +1,7 @@ +const deprecate = require('electron').deprecate +const nativeImage = process.atomBinding('native_image') + +// Deprecated. +deprecate.rename(nativeImage, 'createFromDataUrl', 'createFromDataURL') + +module.exports = nativeImage diff --git a/lib/common/api/shell.js b/lib/common/api/shell.js new file mode 100644 index 00000000000..78e2a0938ba --- /dev/null +++ b/lib/common/api/shell.js @@ -0,0 +1 @@ +module.exports = process.atomBinding('shell') diff --git a/lib/common/asar.js b/lib/common/asar.js new file mode 100644 index 00000000000..26241281322 --- /dev/null +++ b/lib/common/asar.js @@ -0,0 +1,599 @@ +(function () { + const asar = process.binding('atom_common_asar') + const child_process = require('child_process') + const path = require('path') + const util = require('util') + + var hasProp = {}.hasOwnProperty + + // Cache asar archive objects. + var cachedArchives = {} + + var getOrCreateArchive = function (p) { + var archive + archive = cachedArchives[p] + if (archive != null) { + return archive + } + archive = asar.createArchive(p) + if (!archive) { + return false + } + cachedArchives[p] = archive + return archive + } + + // Clean cache on quit. + process.on('exit', function () { + var archive, p + for (p in cachedArchives) { + if (!hasProp.call(cachedArchives, p)) continue + archive = cachedArchives[p] + archive.destroy() + } + }) + + // Separate asar package's path from full path. + var splitPath = function (p) { + var index + + // shortcut to disable asar. + if (process.noAsar) { + return [false] + } + + if (typeof p !== 'string') { + return [false] + } + if (p.substr(-5) === '.asar') { + return [true, p, ''] + } + p = path.normalize(p) + index = p.lastIndexOf('.asar' + path.sep) + if (index === -1) { + return [false] + } + return [true, p.substr(0, index + 5), p.substr(index + 6)] + } + + // Convert asar archive's Stats object to fs's Stats object. + var nextInode = 0 + + var uid = process.getuid != null ? process.getuid() : 0 + + var gid = process.getgid != null ? process.getgid() : 0 + + var fakeTime = new Date() + + var asarStatsToFsStats = function (stats) { + return { + dev: 1, + ino: ++nextInode, + mode: 33188, + nlink: 1, + uid: uid, + gid: gid, + rdev: 0, + atime: stats.atime || fakeTime, + birthtime: stats.birthtime || fakeTime, + mtime: stats.mtime || fakeTime, + ctime: stats.ctime || fakeTime, + size: stats.size, + isFile: function () { + return stats.isFile + }, + isDirectory: function () { + return stats.isDirectory + }, + isSymbolicLink: function () { + return stats.isLink + }, + isBlockDevice: function () { + return false + }, + isCharacterDevice: function () { + return false + }, + isFIFO: function () { + return false + }, + isSocket: function () { + return false + } + } + } + + // Create a ENOENT error. + var notFoundError = function (asarPath, filePath, callback) { + var error + error = new Error(`ENOENT, ${filePath} not found in ${asarPath}`) + error.code = 'ENOENT' + error.errno = -2 + if (typeof callback !== 'function') { + throw error + } + return process.nextTick(function () { + return callback(error) + }) + } + + // Create a ENOTDIR error. + var notDirError = function (callback) { + var error + error = new Error('ENOTDIR, not a directory') + error.code = 'ENOTDIR' + error.errno = -20 + if (typeof callback !== 'function') { + throw error + } + return process.nextTick(function () { + return callback(error) + }) + } + + // Create invalid archive error. + var invalidArchiveError = function (asarPath, callback) { + var error + error = new Error(`Invalid package ${asarPath}`) + if (typeof callback !== 'function') { + throw error + } + return process.nextTick(function () { + return callback(error) + }) + } + + // Override APIs that rely on passing file path instead of content to C++. + var overrideAPISync = function (module, name, arg) { + var old + if (arg == null) { + arg = 0 + } + old = module[name] + module[name] = function () { + var archive, newPath, p + p = arguments[arg] + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return old.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + newPath = archive.copyFileOut(filePath) + if (!newPath) { + notFoundError(asarPath, filePath) + } + arguments[arg] = newPath + return old.apply(this, arguments) + } + } + + var overrideAPI = function (module, name, arg) { + var old + if (arg == null) { + arg = 0 + } + old = module[name] + module[name] = function () { + var archive, callback, newPath, p + p = arguments[arg] + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return old.apply(this, arguments) + } + callback = arguments[arguments.length - 1] + if (typeof callback !== 'function') { + return overrideAPISync(module, name, arg) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + newPath = archive.copyFileOut(filePath) + if (!newPath) { + return notFoundError(asarPath, filePath, callback) + } + arguments[arg] = newPath + return old.apply(this, arguments) + } + } + + // Override fs APIs. + exports.wrapFsWithAsar = function (fs) { + var exists, existsSync, internalModuleReadFile, internalModuleStat, lstat, lstatSync, mkdir, mkdirSync, readFile, readFileSync, readdir, readdirSync, realpath, realpathSync, stat, statSync, statSyncNoException, logFDs, logASARAccess + + logFDs = {} + logASARAccess = function (asarPath, filePath, offset) { + if (!process.env.ELECTRON_LOG_ASAR_READS) { + return + } + if (!logFDs[asarPath]) { + var logFilename, logPath + const path = require('path') + logFilename = path.basename(asarPath, '.asar') + '-access-log.txt' + logPath = path.join(require('os').tmpdir(), logFilename) + logFDs[asarPath] = fs.openSync(logPath, 'a') + console.log('Logging ' + asarPath + ' access to ' + logPath) + } + fs.writeSync(logFDs[asarPath], offset + ': ' + filePath + '\n') + } + + lstatSync = fs.lstatSync + fs.lstatSync = function (p) { + var archive, stats + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return lstatSync(p) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + stats = archive.stat(filePath) + if (!stats) { + notFoundError(asarPath, filePath) + } + return asarStatsToFsStats(stats) + } + lstat = fs.lstat + fs.lstat = function (p, callback) { + var archive, stats + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return lstat(p, callback) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + stats = getOrCreateArchive(asarPath).stat(filePath) + if (!stats) { + return notFoundError(asarPath, filePath, callback) + } + return process.nextTick(function () { + return callback(null, asarStatsToFsStats(stats)) + }) + } + statSync = fs.statSync + fs.statSync = function (p) { + const [isAsar] = splitPath(p) + if (!isAsar) { + return statSync(p) + } + + // Do not distinguish links for now. + return fs.lstatSync(p) + } + stat = fs.stat + fs.stat = function (p, callback) { + const [isAsar] = splitPath(p) + if (!isAsar) { + return stat(p, callback) + } + + // Do not distinguish links for now. + return process.nextTick(function () { + return fs.lstat(p, callback) + }) + } + statSyncNoException = fs.statSyncNoException + fs.statSyncNoException = function (p) { + var archive, stats + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return statSyncNoException(p) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return false + } + stats = archive.stat(filePath) + if (!stats) { + return false + } + return asarStatsToFsStats(stats) + } + realpathSync = fs.realpathSync + fs.realpathSync = function (p) { + var archive, real + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return realpathSync.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + real = archive.realpath(filePath) + if (real === false) { + notFoundError(asarPath, filePath) + } + return path.join(realpathSync(asarPath), real) + } + realpath = fs.realpath + fs.realpath = function (p, cache, callback) { + var archive, real + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return realpath.apply(this, arguments) + } + if (typeof cache === 'function') { + callback = cache + cache = void 0 + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + real = archive.realpath(filePath) + if (real === false) { + return notFoundError(asarPath, filePath, callback) + } + return realpath(asarPath, function (err, p) { + if (err) { + return callback(err) + } + return callback(null, path.join(p, real)) + }) + } + exists = fs.exists + fs.exists = function (p, callback) { + var archive + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return exists(p, callback) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + return process.nextTick(function () { + return callback(archive.stat(filePath) !== false) + }) + } + existsSync = fs.existsSync + fs.existsSync = function (p) { + var archive + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return existsSync(p) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return false + } + return archive.stat(filePath) !== false + } + readFile = fs.readFile + fs.readFile = function (p, options, callback) { + var archive, buffer, encoding, fd, info, realPath + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return readFile.apply(this, arguments) + } + if (typeof options === 'function') { + callback = options + options = void 0 + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + info = archive.getFileInfo(filePath) + if (!info) { + return notFoundError(asarPath, filePath, callback) + } + if (info.size === 0) { + return process.nextTick(function () { + return callback(null, new Buffer(0)) + }) + } + if (info.unpacked) { + realPath = archive.copyFileOut(filePath) + return fs.readFile(realPath, options, callback) + } + if (!options) { + options = { + encoding: null + } + } else if (util.isString(options)) { + options = { + encoding: options + } + } else if (!util.isObject(options)) { + throw new TypeError('Bad arguments') + } + encoding = options.encoding + buffer = new Buffer(info.size) + fd = archive.getFd() + if (!(fd >= 0)) { + return notFoundError(asarPath, filePath, callback) + } + logASARAccess(asarPath, filePath, info.offset) + return fs.read(fd, buffer, 0, info.size, info.offset, function (error) { + return callback(error, encoding ? buffer.toString(encoding) : buffer) + }) + } + readFileSync = fs.readFileSync + fs.readFileSync = function (p, opts) { + // this allows v8 to optimize this function + var archive, buffer, encoding, fd, info, options, realPath + options = opts + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return readFileSync.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + info = archive.getFileInfo(filePath) + if (!info) { + notFoundError(asarPath, filePath) + } + if (info.size === 0) { + if (options) { + return '' + } else { + return new Buffer(0) + } + } + if (info.unpacked) { + realPath = archive.copyFileOut(filePath) + return fs.readFileSync(realPath, options) + } + if (!options) { + options = { + encoding: null + } + } else if (util.isString(options)) { + options = { + encoding: options + } + } else if (!util.isObject(options)) { + throw new TypeError('Bad arguments') + } + encoding = options.encoding + buffer = new Buffer(info.size) + fd = archive.getFd() + if (!(fd >= 0)) { + notFoundError(asarPath, filePath) + } + logASARAccess(asarPath, filePath, info.offset) + fs.readSync(fd, buffer, 0, info.size, info.offset) + if (encoding) { + return buffer.toString(encoding) + } else { + return buffer + } + } + readdir = fs.readdir + fs.readdir = function (p, callback) { + var archive, files + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return readdir.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return invalidArchiveError(asarPath, callback) + } + files = archive.readdir(filePath) + if (!files) { + return notFoundError(asarPath, filePath, callback) + } + return process.nextTick(function () { + return callback(null, files) + }) + } + readdirSync = fs.readdirSync + fs.readdirSync = function (p) { + var archive, files + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return readdirSync.apply(this, arguments) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + invalidArchiveError(asarPath) + } + files = archive.readdir(filePath) + if (!files) { + notFoundError(asarPath, filePath) + } + return files + } + internalModuleReadFile = process.binding('fs').internalModuleReadFile + process.binding('fs').internalModuleReadFile = function (p) { + var archive, buffer, fd, info, realPath + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return internalModuleReadFile(p) + } + archive = getOrCreateArchive(asarPath) + if (!archive) { + return void 0 + } + info = archive.getFileInfo(filePath) + if (!info) { + return void 0 + } + if (info.size === 0) { + return '' + } + if (info.unpacked) { + realPath = archive.copyFileOut(filePath) + return fs.readFileSync(realPath, { + encoding: 'utf8' + }) + } + buffer = new Buffer(info.size) + fd = archive.getFd() + if (!(fd >= 0)) { + return void 0 + } + logASARAccess(asarPath, filePath, info.offset) + fs.readSync(fd, buffer, 0, info.size, info.offset) + return buffer.toString('utf8') + } + internalModuleStat = process.binding('fs').internalModuleStat + process.binding('fs').internalModuleStat = function (p) { + var archive, stats + const [isAsar, asarPath, filePath] = splitPath(p) + if (!isAsar) { + return internalModuleStat(p) + } + archive = getOrCreateArchive(asarPath) + + // -ENOENT + if (!archive) { + return -34 + } + stats = archive.stat(filePath) + + // -ENOENT + if (!stats) { + return -34 + } + if (stats.isDirectory) { + return 1 + } else { + return 0 + } + } + + // Calling mkdir for directory inside asar archive should throw ENOTDIR + // error, but on Windows it throws ENOENT. + // This is to work around the recursive looping bug of mkdirp since it is + // widely used. + if (process.platform === 'win32') { + mkdir = fs.mkdir + fs.mkdir = function (p, mode, callback) { + if (typeof mode === 'function') { + callback = mode + } + const [isAsar, , filePath] = splitPath(p) + if (isAsar && filePath.length) { + return notDirError(callback) + } + return mkdir(p, mode, callback) + } + mkdirSync = fs.mkdirSync + fs.mkdirSync = function (p, mode) { + const [isAsar, , filePath] = splitPath(p) + if (isAsar && filePath.length) { + notDirError() + } + return mkdirSync(p, mode) + } + } + overrideAPI(fs, 'open') + overrideAPI(child_process, 'execFile') + overrideAPISync(process, 'dlopen', 1) + overrideAPISync(require('module')._extensions, '.node', 1) + overrideAPISync(fs, 'openSync') + return overrideAPISync(child_process, 'execFileSync') + } +})() diff --git a/lib/common/asar_init.js b/lib/common/asar_init.js new file mode 100644 index 00000000000..0f728200880 --- /dev/null +++ b/lib/common/asar_init.js @@ -0,0 +1,20 @@ +;(function () { + return function (process, require, asarSource) { + // Make asar.coffee accessible via "require". + process.binding('natives').ATOM_SHELL_ASAR = asarSource + + // Monkey-patch the fs module. + require('ATOM_SHELL_ASAR').wrapFsWithAsar(require('fs')) + + // Make graceful-fs work with asar. + var source = process.binding('natives') + source['original-fs'] = source.fs + source['fs'] = ` +var nativeModule = new process.NativeModule('original-fs') +nativeModule.cache() +nativeModule.compile() +var asar = require('ATOM_SHELL_ASAR') +asar.wrapFsWithAsar(nativeModule.exports) +module.exports = nativeModule.exports` + } +})() diff --git a/atom/common/lib/init.js b/lib/common/init.js similarity index 60% rename from atom/common/lib/init.js rename to lib/common/init.js index a4766c4a7f9..221febb0c37 100644 --- a/atom/common/lib/init.js +++ b/lib/common/init.js @@ -1,47 +1,46 @@ -const path = require('path'); -const timers = require('timers'); -const Module = require('module'); +const path = require('path') +const timers = require('timers') +const Module = require('module') -process.atomBinding = function(name) { +process.atomBinding = function (name) { try { - return process.binding("atom_" + process.type + "_" + name); + return process.binding('atom_' + process.type + '_' + name) } catch (error) { if (/No such module/.test(error.message)) { - return process.binding("atom_common_" + name); + return process.binding('atom_common_' + name) } } -}; +} if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { // Add common/api/lib to module search paths. - Module.globalPaths.push(path.resolve(__dirname, '..', 'api', 'lib')); + Module.globalPaths.push(path.join(__dirname, 'api')) } - // 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 // callbacks wouldn't be called until something else activated the uv loop, // which would delay the callbacks for arbitrary long time. So we should // initiatively activate the uv loop once setImmediate and process.nextTick is // called. -var wrapWithActivateUvLoop = function(func) { - return function() { - process.activateUvLoop(); - return func.apply(this, arguments); - }; -}; +var wrapWithActivateUvLoop = function (func) { + return function () { + process.activateUvLoop() + return func.apply(this, arguments) + } +} -process.nextTick = wrapWithActivateUvLoop(process.nextTick); +process.nextTick = wrapWithActivateUvLoop(process.nextTick) -global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate); +global.setImmediate = wrapWithActivateUvLoop(timers.setImmediate) -global.clearImmediate = timers.clearImmediate; +global.clearImmediate = timers.clearImmediate if (process.type === 'browser') { // setTimeout needs to update the polling timeout of the event loop, when // called under Chromium's event loop the node's event loop won't get a chance // to update the timeout, so we have to force the node's event loop to // recalculate the timeout in browser process. - global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout); - global.setInterval = wrapWithActivateUvLoop(timers.setInterval); + global.setTimeout = wrapWithActivateUvLoop(timers.setTimeout) + global.setInterval = wrapWithActivateUvLoop(timers.setInterval) } diff --git a/lib/common/reset-search-paths.js b/lib/common/reset-search-paths.js new file mode 100644 index 00000000000..30709b56a78 --- /dev/null +++ b/lib/common/reset-search-paths.js @@ -0,0 +1,36 @@ +const path = require('path') +const Module = require('module') + +// Clear Node's global search paths. +Module.globalPaths.length = 0 + +// Clear current and parent(init.coffee)'s search paths. +module.paths = [] + +module.parent.paths = [] + +// Prevent Node from adding paths outside this app to search paths. +Module._nodeModulePaths = function (from) { + var dir, i, part, parts, paths, skipOutsidePaths, splitRe, tip + from = path.resolve(from) + + // If "from" is outside the app then we do nothing. + skipOutsidePaths = from.startsWith(process.resourcesPath) + + // Following logoic is copied from module.js. + splitRe = process.platform === 'win32' ? /[\/\\]/ : /\// + paths = [] + parts = from.split(splitRe) + for (tip = i = parts.length - 1; i >= 0; tip = i += -1) { + part = parts[tip] + if (part === 'node_modules') { + continue + } + dir = parts.slice(0, tip + 1).join(path.sep) + if (skipOutsidePaths && !dir.startsWith(process.resourcesPath)) { + break + } + paths.push(path.join(dir, 'node_modules')) + } + return paths +} diff --git a/lib/renderer/api/desktop-capturer.js b/lib/renderer/api/desktop-capturer.js new file mode 100644 index 00000000000..5683e9cff25 --- /dev/null +++ b/lib/renderer/api/desktop-capturer.js @@ -0,0 +1,47 @@ +const ipcRenderer = require('electron').ipcRenderer +const nativeImage = require('electron').nativeImage + +var nextId = 0 +var includes = [].includes + +var getNextId = function () { + return ++nextId +} + +// |options.type| can not be empty and has to include 'window' or 'screen'. +var isValid = function (options) { + return ((options != null ? options.types : void 0) != null) && Array.isArray(options.types) +} + +exports.getSources = function (options, callback) { + var captureScreen, captureWindow, id + if (!isValid(options)) { + return callback(new Error('Invalid options')) + } + captureWindow = includes.call(options.types, 'window') + captureScreen = includes.call(options.types, 'screen') + if (options.thumbnailSize == null) { + options.thumbnailSize = { + width: 150, + height: 150 + } + } + id = getNextId() + ipcRenderer.send('ATOM_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, options.thumbnailSize, id) + return ipcRenderer.once('ATOM_RENDERER_DESKTOP_CAPTURER_RESULT_' + id, function (event, sources) { + var source + return callback(null, (function () { + var i, len, results + results = [] + for (i = 0, len = sources.length; i < len; i++) { + source = sources[i] + results.push({ + id: source.id, + name: source.name, + thumbnail: nativeImage.createFromDataURL(source.thumbnail) + }) + } + return results + })()) + }) +} diff --git a/lib/renderer/api/exports/electron.js b/lib/renderer/api/exports/electron.js new file mode 100644 index 00000000000..8e05adc980c --- /dev/null +++ b/lib/renderer/api/exports/electron.js @@ -0,0 +1,38 @@ +const common = require('../../../common/api/exports/electron') + +// 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') + } + } +}) diff --git a/lib/renderer/api/ipc-renderer.js b/lib/renderer/api/ipc-renderer.js new file mode 100644 index 00000000000..a6b6b1851ea --- /dev/null +++ b/lib/renderer/api/ipc-renderer.js @@ -0,0 +1,21 @@ +'use strict' + +const binding = process.atomBinding('ipc') +const v8Util = process.atomBinding('v8_util') + +// Created by init.js. +const ipcRenderer = v8Util.getHiddenValue(global, 'ipc') + +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) +} + +module.exports = ipcRenderer diff --git a/lib/renderer/api/ipc.js b/lib/renderer/api/ipc.js new file mode 100644 index 00000000000..25519a308da --- /dev/null +++ b/lib/renderer/api/ipc.js @@ -0,0 +1,27 @@ +const ipcRenderer = require('electron').ipcRenderer +const deprecate = require('electron').deprecate +const EventEmitter = require('events').EventEmitter + +// This module is deprecated, we mirror everything from ipcRenderer. +deprecate.warn('ipc module', 'require("electron").ipcRenderer') + +// Routes events of ipcRenderer. +var ipc = new EventEmitter() + +ipcRenderer.emit = function (channel, event, ...args) { + ipc.emit.apply(ipc, [channel].concat(args)) + return EventEmitter.prototype.emit.apply(ipcRenderer, arguments) +} + +// Deprecated. +for (var method in ipcRenderer) { + if (method.startsWith('send')) { + ipc[method] = ipcRenderer[method] + } +} + +deprecate.rename(ipc, 'sendChannel', 'send') + +deprecate.rename(ipc, 'sendChannelSync', 'sendSync') + +module.exports = ipc diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js new file mode 100644 index 00000000000..30b66983a7d --- /dev/null +++ b/lib/renderer/api/remote.js @@ -0,0 +1,311 @@ +'use strict' + +const ipcRenderer = require('electron').ipcRenderer +const CallbacksRegistry = require('electron').CallbacksRegistry +const v8Util = process.atomBinding('v8_util') +const IDWeakMap = process.atomBinding('id_weak_map').IDWeakMap + +const callbacksRegistry = new CallbacksRegistry() + +var includes = [].includes + +var remoteObjectCache = new IDWeakMap() + +// Check for circular reference. +var isCircular = function (field, visited) { + if (typeof field === 'object') { + if (includes.call(visited, field)) { + return true + } + visited.push(field) + } + return false +} + +// Convert the arguments object into an array of meta data. +var wrapArgs = function (args, visited) { + var valueToMeta + if (visited == null) { + visited = [] + } + valueToMeta = function (value) { + var field, prop, ret + if (Array.isArray(value)) { + return { + type: 'array', + value: wrapArgs(value, visited) + } + } else if (Buffer.isBuffer(value)) { + return { + type: 'buffer', + value: Array.prototype.slice.call(value, 0) + } + } else if (value instanceof Date) { + return { + type: 'date', + value: value.getTime() + } + } else if ((value != null ? value.constructor.name : void 0) === 'Promise') { + return { + type: 'promise', + then: valueToMeta(function (v) { value.then(v) }) + } + } else if ((value != null) && typeof value === 'object' && v8Util.getHiddenValue(value, 'atomId')) { + return { + type: 'remote-object', + id: v8Util.getHiddenValue(value, 'atomId') + } + } else if ((value != null) && typeof value === 'object') { + ret = { + type: 'object', + name: value.constructor.name, + members: [] + } + for (prop in value) { + field = value[prop] + ret.members.push({ + name: prop, + value: valueToMeta(isCircular(field, visited) ? null : field) + }) + } + return ret + } else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) { + return { + type: 'function-with-return-value', + value: valueToMeta(value()) + } + } else if (typeof value === 'function') { + return { + type: 'function', + id: callbacksRegistry.add(value), + location: v8Util.getHiddenValue(value, 'location') + } + } else { + return { + type: 'value', + value: value + } + } + } + return Array.prototype.slice.call(args).map(valueToMeta) +} + +// Populate object's members from descriptors. +// The |ref| will be kept referenced by |members|. +// This matches |getObjectMemebers| in rpc-server. +let setObjectMembers = function (ref, object, metaId, members) { + for (let member of members) { + if (object.hasOwnProperty(member.name)) continue + + let descriptor = { enumerable: member.enumerable } + if (member.type === 'method') { + let remoteMemberFunction = function () { + if (this && this.constructor === remoteMemberFunction) { + // Constructor call. + let ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, member.name, wrapArgs(arguments)) + return metaToValue(ret) + } else { + // Call member function. + let ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CALL', metaId, member.name, wrapArgs(arguments)) + return metaToValue(ret) + } + } + descriptor.get = function () { + remoteMemberFunction.ref = ref // The member should reference its object. + return remoteMemberFunction + } + // Enable monkey-patch the method + descriptor.set = function (value) { + remoteMemberFunction = value + return value + } + descriptor.configurable = true + } else if (member.type === 'get') { + descriptor.get = function () { + return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, member.name)) + } + + // Only set setter when it is writable. + if (member.writable) { + descriptor.set = function (value) { + ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_SET', metaId, member.name, value) + return value + } + } + } + + Object.defineProperty(object, member.name, descriptor) + } +} + +// Populate object's prototype from descriptor. +// This matches |getObjectPrototype| in rpc-server. +let setObjectPrototype = function (ref, object, metaId, descriptor) { + if (descriptor === null) return + let proto = {} + setObjectMembers(ref, proto, metaId, descriptor.members) + setObjectPrototype(ref, proto, metaId, descriptor.proto) + Object.setPrototypeOf(object, proto) +} + +// Convert meta data from browser into real value. +let metaToValue = function (meta) { + var el, i, len, ref1, results, ret + switch (meta.type) { + case 'value': + return meta.value + case 'array': + ref1 = meta.members + results = [] + for (i = 0, len = ref1.length; i < len; i++) { + el = ref1[i] + results.push(metaToValue(el)) + } + return results + case 'buffer': + return new Buffer(meta.value) + case 'promise': + return Promise.resolve({ + then: metaToValue(meta.then) + }) + case 'error': + return metaToPlainObject(meta) + case 'date': + return new Date(meta.value) + case 'exception': + throw new Error(meta.message + '\n' + meta.stack) + default: + if (remoteObjectCache.has(meta.id)) return remoteObjectCache.get(meta.id) + + if (meta.type === 'function') { + // A shadow class to represent the remote function object. + let remoteFunction = function () { + if (this && this.constructor === remoteFunction) { + // Constructor call. + let obj = ipcRenderer.sendSync('ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments)) + // Returning object in constructor will replace constructed object + // with the returned object. + // http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this + return metaToValue(obj) + } else { + // Function call. + let obj = ipcRenderer.sendSync('ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments)) + return metaToValue(obj) + } + } + ret = remoteFunction + } else { + ret = {} + } + + // Populate delegate members. + setObjectMembers(ret, ret, meta.id, meta.members) + // Populate delegate prototype. + setObjectPrototype(ret, ret, meta.id, meta.proto) + + // Set constructor.name to object's name. + Object.defineProperty(ret.constructor, 'name', { value: meta.name }) + + // Track delegate object's life time, and tell the browser to clean up + // when the object is GCed. + v8Util.setDestructor(ret, function () { + ipcRenderer.send('ATOM_BROWSER_DEREFERENCE', meta.id) + }) + + // Remember object's id. + v8Util.setHiddenValue(ret, 'atomId', meta.id) + remoteObjectCache.set(meta.id, ret) + return ret + } +} + +// Construct a plain object from the meta. +var metaToPlainObject = function (meta) { + var i, len, obj, ref1 + obj = (function () { + switch (meta.type) { + case 'error': + return new Error() + default: + return {} + } + })() + ref1 = meta.members + for (i = 0, len = ref1.length; i < len; i++) { + let {name, value} = ref1[i] + obj[name] = value + } + return obj +} + +// Browser calls a callback in renderer. +ipcRenderer.on('ATOM_RENDERER_CALLBACK', function (event, id, args) { + return callbacksRegistry.apply(id, metaToValue(args)) +}) + +// A callback in browser is released. +ipcRenderer.on('ATOM_RENDERER_RELEASE_CALLBACK', function (event, id) { + return 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. +var fn = function (name) { + return Object.defineProperty(exports, name, { + get: function () { + return exports.getBuiltin(name) + } + }) +} +for (var name in browserModules) { + fn(name) +} + +// Get remote module. +exports.require = function (module) { + return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_REQUIRE', module)) +} + +// Alias to remote.require('electron').xxx. +exports.getBuiltin = function (module) { + return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_GET_BUILTIN', module)) +} + +// Get current BrowserWindow. +exports.getCurrentWindow = function () { + return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WINDOW')) +} + +// Get current WebContents object. +exports.getCurrentWebContents = function () { + return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WEB_CONTENTS')) +} + +// Get a global object in browser. +exports.getGlobal = function (name) { + return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_GLOBAL', name)) +} + +// Get the process object in browser. +exports.__defineGetter__('process', function () { + return exports.getGlobal('process') +}) + +// Create a funtion that will return the specifed value when called in browser. +exports.createFunctionWithReturnValue = function (returnValue) { + var func + func = function () { + return returnValue + } + v8Util.setHiddenValue(func, 'returnValue', true) + return func +} + +// Get the guest WebContents from guestInstanceId. +exports.getGuestWebContents = function (guestInstanceId) { + var meta + meta = ipcRenderer.sendSync('ATOM_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId) + return metaToValue(meta) +} diff --git a/lib/renderer/api/screen.js b/lib/renderer/api/screen.js new file mode 100644 index 00000000000..9eecd49dc5b --- /dev/null +++ b/lib/renderer/api/screen.js @@ -0,0 +1 @@ +module.exports = require('electron').remote.screen diff --git a/lib/renderer/api/web-frame.js b/lib/renderer/api/web-frame.js new file mode 100644 index 00000000000..4a152d4f364 --- /dev/null +++ b/lib/renderer/api/web-frame.js @@ -0,0 +1,19 @@ +'use strict' + +const deprecate = require('electron').deprecate +const EventEmitter = require('events').EventEmitter + +const webFrame = process.atomBinding('web_frame').webFrame + +// webFrame is an EventEmitter. +Object.setPrototypeOf(webFrame, EventEmitter.prototype) + +// Lots of webview would subscribe to webFrame's events. +webFrame.setMaxListeners(0) + +// Deprecated. +deprecate.rename(webFrame, 'registerUrlSchemeAsSecure', 'registerURLSchemeAsSecure') +deprecate.rename(webFrame, 'registerUrlSchemeAsBypassingCSP', 'registerURLSchemeAsBypassingCSP') +deprecate.rename(webFrame, 'registerUrlSchemeAsPrivileged', 'registerURLSchemeAsPrivileged') + +module.exports = webFrame diff --git a/lib/renderer/chrome-api.js b/lib/renderer/chrome-api.js new file mode 100644 index 00000000000..719066a6fae --- /dev/null +++ b/lib/renderer/chrome-api.js @@ -0,0 +1,13 @@ +const url = require('url') +const chrome = window.chrome = window.chrome || {} + +chrome.extension = { + getURL: function (path) { + return url.format({ + protocol: window.location.protocol, + slashes: true, + hostname: window.location.hostname, + pathname: path + }) + } +} diff --git a/lib/renderer/init.js b/lib/renderer/init.js new file mode 100644 index 00000000000..02ac36c28e1 --- /dev/null +++ b/lib/renderer/init.js @@ -0,0 +1,139 @@ +'use strict' + +const events = require('events') +const path = require('path') +const Module = require('module') + +// We modified the original process.argv to let node.js load the +// atom-renderer.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') + +var globalPaths = Module.globalPaths + +if (!process.env.ELECTRON_HIDE_INTERNAL_MODULES) { + globalPaths.push(path.join(__dirname, 'api')) +} + +// Expose public APIs. +globalPaths.push(path.join(__dirname, 'api', 'exports')) + +// The global variable will be used by ipc for event dispatching +var v8Util = process.atomBinding('v8_util') + +v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter()) + +// Use electron module after everything is ready. +const electron = require('electron') + +// Call webFrame method. +electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => { + electron.webFrame[method].apply(electron.webFrame, args) +}) + +electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => { + const responseCallback = function (result) { + event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, result) + } + args.push(responseCallback) + electron.webFrame[method].apply(electron.webFrame, args) +}) + +// Process command line arguments. +var nodeIntegration = 'false' +var preloadScript = null + +var ref = process.argv +var i, len, arg +for (i = 0, len = ref.length; i < len; i++) { + arg = ref[i] + if (arg.indexOf('--guest-instance-id=') === 0) { + // This is a guest web view. + process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1)) + } else if (arg.indexOf('--opener-id=') === 0) { + // This is a guest BrowserWindow. + process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1)) + } else if (arg.indexOf('--node-integration=') === 0) { + nodeIntegration = arg.substr(arg.indexOf('=') + 1) + } else if (arg.indexOf('--preload=') === 0) { + preloadScript = arg.substr(arg.indexOf('=') + 1) + } +} + +if (window.location.protocol === 'chrome-devtools:') { + // Override some inspector APIs. + require('./inspector') + nodeIntegration = 'true' +} else if (window.location.protocol === 'chrome-extension:') { + // Add implementations of chrome API. + require('./chrome-api') + nodeIntegration = 'true' +} else { + // Override default web functions. + require('./override') + + // Load webview tag implementation. + if (process.guestInstanceId == null) { + require('./web-view/web-view') + require('./web-view/web-view-attributes') + } +} + +if (nodeIntegration === 'true') { + // 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 (window.location.protocol === 'file:') { + var pathname = process.platform === 'win32' && window.location.pathname[0] === '/' ? window.location.pathname.substr(1) : window.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 + } + + // Redirect window.onerror to uncaughtException. + window.onerror = function (message, filename, lineno, colno, error) { + if (global.process.listeners('uncaughtException').length > 0) { + global.process.emit('uncaughtException', error) + return true + } else { + return false + } + } +} else { + // Delete Node's symbols after the Environment has been loaded. + process.once('loaded', function () { + delete global.process + delete global.setImmediate + delete global.clearImmediate + return delete global.global + }) +} + +// Load the script specfied by the "preload" attribute. +if (preloadScript) { + try { + require(preloadScript) + } catch (error) { + if (error.code === 'MODULE_NOT_FOUND') { + console.error('Unable to load preload script ' + preloadScript) + } else { + console.error(error) + console.error(error.stack) + } + } +} diff --git a/lib/renderer/inspector.js b/lib/renderer/inspector.js new file mode 100644 index 00000000000..df151cf1b5e --- /dev/null +++ b/lib/renderer/inspector.js @@ -0,0 +1,81 @@ +window.onload = function () { + // Use menu API to show context menu. + window.InspectorFrontendHost.showContextMenuAtPoint = createMenu + + // Use dialog API to override file chooser dialog. + return (window.WebInspector.createFileSelectorElement = createFileSelectorElement) +} + +var convertToMenuTemplate = function (items) { + var fn, i, item, len, template + template = [] + fn = function (item) { + var transformed + transformed = item.type === 'subMenu' ? { + type: 'submenu', + label: item.label, + enabled: item.enabled, + submenu: convertToMenuTemplate(item.subItems) + } : item.type === 'separator' ? { + type: 'separator' + } : item.type === 'checkbox' ? { + type: 'checkbox', + label: item.label, + enabled: item.enabled, + checked: item.checked + } : { + type: 'normal', + label: item.label, + enabled: item.enabled + } + if (item.id != null) { + transformed.click = function () { + window.DevToolsAPI.contextMenuItemSelected(item.id) + return window.DevToolsAPI.contextMenuCleared() + } + } + return template.push(transformed) + } + for (i = 0, len = items.length; i < len; i++) { + item = items[i] + fn(item) + } + return template +} + +var createMenu = function (x, y, items) { + const remote = require('electron').remote + const Menu = remote.Menu + const menu = Menu.buildFromTemplate(convertToMenuTemplate(items)) + + // The menu is expected to show asynchronously. + return setTimeout(function () { + return menu.popup(remote.getCurrentWindow()) + }) +} + +var showFileChooserDialog = function (callback) { + var dialog, files, remote + remote = require('electron').remote + dialog = remote.dialog + files = dialog.showOpenDialog({}) + if (files != null) { + return callback(pathToHtml5FileObject(files[0])) + } +} + +var pathToHtml5FileObject = function (path) { + var blob, fs + fs = require('fs') + blob = new Blob([fs.readFileSync(path)]) + blob.name = path + return blob +} + +var createFileSelectorElement = function (callback) { + var fileSelectorElement + fileSelectorElement = document.createElement('span') + fileSelectorElement.style.display = 'none' + fileSelectorElement.click = showFileChooserDialog.bind(this, callback) + return fileSelectorElement +} diff --git a/lib/renderer/override.js b/lib/renderer/override.js new file mode 100644 index 00000000000..20c993d0bc5 --- /dev/null +++ b/lib/renderer/override.js @@ -0,0 +1,267 @@ +'use strict' + +const ipcRenderer = require('electron').ipcRenderer +const remote = require('electron').remote + +// Cache browser window visibility +var _isVisible = true +var _isMinimized = false +var initWindow = function initWindow () { + var currentWindow + currentWindow = remote.getCurrentWindow() + _isVisible = currentWindow.isVisible() + _isMinimized = currentWindow.isMinimized() +} +initWindow() + +// Helper function to resolve relative url. +var a = window.top.document.createElement('a') + +var resolveURL = function (url) { + a.href = url + return a.href +} + +// Window object returned by "window.open". +var BrowserWindowProxy = (function () { + BrowserWindowProxy.proxies = {} + + BrowserWindowProxy.getOrCreate = function (guestId) { + var base + return (base = this.proxies)[guestId] != null ? base[guestId] : base[guestId] = new BrowserWindowProxy(guestId) + } + + BrowserWindowProxy.remove = function (guestId) { + return delete this.proxies[guestId] + } + + function BrowserWindowProxy (guestId1) { + this.guestId = guestId1 + this.closed = false + ipcRenderer.once('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + this.guestId, () => { + BrowserWindowProxy.remove(this.guestId) + this.closed = true + }) + } + + BrowserWindowProxy.prototype.close = function () { + return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId) + } + + BrowserWindowProxy.prototype.focus = function () { + return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus') + } + + BrowserWindowProxy.prototype.blur = function () { + return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur') + } + + Object.defineProperty(BrowserWindowProxy.prototype, 'location', { + get: function () { + return ipcRenderer.sendSync('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'getURL') + }, + set: function (url) { + return ipcRenderer.sendSync('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'loadURL', url) + } + }) + + BrowserWindowProxy.prototype.postMessage = function (message, targetOrigin) { + if (targetOrigin == null) { + targetOrigin = '*' + } + return ipcRenderer.send('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, window.location.origin) + } + + BrowserWindowProxy.prototype['eval'] = function (...args) { + return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript'].concat(args)) + } + + return BrowserWindowProxy +})() + +if (process.guestInstanceId == null) { + // Override default window.close. + window.close = function () { + return remote.getCurrentWindow().close() + } +} + +// Make the browser window or guest view emit "new-window" event. +window.open = function (url, frameName, features) { + var feature, guestId, i, j, len, len1, name, options, ref1, ref2, value + if (frameName == null) { + frameName = '' + } + if (features == null) { + features = '' + } + options = {} + + // TODO remove hyphenated options in both of the following arrays for 1.0 + const ints = ['x', 'y', 'width', 'height', 'min-width', 'minWidth', 'max-width', 'maxWidth', 'min-height', 'minHeight', 'max-height', 'maxHeight', 'zoom-factor', 'zoomFactor'] + const webPreferences = ['zoom-factor', 'zoomFactor', 'node-integration', 'nodeIntegration', 'preload'] + const disposition = 'new-window' + + // Make sure to get rid of excessive whitespace in the property name + ref1 = features.split(/,\s*/) + for (i = 0, len = ref1.length; i < len; i++) { + feature = ref1[i] + ref2 = feature.split(/\s*=/) + name = ref2[0] + value = ref2[1] + value = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value + if (webPreferences.includes(name)) { + if (options.webPreferences == null) { + options.webPreferences = {} + } + options.webPreferences[name] = value + } else { + options[name] = value + } + } + if (options.left) { + if (options.x == null) { + options.x = options.left + } + } + if (options.top) { + if (options.y == null) { + options.y = options.top + } + } + if (options.title == null) { + options.title = frameName + } + if (options.width == null) { + options.width = 800 + } + if (options.height == null) { + options.height = 600 + } + + // Resolve relative urls. + url = resolveURL(url) + for (j = 0, len1 = ints.length; j < len1; j++) { + name = ints[j] + if (options[name] != null) { + options[name] = parseInt(options[name], 10) + } + } + guestId = ipcRenderer.sendSync('ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, disposition, options) + if (guestId) { + return BrowserWindowProxy.getOrCreate(guestId) + } else { + return null + } +} + +// Use the dialog API to implement alert(). +window.alert = function (message, title) { + var buttons + if (arguments.length === 0) { + message = '' + } + if (title == null) { + title = '' + } + buttons = ['OK'] + message = String(message) + remote.dialog.showMessageBox(remote.getCurrentWindow(), { + message: message, + title: title, + buttons: buttons + }) + +// Alert should always return undefined. +} + +// And the confirm(). +window.confirm = function (message, title) { + var buttons, cancelId + if (title == null) { + title = '' + } + buttons = ['OK', 'Cancel'] + cancelId = 1 + return !remote.dialog.showMessageBox(remote.getCurrentWindow(), { + message: message, + title: title, + buttons: buttons, + cancelId: cancelId + }) +} + +// But we do not support prompt(). +window.prompt = function () { + throw new Error('prompt() is and will not be supported.') +} + +if (process.openerId != null) { + window.opener = BrowserWindowProxy.getOrCreate(process.openerId) +} + +ipcRenderer.on('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, isVisible, isMinimized) { + var hasChanged = _isVisible !== isVisible || _isMinimized !== isMinimized + + if (hasChanged) { + _isVisible = isVisible + _isMinimized = isMinimized + + document.dispatchEvent(new Event('visibilitychange')) + } +}) + +ipcRenderer.on('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) { + // Manually dispatch event instead of using postMessage because we also need to + // set event.source. + event = document.createEvent('Event') + event.initEvent('message', false, false) + event.data = message + event.origin = sourceOrigin + event.source = BrowserWindowProxy.getOrCreate(sourceId) + return window.dispatchEvent(event) +}) + +// Forward history operations to browser. +var sendHistoryOperation = function (...args) { + return ipcRenderer.send.apply(ipcRenderer, ['ATOM_SHELL_NAVIGATION_CONTROLLER'].concat(args)) +} + +var getHistoryOperation = function (...args) { + return ipcRenderer.sendSync.apply(ipcRenderer, ['ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER'].concat(args)) +} + +window.history.back = function () { + return sendHistoryOperation('goBack') +} + +window.history.forward = function () { + return sendHistoryOperation('goForward') +} + +window.history.go = function (offset) { + return sendHistoryOperation('goToOffset', offset) +} + +Object.defineProperty(window.history, 'length', { + get: function () { + return getHistoryOperation('length') + } +}) + +// Make document.hidden and document.visibilityState return the correct value. +Object.defineProperty(document, 'hidden', { + get: function () { + return _isMinimized || !_isVisible + } +}) + +Object.defineProperty(document, 'visibilityState', { + get: function () { + if (_isVisible && !_isMinimized) { + return 'visible' + } else { + return 'hidden' + } + } +}) diff --git a/lib/renderer/web-view/guest-view-internal.js b/lib/renderer/web-view/guest-view-internal.js new file mode 100644 index 00000000000..f4023d22701 --- /dev/null +++ b/lib/renderer/web-view/guest-view-internal.js @@ -0,0 +1,106 @@ +'use strict' + +const ipcRenderer = require('electron').ipcRenderer +const webFrame = require('electron').webFrame + +var requestId = 0 + +var WEB_VIEW_EVENTS = { + 'load-commit': ['url', 'isMainFrame'], + 'did-finish-load': [], + 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL'], + 'did-frame-finish-load': ['isMainFrame'], + 'did-start-loading': [], + 'did-stop-loading': [], + 'did-get-response-details': ['status', 'newURL', 'originalURL', 'httpResponseCode', 'requestMethod', 'referrer', 'headers'], + 'did-get-redirect-request': ['oldURL', 'newURL', 'isMainFrame'], + 'dom-ready': [], + 'console-message': ['level', 'message', 'line', 'sourceId'], + 'devtools-opened': [], + 'devtools-closed': [], + 'devtools-focused': [], + 'new-window': ['url', 'frameName', 'disposition', 'options'], + 'will-navigate': ['url'], + 'did-navigate': ['url'], + 'did-navigate-in-page': ['url'], + 'close': [], + 'crashed': [], + 'gpu-crashed': [], + 'plugin-crashed': ['name', 'version'], + 'media-started-playing': [], + 'media-paused': [], + 'did-change-theme-color': ['themeColor'], + 'destroyed': [], + 'page-title-updated': ['title', 'explicitSet'], + 'page-favicon-updated': ['favicons'], + 'enter-html-full-screen': [], + 'leave-html-full-screen': [], + 'found-in-page': ['result'] +} + +var DEPRECATED_EVENTS = { + 'page-title-updated': 'page-title-set' +} + +var dispatchEvent = function (webView, eventName, eventKey, ...args) { + var domEvent, f, i, j, len, ref1 + if (DEPRECATED_EVENTS[eventName] != null) { + dispatchEvent.apply(null, [webView, DEPRECATED_EVENTS[eventName], eventKey].concat(args)) + } + domEvent = new Event(eventName) + ref1 = WEB_VIEW_EVENTS[eventKey] + for (i = j = 0, len = ref1.length; j < len; i = ++j) { + f = ref1[i] + domEvent[f] = args[i] + } + webView.dispatchEvent(domEvent) + if (eventName === 'load-commit') { + return webView.onLoadCommit(domEvent) + } +} + +module.exports = { + registerEvents: function (webView, viewInstanceId) { + ipcRenderer.on('ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + viewInstanceId, function (event, eventName, ...args) { + return dispatchEvent.apply(null, [webView, eventName, eventName].concat(args)) + }) + + ipcRenderer.on('ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + viewInstanceId, function (event, channel, ...args) { + var domEvent = new Event('ipc-message') + domEvent.channel = channel + domEvent.args = args + return webView.dispatchEvent(domEvent) + }) + + return ipcRenderer.on('ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + viewInstanceId, function (event, ...args) { + var domEvent, f, i, j, len, ref1 + domEvent = new Event('size-changed') + ref1 = ['oldWidth', 'oldHeight', 'newWidth', 'newHeight'] + for (i = j = 0, len = ref1.length; j < len; i = ++j) { + f = ref1[i] + domEvent[f] = args[i] + } + return webView.onSizeChanged(domEvent) + }) + }, + deregisterEvents: function (viewInstanceId) { + ipcRenderer.removeAllListeners('ATOM_SHELL_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + viewInstanceId) + ipcRenderer.removeAllListeners('ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + viewInstanceId) + return ipcRenderer.removeAllListeners('ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + viewInstanceId) + }, + createGuest: function (params, callback) { + requestId++ + ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId) + return ipcRenderer.once('ATOM_SHELL_RESPONSE_' + requestId, callback) + }, + attachGuest: function (elementInstanceId, guestInstanceId, params) { + ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', elementInstanceId, guestInstanceId, params) + return webFrame.attachGuest(elementInstanceId) + }, + destroyGuest: function (guestInstanceId) { + return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId) + }, + setSize: function (guestInstanceId, params) { + return ipcRenderer.send('ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params) + } +} diff --git a/atom/renderer/lib/web-view/web-view-attributes.js b/lib/renderer/web-view/web-view-attributes.js similarity index 54% rename from atom/renderer/lib/web-view/web-view-attributes.js rename to lib/renderer/web-view/web-view-attributes.js index 4ad4bd72501..c76e0d26f95 100644 --- a/atom/renderer/lib/web-view/web-view-attributes.js +++ b/lib/renderer/web-view/web-view-attributes.js @@ -1,113 +1,87 @@ -'use strict'; +'use strict' -const WebViewImpl = require('./web-view'); -const guestViewInternal = require('./guest-view-internal'); -const webViewConstants = require('./web-view-constants'); -const remote = require('electron').remote; +const WebViewImpl = require('./web-view') +const guestViewInternal = require('./guest-view-internal') +const webViewConstants = require('./web-view-constants') +const remote = require('electron').remote // Helper function to resolve url set in attribute. -var a = document.createElement('a'); +var a = document.createElement('a') -var resolveURL = function(url) { - a.href = url; - return a.href; -}; +var resolveURL = function (url) { + a.href = url + return a.href +} // Attribute objects. // Default implementation of a WebView attribute. class WebViewAttribute { - constructor(name, webViewImpl) { - this.name = name; - this.value = webViewImpl.webviewNode[name] || ''; - this.webViewImpl = webViewImpl; - this.ignoreMutation = false; - this.defineProperty(); + constructor (name, webViewImpl) { + this.name = name + this.value = webViewImpl.webviewNode[name] || '' + this.webViewImpl = webViewImpl + this.ignoreMutation = false + this.defineProperty() } // Retrieves and returns the attribute's value. - getValue() { - return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value; + getValue () { + return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value } // Sets the attribute's value. - setValue(value) { - return this.webViewImpl.webviewNode.setAttribute(this.name, value || ''); + setValue (value) { + return this.webViewImpl.webviewNode.setAttribute(this.name, value || '') } // Changes the attribute's value without triggering its mutation handler. - setValueIgnoreMutation(value) { - this.ignoreMutation = true; - this.setValue(value); - return this.ignoreMutation = false; + setValueIgnoreMutation (value) { + this.ignoreMutation = true + this.setValue(value) + this.ignoreMutation = false } // Defines this attribute as a property on the webview node. - defineProperty() { + defineProperty () { return Object.defineProperty(this.webViewImpl.webviewNode, this.name, { - get: (function(_this) { - return function() { - return _this.getValue(); - }; - })(this), - set: (function(_this) { - return function(value) { - return _this.setValue(value); - }; - })(this), + get: () => { + return this.getValue() + }, + set: (value) => { + return this.setValue(value) + }, enumerable: true - }); + }) } // Called when the attribute's value changes. - handleMutation() {} + handleMutation () {} } // An attribute that is treated as a Boolean. class BooleanAttribute extends WebViewAttribute { - constructor(name, webViewImpl) { - super(name, webViewImpl); + getValue () { + return this.webViewImpl.webviewNode.hasAttribute(this.name) } - getValue() { - return this.webViewImpl.webviewNode.hasAttribute(this.name); - } - - setValue(value) { + setValue (value) { if (!value) { - return this.webViewImpl.webviewNode.removeAttribute(this.name); + return this.webViewImpl.webviewNode.removeAttribute(this.name) } else { - return this.webViewImpl.webviewNode.setAttribute(this.name, ''); + return this.webViewImpl.webviewNode.setAttribute(this.name, '') } } } -// Attribute that specifies whether transparency is allowed in the webview. -class AllowTransparencyAttribute extends BooleanAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, webViewImpl); - } - - handleMutation() { - if (!this.webViewImpl.guestInstanceId) { - return; - } - return guestViewInternal.setAllowTransparency(this.webViewImpl.guestInstanceId, this.getValue()); - } -} - // Attribute used to define the demension limits of autosizing. class AutosizeDimensionAttribute extends WebViewAttribute { - constructor(name, webViewImpl) { - super(name, webViewImpl); + getValue () { + return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name)) || 0 } - getValue() { - return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name)) || 0; - } - - handleMutation() { + handleMutation () { if (!this.webViewImpl.guestInstanceId) { - return; + return } return guestViewInternal.setSize(this.webViewImpl.guestInstanceId, { enableAutoSize: this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue(), @@ -119,203 +93,194 @@ class AutosizeDimensionAttribute extends WebViewAttribute { width: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() || 0), height: parseInt(this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() || 0) } - }); + }) } } - // Attribute that specifies whether the webview should be autosized. class AutosizeAttribute extends BooleanAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_AUTOSIZE, webViewImpl) } } -AutosizeAttribute.prototype.handleMutation = AutosizeDimensionAttribute.prototype.handleMutation; +AutosizeAttribute.prototype.handleMutation = AutosizeDimensionAttribute.prototype.handleMutation // Attribute representing the state of the storage partition. class PartitionAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_PARTITION, webViewImpl); - this.validPartitionId = true; + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_PARTITION, webViewImpl) + this.validPartitionId = true } - handleMutation(oldValue, newValue) { - newValue = newValue || ''; + handleMutation (oldValue, newValue) { + newValue = newValue || '' // The partition cannot change if the webview has already navigated. if (!this.webViewImpl.beforeFirstNavigation) { - window.console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED); - this.setValueIgnoreMutation(oldValue); - return; + window.console.error(webViewConstants.ERROR_MSG_ALREADY_NAVIGATED) + this.setValueIgnoreMutation(oldValue) + return } if (newValue === 'persist:') { - this.validPartitionId = false; - return window.console.error(webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE); + this.validPartitionId = false + return window.console.error(webViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE) } } } // Attribute that handles the location and navigation of the webview. class SrcAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_SRC, webViewImpl); - this.setupMutationObserver(); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_SRC, webViewImpl) + this.setupMutationObserver() } - getValue() { + getValue () { if (this.webViewImpl.webviewNode.hasAttribute(this.name)) { - return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)); + return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)) } else { - return this.value; + return this.value } } - setValueIgnoreMutation(value) { - super.setValueIgnoreMutation(value); + setValueIgnoreMutation (value) { + super.setValueIgnoreMutation(value) // takeRecords() is needed to clear queued up src mutations. Without it, it // is possible for this change to get picked up asyncronously by src's // mutation observer |observer|, and then get handled even though we do not // want to handle this mutation. - return this.observer.takeRecords(); + return this.observer.takeRecords() } - handleMutation(oldValue, newValue) { - + handleMutation (oldValue, newValue) { // Once we have navigated, we don't allow clearing the src attribute. // Once enters a navigated state, it cannot return to a // placeholder state. if (!newValue && oldValue) { - // src attribute changes normally initiate a navigation. We suppress // the next src attribute handler call to avoid reloading the page // on every guest-initiated navigation. - this.setValueIgnoreMutation(oldValue); - return; + this.setValueIgnoreMutation(oldValue) + return } - return this.parse(); + return this.parse() } // The purpose of this mutation observer is to catch assignment to the src // attribute without any changes to its value. This is useful in the case // where the webview guest has crashed and navigating to the same address // spawns off a new process. - setupMutationObserver() { - var params; - this.observer = new MutationObserver((function(_this) { - return function(mutations) { - var i, len, mutation, newValue, oldValue; - for (i = 0, len = mutations.length; i < len; i++) { - mutation = mutations[i]; - oldValue = mutation.oldValue; - newValue = _this.getValue(); - if (oldValue !== newValue) { - return; - } - _this.handleMutation(oldValue, newValue); + setupMutationObserver () { + var params + this.observer = new MutationObserver((mutations) => { + var i, len, mutation, newValue, oldValue + for (i = 0, len = mutations.length; i < len; i++) { + mutation = mutations[i] + oldValue = mutation.oldValue + newValue = this.getValue() + if (oldValue !== newValue) { + return } - }; - })(this)); + this.handleMutation(oldValue, newValue) + } + }) params = { attributes: true, attributeOldValue: true, attributeFilter: [this.name] - }; - return this.observer.observe(this.webViewImpl.webviewNode, params); + } + return this.observer.observe(this.webViewImpl.webviewNode, params) } - parse() { - var guestContents, httpreferrer, opts, useragent; + parse () { + var guestContents, httpreferrer, opts, useragent if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) { - return; + return } if (this.webViewImpl.guestInstanceId == null) { if (this.webViewImpl.beforeFirstNavigation) { - this.webViewImpl.beforeFirstNavigation = false; - this.webViewImpl.createGuest(); + this.webViewImpl.beforeFirstNavigation = false + this.webViewImpl.createGuest() } - return; + return } // Navigate to |this.src|. - opts = {}; - httpreferrer = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue(); + opts = {} + httpreferrer = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue() if (httpreferrer) { - opts.httpReferrer = httpreferrer; + opts.httpReferrer = httpreferrer } - useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue(); + useragent = this.webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue() if (useragent) { - opts.userAgent = useragent; + opts.userAgent = useragent } - guestContents = remote.getGuestWebContents(this.webViewImpl.guestInstanceId); - return guestContents.loadURL(this.getValue(), opts); + guestContents = remote.getGuestWebContents(this.webViewImpl.guestInstanceId) + return guestContents.loadURL(this.getValue(), opts) } } // Attribute specifies HTTP referrer. class HttpReferrerAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl) } } // Attribute specifies user agent class UserAgentAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl) } } // Attribute that set preload script. class PreloadAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_PRELOAD, webViewImpl) } - getValue() { - var preload, protocol; + getValue () { + var preload, protocol if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) { - return this.value; + return this.value } - preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)); - protocol = preload.substr(0, 5); + preload = resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name)) + protocol = preload.substr(0, 5) if (protocol !== 'file:') { - console.error(webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE); - preload = ''; + console.error(webViewConstants.ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE) + preload = '' } - return preload; + return preload } } // Attribute that specifies the blink features to be enabled. class BlinkFeaturesAttribute extends WebViewAttribute { - constructor(webViewImpl) { - super(webViewConstants.ATTRIBUTE_BLINKFEATURES, webViewImpl); + constructor (webViewImpl) { + super(webViewConstants.ATTRIBUTE_BLINKFEATURES, webViewImpl) } } // Sets up all of the webview attributes. -WebViewImpl.prototype.setupWebViewAttributes = function() { - var attribute, autosizeAttributes, i, len, results; - this.attributes = {}; - this.attributes[webViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY] = new AllowTransparencyAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this); - this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this); - this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this); - this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this); - this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this); - this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this); - autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]; - results = []; - for (i = 0, len = autosizeAttributes.length; i < len; i++) { - attribute = autosizeAttributes[i]; - results.push(this.attributes[attribute] = new AutosizeDimensionAttribute(attribute, this)); - } - return results; -}; +WebViewImpl.prototype.setupWebViewAttributes = function () { + this.attributes = {} + this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE] = new AutosizeAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this) + this.attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this) + this.attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this) + this.attributes[webViewConstants.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_ALLOWPOPUPS, this) + this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this) + this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this) + + const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH] + autosizeAttributes.forEach((attribute) => { + this.attributes[attribute] = new AutosizeDimensionAttribute(attribute, this) + }) +} diff --git a/atom/renderer/lib/web-view/web-view-constants.js b/lib/renderer/web-view/web-view-constants.js similarity index 95% rename from atom/renderer/lib/web-view/web-view-constants.js rename to lib/renderer/web-view/web-view-constants.js index de2a571f5d5..5501f4f3599 100644 --- a/atom/renderer/lib/web-view/web-view-constants.js +++ b/lib/renderer/web-view/web-view-constants.js @@ -1,6 +1,5 @@ module.exports = { // Attributes. - ATTRIBUTE_ALLOWTRANSPARENCY: 'allowtransparency', ATTRIBUTE_AUTOSIZE: 'autosize', ATTRIBUTE_MAXHEIGHT: 'maxheight', ATTRIBUTE_MAXWIDTH: 'maxwidth', @@ -26,4 +25,4 @@ module.exports = { ERROR_MSG_CANNOT_INJECT_SCRIPT: ': ' + 'Script cannot be injected into content until the page has loaded.', ERROR_MSG_INVALID_PARTITION_ATTRIBUTE: 'Invalid partition attribute.', ERROR_MSG_INVALID_PRELOAD_ATTRIBUTE: 'Only "file:" protocol is supported in "preload" attribute.' -}; +} diff --git a/lib/renderer/web-view/web-view.js b/lib/renderer/web-view/web-view.js new file mode 100644 index 00000000000..0e96d0c0643 --- /dev/null +++ b/lib/renderer/web-view/web-view.js @@ -0,0 +1,461 @@ +'use strict' + +const deprecate = require('electron').deprecate +const webFrame = require('electron').webFrame +const remote = require('electron').remote +const ipcRenderer = require('electron').ipcRenderer + +const v8Util = process.atomBinding('v8_util') +const guestViewInternal = require('./guest-view-internal') +const webViewConstants = require('./web-view-constants') + +var hasProp = {}.hasOwnProperty + +// ID generator. +var nextId = 0 + +var getNextId = function () { + return ++nextId +} + +// Represents the internal state of the WebView node. +var WebViewImpl = (function () { + function WebViewImpl (webviewNode) { + var shadowRoot + this.webviewNode = webviewNode + v8Util.setHiddenValue(this.webviewNode, 'internal', this) + this.attached = false + this.elementAttached = false + this.beforeFirstNavigation = true + + // on* Event handlers. + this.on = {} + this.browserPluginNode = this.createBrowserPluginNode() + shadowRoot = this.webviewNode.createShadowRoot() + shadowRoot.innerHTML = '' + this.setupWebViewAttributes() + 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) + } + + WebViewImpl.prototype.createBrowserPluginNode = function () { + // We create BrowserPlugin as a custom element in order to observe changes + // to attributes synchronously. + var browserPluginNode + browserPluginNode = new WebViewImpl.BrowserPlugin() + v8Util.setHiddenValue(browserPluginNode, 'internal', this) + return browserPluginNode + } + + // Resets some state upon reattaching element to the DOM. + WebViewImpl.prototype.reset = function () { + // Unlisten the zoom-level-changed event. + webFrame.removeListener('zoom-level-changed', this.onZoomLevelChanged) + + // 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 + // guestInstanceId has yet to be initialized. This means that we have not + // heard back from createGuest yet. We will not reset the flag in this case so + // that we don't end up allocating a second guest. + if (this.guestInstanceId) { + guestViewInternal.destroyGuest(this.guestInstanceId) + this.webContents = null + this.guestInstanceId = void 0 + this.beforeFirstNavigation = true + this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true + } + this.internalInstanceId = 0 + } + + // Sets the .request property. + WebViewImpl.prototype.setRequestPropertyOnWebViewNode = function (request) { + return Object.defineProperty(this.webviewNode, 'request', { + value: request, + enumerable: true + }) + } + + WebViewImpl.prototype.setupFocusPropagation = function () { + if (!this.webviewNode.hasAttribute('tabIndex')) { + // needs a tabIndex in order to be focusable. + // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute + // to allow to be focusable. + // See http://crbug.com/231664. + this.webviewNode.setAttribute('tabIndex', -1) + } + + // Focus the BrowserPlugin when the takes focus. + this.webviewNode.addEventListener('focus', () => { + this.browserPluginNode.focus() + }) + + // Blur the BrowserPlugin when the loses focus. + this.webviewNode.addEventListener('blur', () => { + this.browserPluginNode.blur() + }) + } + + // This observer monitors mutations to attributes of the and + // updates the BrowserPlugin properties accordingly. In turn, updating + // a BrowserPlugin property will update the corresponding BrowserPlugin + // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more + // details. + WebViewImpl.prototype.handleWebviewAttributeMutation = function (attributeName, oldValue, newValue) { + if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) { + return + } + + // Let the changed attribute handle its own mutation + return this.attributes[attributeName].handleMutation(oldValue, newValue) + } + + WebViewImpl.prototype.handleBrowserPluginAttributeMutation = function (attributeName, oldValue, newValue) { + if (attributeName === webViewConstants.ATTRIBUTE_INTERNALINSTANCEID && !oldValue && !!newValue) { + this.browserPluginNode.removeAttribute(webViewConstants.ATTRIBUTE_INTERNALINSTANCEID) + this.internalInstanceId = parseInt(newValue) + + // Track when the element resizes using the element resize callback. + webFrame.registerElementResizeCallback(this.internalInstanceId, this.onElementResize.bind(this)) + if (!this.guestInstanceId) { + return + } + return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()) + } + } + + WebViewImpl.prototype.onSizeChanged = function (webViewEvent) { + var maxHeight, maxWidth, minHeight, minWidth, newHeight, newWidth, node, width + newWidth = webViewEvent.newWidth + newHeight = webViewEvent.newHeight + node = this.webviewNode + width = node.offsetWidth + + // Check the current bounds to make sure we do not resize + // outside of current constraints. + maxWidth = this.attributes[webViewConstants.ATTRIBUTE_MAXWIDTH].getValue() | width + maxHeight = this.attributes[webViewConstants.ATTRIBUTE_MAXHEIGHT].getValue() | width + minWidth = this.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width + minHeight = this.attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width + minWidth = Math.min(minWidth, maxWidth) + minHeight = Math.min(minHeight, maxHeight) + if (!this.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() || (newWidth >= minWidth && newWidth <= maxWidth && newHeight >= minHeight && newHeight <= maxHeight)) { + node.style.width = newWidth + 'px' + node.style.height = newHeight + 'px' + + // Only fire the DOM event if the size of the has actually + // changed. + return this.dispatchEvent(webViewEvent) + } + } + + WebViewImpl.prototype.onElementResize = function (newSize) { + // Dispatch the 'resize' event. + var resizeEvent + resizeEvent = new Event('resize', { + bubbles: true + }) + resizeEvent.newWidth = newSize.width + resizeEvent.newHeight = newSize.height + this.dispatchEvent(resizeEvent) + if (this.guestInstanceId) { + return guestViewInternal.setSize(this.guestInstanceId, { + normal: newSize + }) + } + } + + WebViewImpl.prototype.createGuest = function () { + return guestViewInternal.createGuest(this.buildParams(), (event, guestInstanceId) => { + this.attachWindow(guestInstanceId) + }) + } + + WebViewImpl.prototype.dispatchEvent = function (webViewEvent) { + return this.webviewNode.dispatchEvent(webViewEvent) + } + + // Adds an 'on' property on the webview, which can be used to set/unset + // an event handler. + WebViewImpl.prototype.setupEventProperty = function (eventName) { + var propertyName + propertyName = 'on' + eventName.toLowerCase() + return Object.defineProperty(this.webviewNode, propertyName, { + get: () => { + return this.on[propertyName] + }, + set: (value) => { + if (this.on[propertyName]) { + this.webviewNode.removeEventListener(eventName, this.on[propertyName]) + } + this.on[propertyName] = value + if (value) { + return this.webviewNode.addEventListener(eventName, value) + } + }, + enumerable: true + }) + } + + // Updates state upon loadcommit. + WebViewImpl.prototype.onLoadCommit = function (webViewEvent) { + var newValue, oldValue + oldValue = this.webviewNode.getAttribute(webViewConstants.ATTRIBUTE_SRC) + newValue = webViewEvent.url + if (webViewEvent.isMainFrame && (oldValue !== newValue)) { + // Touching the src attribute triggers a navigation. To avoid + // triggering a page reload on every guest-initiated navigation, + // we do not handle this mutation. + return this.attributes[webViewConstants.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue) + } + } + + WebViewImpl.prototype.onAttach = function (storagePartitionId) { + return this.attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue(storagePartitionId) + } + + WebViewImpl.prototype.buildParams = function () { + var attribute, attributeName, css, elementRect, params, ref1 + params = { + instanceId: this.viewInstanceId, + userAgentOverride: this.userAgentOverride + } + ref1 = this.attributes + for (attributeName in ref1) { + if (!hasProp.call(ref1, attributeName)) continue + attribute = ref1[attributeName] + params[attributeName] = attribute.getValue() + } + + // When the WebView is not participating in layout (display:none) + // then getBoundingClientRect() would report a width and height of 0. + // However, in the case where the WebView has a fixed size we can + // use that value to initially size the guest so as to avoid a relayout of + // the on display:block. + css = window.getComputedStyle(this.webviewNode, null) + elementRect = this.webviewNode.getBoundingClientRect() + params.elementWidth = parseInt(elementRect.width) || parseInt(css.getPropertyValue('width')) + params.elementHeight = parseInt(elementRect.height) || parseInt(css.getPropertyValue('height')) + return params + } + + WebViewImpl.prototype.attachWindow = function (guestInstanceId) { + this.guestInstanceId = guestInstanceId + this.webContents = remote.getGuestWebContents(this.guestInstanceId) + if (!this.internalInstanceId) { + return true + } + return guestViewInternal.attachGuest(this.internalInstanceId, this.guestInstanceId, this.buildParams()) + } + + return WebViewImpl +})() + +// Registers browser plugin custom element. +var registerBrowserPluginElement = function () { + var proto + proto = Object.create(HTMLObjectElement.prototype) + proto.createdCallback = function () { + this.setAttribute('type', 'application/browser-plugin') + this.setAttribute('id', 'browser-plugin-' + getNextId()) + + // The node fills in the container. + this.style.flex = '1 1 auto' + } + proto.attributeChangedCallback = function (name, oldValue, newValue) { + var internal + internal = v8Util.getHiddenValue(this, 'internal') + if (!internal) { + return + } + return internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue) + } + proto.attachedCallback = function () { + // Load the plugin immediately. + return this.nonExistentAttribute + } + WebViewImpl.BrowserPlugin = webFrame.registerEmbedderCustomElement('browserplugin', { + 'extends': 'object', + prototype: proto + }) + delete proto.createdCallback + delete proto.attachedCallback + delete proto.detachedCallback + return delete proto.attributeChangedCallback +} + +// Registers custom element. +var registerWebViewElement = function () { + var createBlockHandler, createNonBlockHandler, i, j, len, len1, m, methods, nonblockMethods, proto + 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') + if (!internal) { + return + } + return internal.handleWebviewAttributeMutation(name, oldValue, newValue) + } + proto.detachedCallback = function () { + var internal + internal = v8Util.getHiddenValue(this, 'internal') + if (!internal) { + return + } + guestViewInternal.deregisterEvents(internal.viewInstanceId) + internal.elementAttached = false + return internal.reset() + } + proto.attachedCallback = function () { + var internal + internal = v8Util.getHiddenValue(this, 'internal') + if (!internal) { + return + } + if (!internal.elementAttached) { + guestViewInternal.registerEvents(internal, internal.viewInstanceId) + internal.elementAttached = true + return internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse() + } + } + + // Public-facing API methods. + methods = [ + 'getURL', + 'loadURL', + 'getTitle', + 'isLoading', + 'isWaitingForResponse', + 'stop', + 'reload', + 'reloadIgnoringCache', + 'canGoBack', + 'canGoForward', + 'canGoToOffset', + 'clearHistory', + 'goBack', + 'goForward', + 'goToIndex', + 'goToOffset', + 'isCrashed', + 'setUserAgent', + 'getUserAgent', + 'openDevTools', + 'closeDevTools', + 'isDevToolsOpened', + 'isDevToolsFocused', + 'inspectElement', + 'setAudioMuted', + 'isAudioMuted', + 'undo', + 'redo', + 'cut', + 'copy', + 'paste', + 'pasteAndMatchStyle', + 'delete', + 'selectAll', + 'unselect', + 'replace', + 'replaceMisspelling', + 'findInPage', + 'stopFindInPage', + 'getId', + 'downloadURL', + 'inspectServiceWorker', + 'print', + 'printToPDF' + ] + nonblockMethods = [ + 'insertCSS', + 'insertText', + 'send', + 'sendInputEvent', + 'setZoomFactor', + 'setZoomLevel', + 'setZoomLevelLimits' + ] + + // Forward proto.foo* method calls to WebViewImpl.foo*. + createBlockHandler = function (m) { + return function (...args) { + const internal = v8Util.getHiddenValue(this, 'internal') + if (internal.webContents) { + return internal.webContents[m].apply(internal.webContents, args) + } else { + throw new Error(`Cannot call ${m} because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.`) + } + } + } + for (i = 0, len = methods.length; i < len; i++) { + m = methods[i] + proto[m] = createBlockHandler(m) + } + createNonBlockHandler = function (m) { + return function (...args) { + const internal = v8Util.getHiddenValue(this, 'internal') + return ipcRenderer.send.apply(ipcRenderer, ['ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', null, internal.guestInstanceId, m].concat(args)) + } + } + for (j = 0, len1 = nonblockMethods.length; j < len1; j++) { + m = nonblockMethods[j] + proto[m] = createNonBlockHandler(m) + } + + proto.executeJavaScript = function (code, hasUserGesture, callback) { + var internal = v8Util.getHiddenValue(this, 'internal') + if (typeof hasUserGesture === 'function') { + callback = hasUserGesture + hasUserGesture = false + } + let requestId = getNextId() + ipcRenderer.send('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', requestId, internal.guestInstanceId, 'executeJavaScript', code, hasUserGesture) + ipcRenderer.once(`ATOM_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, function (event, result) { + if (callback) callback(result) + }) + } + + // WebContents associated with this webview. + proto.getWebContents = function () { + var internal = v8Util.getHiddenValue(this, 'internal') + return internal.webContents + } + + // Deprecated. + deprecate.rename(proto, 'getUrl', 'getURL') + window.WebView = webFrame.registerEmbedderCustomElement('webview', { + prototype: proto + }) + + // Delete the callbacks so developers cannot call them and produce unexpected + // behavior. + delete proto.createdCallback + delete proto.attachedCallback + delete proto.detachedCallback + return delete proto.attributeChangedCallback +} + +var useCapture = true + +var listener = function (event) { + if (document.readyState === 'loading') { + return + } + registerBrowserPluginElement() + registerWebViewElement() + return window.removeEventListener(event.type, listener, useCapture) +} + +window.addEventListener('readystatechange', listener, true) + +module.exports = WebViewImpl diff --git a/package.json b/package.json index 5d897230851..c12f3d37c42 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,33 @@ { "name": "electron", - "version": "0.36.4", + "version": "0.37.3", "devDependencies": { - "asar": "^0.9.0", - "eslint": "^1.10.3", - "request": "*" + "asar": "^0.10.0", + "request": "*", + "standard": "^6.0.8" }, "optionalDependencies": { "runas": "^3.0.0" }, + "standard": { + "ignore": [ + "/out", + "/spec", + "/vendor" + ], + "env": { + "browser": true + } + }, "private": true, "scripts": { - "lint": "python ./script/eslint.py && python ./script/cpplint.py", + "bootstrap": "python ./script/bootstrap.py", + "build": "python ./script/build.py -c D", + "lint": "npm run lint-js && npm run lint-cpp", + "lint-js": "standard && standard spec", + "lint-cpp": "python ./script/cpplint.py", "preinstall": "node -e 'process.exit(0)'", + "repl": "python ./script/start.py --interactive", "start": "python ./script/start.py", "test": "python ./script/test.py" } diff --git a/script/bootstrap.py b/script/bootstrap.py index 6eaf635bfd8..1efea3c2897 100755 --- a/script/bootstrap.py +++ b/script/bootstrap.py @@ -43,7 +43,7 @@ def main(): args.libcc_source_path, args.libcc_shared_library_path, args.libcc_static_library_path) - if args.target_arch in ['arm', 'ia32'] and PLATFORM == 'linux': + if PLATFORM == 'linux': download_sysroot(args.target_arch) create_chrome_version_h() @@ -166,10 +166,11 @@ def update_clang(): def download_sysroot(target_arch): if target_arch == 'ia32': target_arch = 'i386' + if target_arch == 'x64': + target_arch = 'amd64' execute_stdout([os.path.join(SOURCE_ROOT, 'script', 'install-sysroot.py'), '--arch', target_arch]) - def create_chrome_version_h(): version_file = os.path.join(SOURCE_ROOT, 'vendor', 'brightray', 'vendor', 'libchromiumcontent', 'VERSION') diff --git a/script/bump-version.py b/script/bump-version.py index e2c22e8cd9d..5c70d4494e8 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -44,13 +44,13 @@ def increase_version(versions, index): def update_atom_gyp(version): pattern = re.compile(" *'version%' *: *'[0-9.]+'") - with open('atom.gyp', 'r') as f: + with open('electron.gyp', 'r') as f: lines = f.readlines() for i in range(0, len(lines)): if pattern.match(lines[i]): lines[i] = " 'version%': '{0}',\n".format(version) - with open('atom.gyp', 'w') as f: + with open('electron.gyp', 'w') as f: f.write(''.join(lines)) return diff --git a/script/cibuild b/script/cibuild index 391b859a3d2..54cfa508910 100755 --- a/script/cibuild +++ b/script/cibuild @@ -62,9 +62,10 @@ def main(): args += ['--dev'] run_script('bootstrap.py', args) - run_script('cpplint.py') - run_script('eslint.py') if PLATFORM != 'win32': + sys.stderr.write('\nRunning `npm run lint`\n') + sys.stderr.flush() + execute([npm, 'run', 'lint']) run_script('pylint.py') if is_release: run_script('build.py', ['-c', 'R']) diff --git a/script/create-dist.py b/script/create-dist.py index a619e04d3ea..e25845432c7 100755 --- a/script/create-dist.py +++ b/script/create-dist.py @@ -38,8 +38,8 @@ TARGET_BINARIES = { 'libGLESv2.dll', 'msvcp120.dll', 'msvcr120.dll', + 'ffmpeg.dll', 'node.dll', - 'pdf.dll', 'content_resources_200_percent.pak', 'ui_resources_200_percent.pak', 'xinput1_3.dll', @@ -51,6 +51,7 @@ TARGET_BINARIES = { PROJECT_NAME, # 'electron' 'content_shell.pak', 'icudtl.dat', + 'libffmpeg.so', 'libnode.so', 'natives_blob.bin', 'snapshot_blob.bin', @@ -70,17 +71,11 @@ TARGET_DIRECTORIES = { ], } -SYSTEM_LIBRARIES = [ - 'libgcrypt.so', -] - def main(): rm_rf(DIST_DIR) os.makedirs(DIST_DIR) - target_arch = get_target_arch() - force_build() create_symbols() copy_binaries() @@ -90,13 +85,12 @@ def main(): if PLATFORM == 'linux': strip_binaries() - if target_arch != 'arm': - copy_system_libraries() create_version() create_dist_zip() create_chrome_binary_zip('chromedriver', get_chromedriver_version()) create_chrome_binary_zip('mksnapshot', ATOM_SHELL_VERSION) + create_ffmpeg_zip() create_symbols_zip() @@ -142,21 +136,6 @@ def strip_binaries(): execute([strip, os.path.join(DIST_DIR, binary)]) -def copy_system_libraries(): - executable_path = os.path.join(OUT_DIR, PROJECT_NAME) # our/R/electron - ldd = execute(['ldd', executable_path]) - lib_re = re.compile('\t(.*) => (.+) \(.*\)$') - for line in ldd.splitlines(): - m = lib_re.match(line) - if not m: - continue - for i, library in enumerate(SYSTEM_LIBRARIES): - real_library = m.group(1) - if real_library.startswith(library): - shutil.copyfile(m.group(2), os.path.join(DIST_DIR, real_library)) - SYSTEM_LIBRARIES[i] = real_library - - def create_version(): version_path = os.path.join(SOURCE_ROOT, 'dist', 'version') with open(version_path, 'w') as version_file: @@ -183,8 +162,6 @@ def create_dist_zip(): with scoped_cwd(DIST_DIR): files = TARGET_BINARIES[PLATFORM] + ['LICENSE', 'LICENSES.chromium.html', 'version'] - if PLATFORM == 'linux': - files += [lib for lib in SYSTEM_LIBRARIES if os.path.exists(lib)] dirs = TARGET_DIRECTORIES[PLATFORM] make_zip(zip_file, files, dirs) @@ -203,6 +180,24 @@ def create_chrome_binary_zip(binary, version): make_zip(zip_file, files, []) +def create_ffmpeg_zip(): + dist_name = 'ffmpeg-{0}-{1}-{2}.zip'.format( + ATOM_SHELL_VERSION, get_platform_key(), get_target_arch()) + zip_file = os.path.join(SOURCE_ROOT, 'dist', dist_name) + + if PLATFORM == 'darwin': + ffmpeg_name = 'libffmpeg.dylib' + elif PLATFORM == 'linux': + ffmpeg_name = 'libffmpeg.so' + elif PLATFORM == 'win32': + ffmpeg_name = 'ffmpeg.dll' + + shutil.copy2(os.path.join(CHROMIUM_DIR, '..', 'ffmpeg', ffmpeg_name), + DIST_DIR) + with scoped_cwd(DIST_DIR): + make_zip(zip_file, [ffmpeg_name, 'LICENSE', 'LICENSES.chromium.html'], []) + + def create_symbols_zip(): dist_name = '{0}-{1}-{2}-{3}-symbols.zip'.format(PROJECT_NAME, ATOM_SHELL_VERSION, diff --git a/script/eslint.py b/script/eslint.py deleted file mode 100755 index 8fa2c3a7e3a..00000000000 --- a/script/eslint.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python - -import glob -import os -import sys - -from lib.util import execute - - -SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) - - -def main(): - os.chdir(SOURCE_ROOT) - - eslint = os.path.join(SOURCE_ROOT, 'node_modules', '.bin', 'eslint') - if sys.platform in ['win32', 'cygwin']: - eslint += '.cmd' - settings = ['--quiet', '--config'] - - sourceConfig = os.path.join('script', 'eslintrc-base.json') - sourceFiles = ['atom'] - execute([eslint] + settings + [sourceConfig] + sourceFiles) - - specConfig = os.path.join('script', 'eslintrc-spec.json') - specFiles = glob.glob('spec/*.js') - execute([eslint] + settings + [specConfig] + specFiles) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/script/eslintrc-base.json b/script/eslintrc-base.json deleted file mode 100644 index f968a2d20c7..00000000000 --- a/script/eslintrc-base.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "rules": { - "indent": [ - 2, - 2, - { - "SwitchCase": 1 - } - ], - "quotes": [ - 0, - "single" - ], - "semi": [ - 2, - "always" - ], - "comma-dangle": 0, - "linebreak-style": 0, - "no-console": 0, - "no-undef": 2, - "no-unused-vars": 2 - }, - "env": { - "es6": true, - "node": true, - "browser": true - }, - "extends": "eslint:recommended", - "globals": { - "DevToolsAPI": false, - "InspectorFrontendHost": false, - "WebInspector": false, - "WebView": false - } -} diff --git a/script/eslintrc-spec.json b/script/eslintrc-spec.json deleted file mode 100644 index 5aa9df265d6..00000000000 --- a/script/eslintrc-spec.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": { - "jquery": true, - "mocha": true - }, - "extends": "./eslintrc-base.json" -} diff --git a/script/install-sysroot.py b/script/install-sysroot.py index 69acfb13269..be68fbad0ad 100755 --- a/script/install-sysroot.py +++ b/script/install-sysroot.py @@ -30,15 +30,15 @@ from lib.util import get_host_arch SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) URL_PREFIX = 'https://github.com' URL_PATH = 'atom/debian-sysroot-image-creator/releases/download' -REVISION_AMD64 = 264817 -REVISION_I386 = 'v0.2.0' -REVISION_ARM = 'v0.1.0' +REVISION_AMD64 = 'v0.4.0' +REVISION_I386 = 'v0.4.0' +REVISION_ARM = 'v0.4.0' TARBALL_AMD64 = 'debian_wheezy_amd64_sysroot.tgz' TARBALL_I386 = 'debian_wheezy_i386_sysroot.tgz' TARBALL_ARM = 'debian_wheezy_arm_sysroot.tgz' -TARBALL_AMD64_SHA1SUM = '74b7231e12aaf45c5c5489d9aebb56bd6abb3653' -TARBALL_I386_SHA1SUM = 'f5b2ceaeb3f7e6bc2058733585fe877d002b5fa7' -TARBALL_ARM_SHA1SUM = '72e668c57b8591e108759584942ddb6f6cee1322' +TARBALL_AMD64_SHA1SUM = 'a7e8faa99b681317969ac450a27233925bdeed62' +TARBALL_I386_SHA1SUM = '9fc827eddc26e562c0a0b2586be5dc075e570e10' +TARBALL_ARM_SHA1SUM = 'bfa4233708ab937d682a14e8d87ddba3cadb6eae' SYSROOT_DIR_AMD64 = 'debian_wheezy_amd64-sysroot' SYSROOT_DIR_I386 = 'debian_wheezy_i386-sysroot' SYSROOT_DIR_ARM = 'debian_wheezy_arm-sysroot' diff --git a/script/lib/config.py b/script/lib/config.py index 279fa7340a9..83960e68e2c 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -8,7 +8,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' -LIBCHROMIUMCONTENT_COMMIT = 'ad63d8ba890bcaad2f1b7e6de148b7992f4d3af7' +LIBCHROMIUMCONTENT_COMMIT = 'd41c1e48f428257d99abcf29fd9f26928e9fc53e' PLATFORM = { 'cygwin': 'win32', diff --git a/script/lib/util.py b/script/lib/util.py index fc52d316d30..4e5fb90988d 100644 --- a/script/lib/util.py +++ b/script/lib/util.py @@ -180,7 +180,7 @@ def execute_stdout(argv, env=os.environ): def atom_gyp(): SOURCE_ROOT = os.path.abspath(os.path.join(__file__, '..', '..', '..')) - gyp = os.path.join(SOURCE_ROOT, 'atom.gyp') + gyp = os.path.join(SOURCE_ROOT, 'electron.gyp') with open(gyp) as f: obj = eval(f.read()); return obj['variables'] diff --git a/script/test.py b/script/test.py index 7f75d3113d6..28aeac9dc1f 100755 --- a/script/test.py +++ b/script/test.py @@ -16,9 +16,6 @@ PRODUCT_NAME = atom_gyp()['product_name%'] def main(): os.chdir(SOURCE_ROOT) - # Disable old APIs - os.environ['ELECTRON_HIDE_INTERNAL_MODULES'] = 'true' - config = 'D' if len(sys.argv) == 2 and sys.argv[1] == '-R': config = 'R' diff --git a/script/update.py b/script/update.py index e91e8401cbf..33a4cff50f6 100755 --- a/script/update.py +++ b/script/update.py @@ -68,7 +68,7 @@ def run_gyp(target_arch, component): '-Dmas_build={0}'.format(mas_build), ] return subprocess.call([python, gyp, '-f', 'ninja', '--depth', '.', - 'atom.gyp', '-Icommon.gypi'] + defines, env=env) + 'electron.gyp', '-Icommon.gypi'] + defines, env=env) if __name__ == '__main__': diff --git a/script/upload.py b/script/upload.py index 3245d9caaa7..d23bc554c3c 100755 --- a/script/upload.py +++ b/script/upload.py @@ -35,9 +35,6 @@ DSYM_NAME = '{0}-{1}-{2}-{3}-dsym.zip'.format(PROJECT_NAME, ATOM_SHELL_VERSION, get_platform_key(), get_target_arch()) -MKSNAPSHOT_NAME = 'mksnapshot-{0}-{1}-{2}.zip'.format(ATOM_SHELL_VERSION, - get_platform_key(), - get_target_arch()) def main(): @@ -89,12 +86,19 @@ def main(): if PLATFORM == 'darwin': upload_atom_shell(github, release, os.path.join(DIST_DIR, DSYM_NAME)) + # Upload free version of ffmpeg. + ffmpeg = 'ffmpeg-{0}-{1}-{2}.zip'.format( + ATOM_SHELL_VERSION, get_platform_key(), get_target_arch()) + upload_atom_shell(github, release, os.path.join(DIST_DIR, ffmpeg)) + # Upload chromedriver and mksnapshot for minor version update. if parse_version(args.version)[2] == '0': chromedriver = 'chromedriver-{0}-{1}-{2}.zip'.format( get_chromedriver_version(), get_platform_key(), get_target_arch()) upload_atom_shell(github, release, os.path.join(DIST_DIR, chromedriver)) - upload_atom_shell(github, release, os.path.join(DIST_DIR, MKSNAPSHOT_NAME)) + mksnapshot = 'mksnapshot-{0}-{1}-{2}.zip'.format( + ATOM_SHELL_VERSION, get_platform_key(), get_target_arch()) + upload_atom_shell(github, release, os.path.join(DIST_DIR, mksnapshot)) if PLATFORM == 'win32' and not tag_exists: # Upload node headers. diff --git a/spec/api-app-spec.js b/spec/api-app-spec.js index dafaf44a2d1..c237ef17238 100644 --- a/spec/api-app-spec.js +++ b/spec/api-app-spec.js @@ -1,111 +1,135 @@ -var BrowserWindow, ChildProcess, app, assert, path, ref, remote; +const assert = require('assert') +const ChildProcess = require('child_process') +const path = require('path') +const remote = require('electron').remote -assert = require('assert'); +const app = remote.require('electron').app +const BrowserWindow = remote.require('electron').BrowserWindow -ChildProcess = require('child_process'); +describe('electron module', function () { + it('allows old style require by default', function () { + require('shell') + }) -path = require('path'); + it('can prevent exposing internal modules to require', function (done) { + const electron = require('electron') + const clipboard = require('clipboard') + assert.equal(typeof clipboard, 'object') + electron.hideInternalModules() + try { + require('clipboard') + } catch (err) { + assert.equal(err.message, "Cannot find module 'clipboard'") + done() + } + }) +}) -remote = require('electron').remote; +describe('app module', function () { + describe('app.getVersion()', function () { + it('returns the version field of package.json', function () { + assert.equal(app.getVersion(), '0.1.0') + }) + }) -ref = remote.require('electron'), app = ref.app, BrowserWindow = ref.BrowserWindow; + describe('app.setVersion(version)', function () { + it('overrides the version', function () { + assert.equal(app.getVersion(), '0.1.0') + app.setVersion('test-version') + assert.equal(app.getVersion(), 'test-version') + app.setVersion('0.1.0') + }) + }) -describe('app module', function() { - describe('app.getVersion()', function() { - return it('returns the version field of package.json', function() { - return assert.equal(app.getVersion(), '0.1.0'); - }); - }); - describe('app.setVersion(version)', function() { - return it('overrides the version', function() { - assert.equal(app.getVersion(), '0.1.0'); - app.setVersion('test-version'); - assert.equal(app.getVersion(), 'test-version'); - return app.setVersion('0.1.0'); - }); - }); - describe('app.getName()', function() { - return it('returns the name field of package.json', function() { - return assert.equal(app.getName(), 'Electron Test'); - }); - }); - describe('app.setName(name)', function() { - return it('overrides the name', function() { - assert.equal(app.getName(), 'Electron Test'); - app.setName('test-name'); - assert.equal(app.getName(), 'test-name'); - return app.setName('Electron Test'); - }); - }); - describe('app.getLocale()', function() { - return it('should not be empty', function() { - return assert.notEqual(app.getLocale(), ''); - }); - }); - describe('app.exit(exitCode)', function() { - var appProcess; - appProcess = null; - afterEach(function() { - return appProcess != null ? appProcess.kill() : void 0; - }); - return it('emits a process exit event with the code', function(done) { - var appPath, electronPath, output; - appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app'); - electronPath = remote.getGlobal('process').execPath; - appProcess = ChildProcess.spawn(electronPath, [appPath]); - output = ''; - appProcess.stdout.on('data', function(data) { - return output += data; - }); - return appProcess.on('close', function(code) { + describe('app.getName()', function () { + it('returns the name field of package.json', function () { + assert.equal(app.getName(), 'Electron Test') + }) + }) + + describe('app.setName(name)', function () { + it('overrides the name', function () { + assert.equal(app.getName(), 'Electron Test') + app.setName('test-name') + assert.equal(app.getName(), 'test-name') + app.setName('Electron Test') + }) + }) + + describe('app.getLocale()', function () { + it('should not be empty', function () { + assert.notEqual(app.getLocale(), '') + }) + }) + + describe('app.exit(exitCode)', function () { + var appProcess = null + + afterEach(function () { + appProcess != null ? appProcess.kill() : void 0 + }) + + it('emits a process exit event with the code', function (done) { + var appPath = path.join(__dirname, 'fixtures', 'api', 'quit-app') + var electronPath = remote.getGlobal('process').execPath + var output = '' + appProcess = ChildProcess.spawn(electronPath, [appPath]) + appProcess.stdout.on('data', function (data) { + output += data + }) + appProcess.on('close', function (code) { if (process.platform !== 'win32') { - assert.notEqual(output.indexOf('Exit event with code: 123'), -1); + assert.notEqual(output.indexOf('Exit event with code: 123'), -1) } - assert.equal(code, 123); - return done(); - }); - }); - }); - return describe('BrowserWindow events', function() { - var w; - w = null; - afterEach(function() { + assert.equal(code, 123) + done() + }) + }) + }) + + describe('BrowserWindow events', function () { + var w = null + + afterEach(function () { if (w != null) { - w.destroy(); + w.destroy() } - return w = null; - }); - it('should emit browser-window-focus event when window is focused', function(done) { - app.once('browser-window-focus', function(e, window) { - assert.equal(w.id, window.id); - return done(); - }); + w = null + }) + + it('should emit browser-window-focus event when window is focused', function (done) { + app.once('browser-window-focus', function (e, window) { + assert.equal(w.id, window.id) + done() + }) w = new BrowserWindow({ show: false - }); - return w.emit('focus'); - }); - it('should emit browser-window-blur event when window is blured', function(done) { - app.once('browser-window-blur', function(e, window) { - assert.equal(w.id, window.id); - return done(); - }); + }) + w.emit('focus') + }) + + it('should emit browser-window-blur event when window is blured', function (done) { + app.once('browser-window-blur', function (e, window) { + assert.equal(w.id, window.id) + done() + }) w = new BrowserWindow({ show: false - }); - return w.emit('blur'); - }); - return it('should emit browser-window-created event when window is created', function(done) { - app.once('browser-window-created', function(e, window) { - return setImmediate(function() { - assert.equal(w.id, window.id); - return done(); - }); - }); + }) + w.emit('blur') + }) + + it('should emit browser-window-created event when window is created', function (done) { + app.once('browser-window-created', function (e, window) { + setImmediate(function () { + assert.equal(w.id, window.id) + done() + }) + }) w = new BrowserWindow({ show: false - }); - return w.emit('blur'); - }); - }); -}); + }) + w.emit('blur') + }) + }) +}) diff --git a/spec/api-auto-updater-spec.js b/spec/api-auto-updater-spec.js new file mode 100644 index 00000000000..6335f335943 --- /dev/null +++ b/spec/api-auto-updater-spec.js @@ -0,0 +1,37 @@ +const assert = require('assert') +const autoUpdater = require('electron').remote.autoUpdater +const ipcRenderer = require('electron').ipcRenderer + +// Skip autoUpdater tests in MAS build. +if (!process.mas) { + describe('autoUpdater module', function () { + describe('checkForUpdates', function () { + it('emits an error on Windows when called the feed URL is not set', function (done) { + if (process.platform !== 'win32') { + return done() + } + + ipcRenderer.once('auto-updater-error', function (event, message) { + assert.equal(message, 'Update URL is not set') + done() + }) + autoUpdater.setFeedURL('') + autoUpdater.checkForUpdates() + }) + }) + + describe('setFeedURL', function () { + it('emits an error on Mac OS X when the application is unsigned', function (done) { + if (process.platform !== 'darwin') { + return done() + } + + ipcRenderer.once('auto-updater-error', function (event, message) { + assert.equal(message, 'Could not get code signature for running application') + done() + }) + autoUpdater.setFeedURL('') + }) + }) + }) +} diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 92f69d17bca..468586eb7bb 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1,494 +1,778 @@ -const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); -const os = require('os'); +'use strict' -const remote = require('electron').remote; -const screen = require('electron').screen; +const assert = require('assert') +const fs = require('fs') +const path = require('path') +const os = require('os') -const ipcMain = remote.require('electron').ipcMain; -const BrowserWindow = remote.require('electron').BrowserWindow; +const remote = require('electron').remote +const screen = require('electron').screen -const isCI = remote.getGlobal('isCi'); +const app = remote.require('electron').app +const ipcMain = remote.require('electron').ipcMain +const ipcRenderer = require('electron').ipcRenderer +const BrowserWindow = remote.require('electron').BrowserWindow -describe('browser-window module', function() { - var fixtures, w; - fixtures = path.resolve(__dirname, 'fixtures'); - w = null; - beforeEach(function() { +const isCI = remote.getGlobal('isCi') + +describe('browser-window module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + var w = null + + beforeEach(function () { if (w != null) { - w.destroy(); + w.destroy() } - return w = new BrowserWindow({ + w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - }); - afterEach(function() { + }) + }) + + afterEach(function () { if (w != null) { - w.destroy(); + w.destroy() } - return w = null; - }); - describe('BrowserWindow.close()', function() { - it('should emit unload handler', function(done) { - w.webContents.on('did-finish-load', function() { - return w.close(); - }); - w.on('closed', function() { - var content, test; - test = path.join(fixtures, 'api', 'unload'); - content = fs.readFileSync(test); - fs.unlinkSync(test); - assert.equal(String(content), 'unload'); - return done(); - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'unload.html')); - }); - return it('should emit beforeunload handler', function(done) { - w.on('onbeforeunload', function() { - return done(); - }); - w.webContents.on('did-finish-load', function() { - return w.close(); - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html')); - }); - }); - describe('window.close()', function() { - it('should emit unload handler', function(done) { - w.on('closed', function() { - var content, test; - test = path.join(fixtures, 'api', 'close'); - content = fs.readFileSync(test); - fs.unlinkSync(test); - assert.equal(String(content), 'close'); - return done(); - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'close.html')); - }); - return it('should emit beforeunload handler', function(done) { - w.on('onbeforeunload', function() { - return done(); - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')); - }); - }); - describe('BrowserWindow.destroy()', function() { - return it('prevents users to access methods of webContents', function() { - var webContents; - webContents = w.webContents; - w.destroy(); - return assert.throws((function() { - return webContents.getId(); - }), /Object has been destroyed/); - }); - }); - describe('BrowserWindow.loadURL(url)', function() { - it('should emit did-start-loading event', function(done) { - w.webContents.on('did-start-loading', function() { - return done(); - }); - return w.loadURL('about:blank'); - }); - return it('should emit did-fail-load event', function(done) { - w.webContents.on('did-fail-load', function() { - return done(); - }); - return w.loadURL('file://a.txt'); - }); - }); - describe('BrowserWindow.show()', function() { - return it('should focus on window', function() { - if (isCI) { - return; - } - w.show(); - return assert(w.isFocused()); - }); - }); - describe('BrowserWindow.showInactive()', function() { - return it('should not focus on window', function() { - w.showInactive(); - return assert(!w.isFocused()); - }); - }); - describe('BrowserWindow.focus()', function() { - return it('does not make the window become visible', function() { - assert.equal(w.isVisible(), false); - w.focus(); - return assert.equal(w.isVisible(), false); - }); - }); - describe('BrowserWindow.capturePage(rect, callback)', function() { - return it('calls the callback with a Buffer', function(done) { - return w.capturePage({ + w = null + }) + + describe('BrowserWindow.close()', function () { + it('should emit unload handler', function (done) { + w.webContents.on('did-finish-load', function () { + w.close() + }) + w.on('closed', function () { + var test = path.join(fixtures, 'api', 'unload') + var content = fs.readFileSync(test) + fs.unlinkSync(test) + assert.equal(String(content), 'unload') + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'unload.html')) + }) + + it('should emit beforeunload handler', function (done) { + w.on('onbeforeunload', function () { + done() + }) + w.webContents.on('did-finish-load', function () { + w.close() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'beforeunload-false.html')) + }) + }) + + describe('window.close()', function () { + it('should emit unload handler', function (done) { + w.on('closed', function () { + var test = path.join(fixtures, 'api', 'close') + var content = fs.readFileSync(test) + fs.unlinkSync(test) + assert.equal(String(content), 'close') + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close.html')) + }) + + it('should emit beforeunload handler', function (done) { + w.on('onbeforeunload', function () { + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')) + }) + }) + + describe('BrowserWindow.destroy()', function () { + it('prevents users to access methods of webContents', function () { + var webContents = w.webContents + w.destroy() + assert.throws(function () { + webContents.getId() + }, /Object has been destroyed/) + }) + }) + + describe('BrowserWindow.loadURL(url)', function () { + it('should emit did-start-loading event', function (done) { + w.webContents.on('did-start-loading', function () { + done() + }) + w.loadURL('about:blank') + }) + + it('should emit did-fail-load event for files that do not exist', function (done) { + w.webContents.on('did-fail-load', function (event, code) { + assert.equal(code, -6) + done() + }) + w.loadURL('file://a.txt') + }) + + it('should emit did-fail-load event for invalid URL', function (done) { + w.webContents.on('did-fail-load', function (event, code, desc) { + assert.equal(desc, 'ERR_INVALID_URL') + assert.equal(code, -300) + done() + }) + w.loadURL('http://example:port') + }) + }) + + describe('BrowserWindow.show()', function () { + if (isCI) { + return + } + + it('should focus on window', function () { + w.show() + assert(w.isFocused()) + }) + + it('should make the window visible', function () { + w.show() + assert(w.isVisible()) + }) + + it('emits when window is shown', function (done) { + this.timeout(10000) + w.once('show', function () { + assert.equal(w.isVisible(), true) + done() + }) + w.show() + }) + }) + + describe('BrowserWindow.hide()', function () { + if (isCI) { + return + } + + it('should defocus on window', function () { + w.hide() + assert(!w.isFocused()) + }) + + it('should make the window not visible', function () { + w.show() + w.hide() + assert(!w.isVisible()) + }) + + it('emits when window is hidden', function (done) { + this.timeout(10000) + w.show() + w.once('hide', function () { + assert.equal(w.isVisible(), false) + done() + }) + w.hide() + }) + }) + + describe('BrowserWindow.showInactive()', function () { + it('should not focus on window', function () { + w.showInactive() + assert(!w.isFocused()) + }) + }) + + describe('BrowserWindow.focus()', function () { + it('does not make the window become visible', function () { + assert.equal(w.isVisible(), false) + w.focus() + assert.equal(w.isVisible(), false) + }) + }) + + describe('BrowserWindow.blur()', function () { + it('removes focus from window', function () { + w.blur() + assert(!w.isFocused()) + }) + }) + + describe('BrowserWindow.capturePage(rect, callback)', function () { + it('calls the callback with a Buffer', function (done) { + w.capturePage({ x: 0, y: 0, width: 100, height: 100 - }, function(image) { - assert.equal(image.isEmpty(), true); - return done(); - }); - }); - }); - describe('BrowserWindow.setSize(width, height)', function() { - return it('sets the window size', function(done) { - var size; - size = [300, 400]; - w.once('resize', function() { - var newSize; - newSize = w.getSize(); - assert.equal(newSize[0], size[0]); - assert.equal(newSize[1], size[1]); - return done(); - }); - return w.setSize(size[0], size[1]); - }); - }); - describe('BrowserWindow.setPosition(x, y)', function() { - return it('sets the window position', function(done) { - var pos; - pos = [10, 10]; - w.once('move', function() { - var newPos; - newPos = w.getPosition(); - assert.equal(newPos[0], pos[0]); - assert.equal(newPos[1], pos[1]); - return done(); - }); - return w.setPosition(pos[0], pos[1]); - }); - }); - describe('BrowserWindow.setContentSize(width, height)', function() { - it('sets the content size', function() { - var after, size; - size = [400, 400]; - w.setContentSize(size[0], size[1]); - after = w.getContentSize(); - assert.equal(after[0], size[0]); - return assert.equal(after[1], size[1]); - }); - return it('works for framless window', function() { - var after, size; - w.destroy(); + }, function (image) { + assert.equal(image.isEmpty(), true) + done() + }) + }) + }) + + describe('BrowserWindow.setSize(width, height)', function () { + it('sets the window size', function (done) { + var size = [300, 400] + w.once('resize', function () { + var newSize = w.getSize() + assert.equal(newSize[0], size[0]) + assert.equal(newSize[1], size[1]) + done() + }) + w.setSize(size[0], size[1]) + }) + }) + + describe('BrowserWindow.setPosition(x, y)', function () { + it('sets the window position', function (done) { + var pos = [10, 10] + w.once('move', function () { + var newPos = w.getPosition() + assert.equal(newPos[0], pos[0]) + assert.equal(newPos[1], pos[1]) + done() + }) + w.setPosition(pos[0], pos[1]) + }) + }) + + describe('BrowserWindow.setContentSize(width, height)', function () { + it('sets the content size', function () { + var size = [400, 400] + w.setContentSize(size[0], size[1]) + var after = w.getContentSize() + assert.equal(after[0], size[0]) + assert.equal(after[1], size[1]) + }) + + it('works for framless window', function () { + w.destroy() w = new BrowserWindow({ show: false, frame: false, width: 400, height: 400 - }); - size = [400, 400]; - w.setContentSize(size[0], size[1]); - after = w.getContentSize(); - assert.equal(after[0], size[0]); - return assert.equal(after[1], size[1]); - }); - }); - describe('BrowserWindow.fromId(id)', function() { - return it('returns the window with id', function() { - return assert.equal(w.id, BrowserWindow.fromId(w.id).id); - }); - }); - describe('BrowserWindow.setResizable(resizable)', function() { - return it('does not change window size for frameless window', function() { - var s; - w.destroy(); - w = new BrowserWindow({ - show: true, - frame: false - }); - s = w.getSize(); - w.setResizable(!w.isResizable()); - return assert.deepEqual(s, w.getSize()); - }); - }); - describe('"useContentSize" option', function() { - it('make window created with content size when used', function() { - var contentSize; - w.destroy(); + }) + var size = [400, 400] + w.setContentSize(size[0], size[1]) + var after = w.getContentSize() + assert.equal(after[0], size[0]) + assert.equal(after[1], size[1]) + }) + }) + + describe('BrowserWindow.fromId(id)', function () { + it('returns the window with id', function () { + assert.equal(w.id, BrowserWindow.fromId(w.id).id) + }) + }) + + describe('"useContentSize" option', function () { + it('make window created with content size when used', function () { + w.destroy() w = new BrowserWindow({ show: false, width: 400, height: 400, useContentSize: true - }); - contentSize = w.getContentSize(); - assert.equal(contentSize[0], 400); - return assert.equal(contentSize[1], 400); - }); - it('make window created with window size when not used', function() { - var size; - size = w.getSize(); - assert.equal(size[0], 400); - return assert.equal(size[1], 400); - }); - return it('works for framless window', function() { - var contentSize, size; - w.destroy(); + }) + var contentSize = w.getContentSize() + assert.equal(contentSize[0], 400) + assert.equal(contentSize[1], 400) + }) + + it('make window created with window size when not used', function () { + var size = w.getSize() + assert.equal(size[0], 400) + assert.equal(size[1], 400) + }) + + it('works for framless window', function () { + w.destroy() w = new BrowserWindow({ show: false, frame: false, width: 400, height: 400, useContentSize: true - }); - contentSize = w.getContentSize(); - assert.equal(contentSize[0], 400); - assert.equal(contentSize[1], 400); - size = w.getSize(); - assert.equal(size[0], 400); - return assert.equal(size[1], 400); - }); - }); - describe('"title-bar-style" option', function() { + }) + var contentSize = w.getContentSize() + assert.equal(contentSize[0], 400) + assert.equal(contentSize[1], 400) + var size = w.getSize() + assert.equal(size[0], 400) + assert.equal(size[1], 400) + }) + }) + + describe('"title-bar-style" option', function () { if (process.platform !== 'darwin') { - return; + return } if (parseInt(os.release().split('.')[0]) < 14) { - return; + return } - it('creates browser window with hidden title bar', function() { - var contentSize; - w.destroy(); + + it('creates browser window with hidden title bar', function () { + w.destroy() w = new BrowserWindow({ show: false, width: 400, height: 400, titleBarStyle: 'hidden' - }); - contentSize = w.getContentSize(); - return assert.equal(contentSize[1], 400); - }); - return it('creates browser window with hidden inset title bar', function() { - var contentSize; - w.destroy(); + }) + var contentSize = w.getContentSize() + assert.equal(contentSize[1], 400) + }) + + it('creates browser window with hidden inset title bar', function () { + w.destroy() w = new BrowserWindow({ show: false, width: 400, height: 400, titleBarStyle: 'hidden-inset' - }); - contentSize = w.getContentSize(); - return assert.equal(contentSize[1], 400); - }); - }); - describe('"enableLargerThanScreen" option', function() { + }) + var contentSize = w.getContentSize() + assert.equal(contentSize[1], 400) + }) + }) + + describe('"enableLargerThanScreen" option', function () { if (process.platform === 'linux') { - return; + return } - beforeEach(function() { - w.destroy(); - return w = new BrowserWindow({ + + beforeEach(function () { + w.destroy() + w = new BrowserWindow({ show: true, width: 400, height: 400, enableLargerThanScreen: true - }); - }); - it('can move the window out of screen', function() { - var after; - w.setPosition(-10, -10); - after = w.getPosition(); - assert.equal(after[0], -10); - return assert.equal(after[1], -10); - }); - return it('can set the window larger than screen', function() { - var after, size; - size = screen.getPrimaryDisplay().size; - size.width += 100; - size.height += 100; - w.setSize(size.width, size.height); - after = w.getSize(); - assert.equal(after[0], size.width); - return assert.equal(after[1], size.height); - }); - }); - describe('"web-preferences" option', function() { - afterEach(function() { - return ipcMain.removeAllListeners('answer'); - }); - describe('"preload" option', function() { - return it('loads the script before other scripts in window', function(done) { - var preload; - preload = path.join(fixtures, 'module', 'set-global.js'); - ipcMain.once('answer', function(event, test) { - assert.equal(test, 'preload'); - return done(); - }); - w.destroy(); + }) + }) + + it('can move the window out of screen', function () { + w.setPosition(-10, -10) + var after = w.getPosition() + assert.equal(after[0], -10) + assert.equal(after[1], -10) + }) + + it('can set the window larger than screen', function () { + var size = screen.getPrimaryDisplay().size + size.width += 100 + size.height += 100 + w.setSize(size.width, size.height) + var after = w.getSize() + assert.equal(after[0], size.width) + assert.equal(after[1], size.height) + }) + }) + + describe('"web-preferences" option', function () { + afterEach(function () { + ipcMain.removeAllListeners('answer') + }) + + describe('"preload" option', function () { + it('loads the script before other scripts in window', function (done) { + var preload = path.join(fixtures, 'module', 'set-global.js') + ipcMain.once('answer', function (event, test) { + assert.equal(test, 'preload') + done() + }) + w.destroy() w = new BrowserWindow({ show: false, webPreferences: { preload: preload } - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html')); - }); - }); - return describe('"node-integration" option', function() { - return it('disables node integration when specified to false', function(done) { - var preload; - preload = path.join(fixtures, 'module', 'send-later.js'); - ipcMain.once('answer', function(event, test) { - assert.equal(test, 'undefined'); - return done(); - }); - w.destroy(); + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html')) + }) + }) + + 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') + done() + }) + w.destroy() w = new BrowserWindow({ show: false, webPreferences: { preload: preload, nodeIntegration: false } - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html')); - }); - }); - }); - describe('beforeunload handler', function() { - it('returning true would not prevent close', function(done) { - w.on('closed', function() { - return done(); - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-true.html')); - }); - it('returning non-empty string would not prevent close', function(done) { - w.on('closed', function() { - return done(); - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-string.html')); - }); - it('returning false would prevent close', function(done) { - w.on('onbeforeunload', function() { - return done(); - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')); - }); - return it('returning empty string would prevent close', function(done) { - w.on('onbeforeunload', function() { - return done(); - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html')); - }); - }); - describe('new-window event', function() { + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html')) + }) + }) + }) + + describe('beforeunload handler', function () { + it('returning true would not prevent close', function (done) { + w.on('closed', function () { + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-true.html')) + }) + + it('returning non-empty string would not prevent close', function (done) { + w.on('closed', function () { + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-string.html')) + }) + + it('returning false would prevent close', function (done) { + w.on('onbeforeunload', function () { + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html')) + }) + + it('returning empty string would prevent close', function (done) { + w.on('onbeforeunload', function () { + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html')) + }) + }) + + describe('new-window event', function () { if (isCI && process.platform === 'darwin') { - return; + return } - it('emits when window.open is called', function(done) { - w.webContents.once('new-window', function(e, url, frameName) { - e.preventDefault(); - assert.equal(url, 'http://host/'); - assert.equal(frameName, 'host'); - return done(); - }); - return w.loadURL("file://" + fixtures + "/pages/window-open.html"); - }); - return it('emits when link with target is called', function(done) { - this.timeout(10000); - w.webContents.once('new-window', function(e, url, frameName) { - e.preventDefault(); - assert.equal(url, 'http://host/'); - assert.equal(frameName, 'target'); - return done(); - }); - return w.loadURL("file://" + fixtures + "/pages/target-name.html"); - }); - }); - describe('maximize event', function() { - if (isCI) { - return; - } - return it('emits when window is maximized', function(done) { - this.timeout(10000); - w.once('maximize', function() { - return done(); - }); - w.show(); - return w.maximize(); - }); - }); - describe('unmaximize event', function() { - if (isCI) { - return; - } - return it('emits when window is unmaximized', function(done) { - this.timeout(10000); - w.once('unmaximize', function() { - return done(); - }); - w.show(); - w.maximize(); - return w.unmaximize(); - }); - }); - describe('minimize event', function() { - if (isCI) { - return; - } - return it('emits when window is minimized', function(done) { - this.timeout(10000); - w.once('minimize', function() { - return done(); - }); - w.show(); - return w.minimize(); - }); - }); - xdescribe('beginFrameSubscription method', function() { - return it('subscribes frame updates', function(done) { - w.loadURL("file://" + fixtures + "/api/blank.html"); - return w.webContents.beginFrameSubscription(function(data) { - assert.notEqual(data.length, 0); - w.webContents.endFrameSubscription(); - return done(); - }); - }); - }); - describe('savePage method', function() { - const savePageDir = path.join(fixtures, 'save_page'); - const savePageHtmlPath = path.join(savePageDir, 'save_page.html'); - const savePageJsPath = path.join(savePageDir, 'save_page_files', 'test.js'); - const savePageCssPath = path.join(savePageDir, 'save_page_files', 'test.css'); + it('emits when window.open is called', function (done) { + w.webContents.once('new-window', function (e, url, frameName) { + e.preventDefault() + assert.equal(url, 'http://host/') + assert.equal(frameName, 'host') + done() + }) + w.loadURL('file://' + fixtures + '/pages/window-open.html') + }) - after(function() { + it('emits when link with target is called', function (done) { + this.timeout(10000) + w.webContents.once('new-window', function (e, url, frameName) { + e.preventDefault() + assert.equal(url, 'http://host/') + assert.equal(frameName, 'target') + done() + }) + w.loadURL('file://' + fixtures + '/pages/target-name.html') + }) + }) + + describe('maximize event', function () { + if (isCI) { + return + } + + it('emits when window is maximized', function (done) { + this.timeout(10000) + w.once('maximize', function () { + done() + }) + w.show() + w.maximize() + }) + }) + + describe('unmaximize event', function () { + if (isCI) { + return + } + + it('emits when window is unmaximized', function (done) { + this.timeout(10000) + w.once('unmaximize', function () { + done() + }) + w.show() + w.maximize() + w.unmaximize() + }) + }) + + describe('minimize event', function () { + if (isCI) { + return + } + + it('emits when window is minimized', function (done) { + this.timeout(10000) + w.once('minimize', function () { + done() + }) + w.show() + w.minimize() + }) + }) + + describe('beginFrameSubscription method', function () { + this.timeout(20000) + + it('subscribes frame updates', function (done) { + let called = false + w.loadURL('file://' + fixtures + '/api/blank.html') + w.webContents.beginFrameSubscription(function (data) { + // This callback might be called twice. + if (called) return + called = true + + assert.notEqual(data.length, 0) + w.webContents.endFrameSubscription() + done() + }) + }) + }) + + describe('savePage method', function () { + const savePageDir = path.join(fixtures, 'save_page') + const savePageHtmlPath = path.join(savePageDir, 'save_page.html') + const savePageJsPath = path.join(savePageDir, 'save_page_files', 'test.js') + const savePageCssPath = path.join(savePageDir, 'save_page_files', 'test.css') + + after(function () { try { - fs.unlinkSync(savePageCssPath); - fs.unlinkSync(savePageJsPath); - fs.unlinkSync(savePageHtmlPath); - fs.rmdirSync(path.join(savePageDir, 'save_page_files')); - fs.rmdirSync(savePageDir); + fs.unlinkSync(savePageCssPath) + fs.unlinkSync(savePageJsPath) + fs.unlinkSync(savePageHtmlPath) + fs.rmdirSync(path.join(savePageDir, 'save_page_files')) + fs.rmdirSync(savePageDir) } catch (e) { // Ignore error } - }); + }) - it('should save page to disk', function(done) { - w.webContents.on('did-finish-load', function() { - w.webContents.savePage(savePageHtmlPath, 'HTMLComplete', function(error) { - assert.equal(error, null); - assert(fs.existsSync(savePageHtmlPath)); - assert(fs.existsSync(savePageJsPath)); - assert(fs.existsSync(savePageCssPath)); - done(); - }); - }); - w.loadURL("file://" + fixtures + "/pages/save_page/index.html"); - }); - }); + it('should save page to disk', function (done) { + w.webContents.on('did-finish-load', function () { + w.webContents.savePage(savePageHtmlPath, 'HTMLComplete', function (error) { + assert.equal(error, null) + assert(fs.existsSync(savePageHtmlPath)) + assert(fs.existsSync(savePageJsPath)) + assert(fs.existsSync(savePageCssPath)) + done() + }) + }) + w.loadURL('file://' + fixtures + '/pages/save_page/index.html') + }) + }) - describe('BrowserWindow options argument is optional', function() { - return it('should create a window with default size (800x600)', function() { - var size; - w.destroy(); - w = new BrowserWindow(); - size = w.getSize(); - assert.equal(size[0], 800); - return assert.equal(size[1], 600); - }); - }); -}); + describe('BrowserWindow options argument is optional', function () { + it('should create a window with default size (800x600)', function () { + w.destroy() + w = new BrowserWindow() + var size = w.getSize() + assert.equal(size[0], 800) + assert.equal(size[1], 600) + }) + }) + + describe('window states', function () { + describe('resizable state', function () { + it('can be changed with resizable option', function () { + w.destroy() + w = new BrowserWindow({show: false, resizable: false}) + assert.equal(w.isResizable(), false) + }) + + it('can be changed with setResizable method', function () { + assert.equal(w.isResizable(), true) + w.setResizable(false) + assert.equal(w.isResizable(), false) + w.setResizable(true) + assert.equal(w.isResizable(), true) + }) + }) + }) + + describe('window states (excluding Linux)', function () { + // Not implemented on Linux. + if (process.platform === 'linux') return + + describe('movable state', function () { + it('can be changed with movable option', function () { + w.destroy() + w = new BrowserWindow({show: false, movable: false}) + assert.equal(w.isMovable(), false) + }) + + it('can be changed with setMovable method', function () { + assert.equal(w.isMovable(), true) + w.setMovable(false) + assert.equal(w.isMovable(), false) + w.setMovable(true) + assert.equal(w.isMovable(), true) + }) + }) + + describe('minimizable state', function () { + it('can be changed with minimizable option', function () { + w.destroy() + w = new BrowserWindow({show: false, minimizable: false}) + assert.equal(w.isMinimizable(), false) + }) + + it('can be changed with setMinimizable method', function () { + assert.equal(w.isMinimizable(), true) + w.setMinimizable(false) + assert.equal(w.isMinimizable(), false) + w.setMinimizable(true) + assert.equal(w.isMinimizable(), true) + }) + }) + + describe('maximizable state', function () { + it('can be changed with maximizable option', function () { + w.destroy() + w = new BrowserWindow({show: false, maximizable: false}) + assert.equal(w.isMaximizable(), false) + }) + + it('can be changed with setMaximizable method', function () { + assert.equal(w.isMaximizable(), true) + w.setMaximizable(false) + assert.equal(w.isMaximizable(), false) + w.setMaximizable(true) + assert.equal(w.isMaximizable(), true) + }) + + it('is not affected when changing other states', function () { + w.setMaximizable(false) + assert.equal(w.isMaximizable(), false) + w.setMinimizable(false) + assert.equal(w.isMaximizable(), false) + w.setClosable(false) + assert.equal(w.isMaximizable(), false) + }) + }) + + describe('fullscreenable state', function () { + // Only implemented on OS X. + if (process.platform !== 'darwin') return + + it('can be changed with fullscreenable option', function () { + w.destroy() + w = new BrowserWindow({show: false, fullscreenable: false}) + assert.equal(w.isFullScreenable(), false) + }) + + it('can be changed with setFullScreenable method', function () { + assert.equal(w.isFullScreenable(), true) + w.setFullScreenable(false) + assert.equal(w.isFullScreenable(), false) + w.setFullScreenable(true) + assert.equal(w.isFullScreenable(), true) + }) + }) + + describe('closable state', function () { + it('can be changed with closable option', function () { + w.destroy() + w = new BrowserWindow({show: false, closable: false}) + assert.equal(w.isClosable(), false) + }) + + it('can be changed with setClosable method', function () { + assert.equal(w.isClosable(), true) + w.setClosable(false) + assert.equal(w.isClosable(), false) + w.setClosable(true) + assert.equal(w.isClosable(), true) + }) + }) + + describe('hasShadow state', function () { + // On Window there is no shadow by default and it can not be changed + // dynamically. + it('can be changed with hasShadow option', function () { + w.destroy() + let hasShadow = process.platform !== 'darwin' + w = new BrowserWindow({show: false, hasShadow: hasShadow}) + assert.equal(w.hasShadow(), hasShadow) + }) + + it('can be changed with setHasShadow method', function () { + if (process.platform !== 'darwin') return + + assert.equal(w.hasShadow(), true) + w.setHasShadow(false) + assert.equal(w.hasShadow(), false) + w.setHasShadow(true) + assert.equal(w.hasShadow(), true) + }) + }) + }) + + describe('window.webContents.send(channel, args...)', function () { + it('throws an error when the channel is missing', function () { + assert.throws(function () { + w.webContents.send() + }, 'Missing required channel argument') + + assert.throws(function () { + w.webContents.send(null) + }, 'Missing required channel argument') + }) + }) + + describe('dev tool extensions', function () { + it('serializes the registered extensions on quit', function () { + var extensionName = 'foo' + var extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', extensionName) + var serializedPath = path.join(app.getPath('userData'), 'DevTools Extensions') + + BrowserWindow.addDevToolsExtension(extensionPath) + app.emit('will-quit') + assert.deepEqual(JSON.parse(fs.readFileSync(serializedPath)), [extensionPath]) + + BrowserWindow.removeDevToolsExtension(extensionName) + app.emit('will-quit') + assert.equal(fs.existsSync(serializedPath), false) + }) + }) + + describe('window.webContents.executeJavaScript', function () { + var expected = 'hello, world!' + var code = '(() => "' + expected + '")()' + + it('doesnt throw when no calback is provided', function () { + const result = ipcRenderer.sendSync('executeJavaScript', code, false) + assert.equal(result, 'success') + }) + + it('returns result when calback is provided', function (done) { + ipcRenderer.send('executeJavaScript', code, true) + ipcRenderer.once('executeJavaScript-response', function (event, result) { + assert.equal(result, expected) + done() + }) + }) + }) + + describe('deprecated options', function () { + it('throws a deprecation error for option keys using hyphens instead of camel case', function () { + assert.throws(function () { + return new BrowserWindow({'min-width': 500}) + }, 'min-width is deprecated. Use minWidth instead.') + }) + + it('throws a deprecation error for webPreference keys using hyphens instead of camel case', function () { + assert.throws(function () { + return new BrowserWindow({webPreferences: {'node-integration': false}}) + }, 'node-integration is deprecated. Use nodeIntegration instead.') + }) + + it('throws a deprecation error for option keys that should be set on webPreferences', function () { + assert.throws(function () { + return new BrowserWindow({zoomFactor: 1}) + }, 'options.zoomFactor is deprecated. Use options.webPreferences.zoomFactor instead.') + }) + }) +}) diff --git a/spec/api-clipboard-spec.js b/spec/api-clipboard-spec.js index 6154181f092..63f1d907e8a 100644 --- a/spec/api-clipboard-spec.js +++ b/spec/api-clipboard-spec.js @@ -1,55 +1,63 @@ -var assert, clipboard, nativeImage, path, ref; +const assert = require('assert') +const path = require('path') -assert = require('assert'); +const clipboard = require('electron').clipboard +const nativeImage = require('electron').nativeImage -path = require('path'); +describe('clipboard module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') -ref = require('electron'), clipboard = ref.clipboard, nativeImage = ref.nativeImage; + describe('clipboard.readImage()', function () { + it('returns NativeImage intance', function () { + var p = path.join(fixtures, 'assets', 'logo.png') + var i = nativeImage.createFromPath(p) + clipboard.writeImage(p) + assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()) + }) + }) -describe('clipboard module', function() { - var fixtures; - fixtures = path.resolve(__dirname, 'fixtures'); - describe('clipboard.readImage()', function() { - return it('returns NativeImage intance', function() { - var i, p; - p = path.join(fixtures, 'assets', 'logo.png'); - i = nativeImage.createFromPath(p); - clipboard.writeImage(p); - return assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()); - }); - }); - describe('clipboard.readText()', function() { - return it('returns unicode string correctly', function() { - var text; - text = '千江有水千江月,万里无云万里天'; - clipboard.writeText(text); - return assert.equal(clipboard.readText(), text); - }); - }); - describe('clipboard.readHtml()', function() { - return it('returns markup correctly', function() { - var markup, text; - text = 'Hi'; - markup = process.platform === 'darwin' ? 'Hi' : process.platform === 'linux' ? 'Hi' : 'Hi'; - clipboard.writeHtml(text); - return assert.equal(clipboard.readHtml(), markup); - }); - }); - return describe('clipboard.write()', function() { - return it('returns data correctly', function() { - var i, markup, p, text; - text = 'test'; - p = path.join(fixtures, 'assets', 'logo.png'); - i = nativeImage.createFromPath(p); - markup = process.platform === 'darwin' ? 'Hi' : process.platform === 'linux' ? 'Hi' : 'Hi'; + describe('clipboard.readText()', function () { + it('returns unicode string correctly', function () { + var text = '千江有水千江月,万里无云万里天' + clipboard.writeText(text) + assert.equal(clipboard.readText(), text) + }) + }) + + describe('clipboard.readHtml()', function () { + it('returns markup correctly', function () { + var text = 'Hi' + var markup = process.platform === 'darwin' ? "Hi" : process.platform === 'linux' ? 'Hi' : 'Hi' + clipboard.writeHtml(text) + assert.equal(clipboard.readHtml(), markup) + }) + }) + + describe('clipboard.readRtf', function () { + it('returns rtf text correctly', function () { + var rtf = '{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}' + clipboard.writeRtf(rtf) + assert.equal(clipboard.readRtf(), rtf) + }) + }) + + describe('clipboard.write()', function () { + it('returns data correctly', function () { + var text = 'test' + var rtf = '{\\rtf1\\utf8 text}' + var p = path.join(fixtures, 'assets', 'logo.png') + var i = nativeImage.createFromPath(p) + var markup = process.platform === 'darwin' ? "Hi" : process.platform === 'linux' ? 'Hi' : 'Hi' clipboard.write({ - text: "test", + text: 'test', html: 'Hi', + rtf: '{\\rtf1\\utf8 text}', image: p - }); - assert.equal(clipboard.readText(), text); - assert.equal(clipboard.readHtml(), markup); - return assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()); - }); - }); -}); + }) + assert.equal(clipboard.readText(), text) + assert.equal(clipboard.readHtml(), markup) + assert.equal(clipboard.readRtf(), rtf) + assert.equal(clipboard.readImage().toDataURL(), i.toDataURL()) + }) + }) +}) diff --git a/spec/api-crash-reporter-spec.js b/spec/api-crash-reporter-spec.js index b3c4c311589..2e49fdc36c5 100644 --- a/spec/api-crash-reporter-spec.js +++ b/spec/api-crash-reporter-spec.js @@ -1,94 +1,92 @@ -var BrowserWindow, app, assert, crashReporter, http, multiparty, path, ref, remote, url; +const assert = require('assert') +const http = require('http') +const multiparty = require('multiparty') +const path = require('path') +const url = require('url') -assert = require('assert'); +const remote = require('electron').remote +const app = remote.require('electron').app +const crashReporter = remote.require('electron').crashReporter +const BrowserWindow = remote.require('electron').BrowserWindow -path = require('path'); +describe('crash-reporter module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + var w = null -http = require('http'); - -url = require('url'); - -multiparty = require('multiparty'); - -remote = require('electron').remote; - -ref = remote.require('electron'), app = ref.app, crashReporter = ref.crashReporter, BrowserWindow = ref.BrowserWindow; - -describe('crash-reporter module', function() { - var fixtures, isCI, w; - fixtures = path.resolve(__dirname, 'fixtures'); - w = null; - beforeEach(function() { - return w = new BrowserWindow({ + beforeEach(function () { + w = new BrowserWindow({ show: false - }); - }); - afterEach(function() { - return w.destroy(); - }); + }) + }) + + afterEach(function () { + w.destroy() + }) + if (process.mas) { - return; + return } - isCI = remote.getGlobal('isCi'); + + var isCI = remote.getGlobal('isCi') if (isCI) { - return; + return } - it('should send minidump when renderer crashes', function(done) { - var called, port, server; - this.timeout(120000); - called = false; - server = http.createServer(function(req, res) { - var form; - server.close(); - form = new multiparty.Form(); - return form.parse(req, function(error, fields) { - if (called) { - return; - } - called = true; - assert.equal(fields['prod'], 'Electron'); - assert.equal(fields['ver'], process.versions['electron']); - assert.equal(fields['process_type'], 'renderer'); - assert.equal(fields['platform'], process.platform); - assert.equal(fields['extra1'], 'extra1'); - assert.equal(fields['extra2'], 'extra2'); - assert.equal(fields['_productName'], 'Zombies'); - assert.equal(fields['_companyName'], 'Umbrella Corporation'); - assert.equal(fields['_version'], app.getVersion()); - res.end('abc-123-def'); - return done(); - }); - }); - port = remote.process.port; - return server.listen(port, '127.0.0.1', function() { - port = server.address().port; - remote.process.port = port; - url = url.format({ + + it('should send minidump when renderer crashes', function (done) { + this.timeout(120000) + + var called = false + var server = http.createServer(function (req, res) { + server.close() + var form = new multiparty.Form() + form.parse(req, function (error, fields) { + if (error) throw error + if (called) return + called = true + assert.equal(fields['prod'], 'Electron') + assert.equal(fields['ver'], process.versions['electron']) + assert.equal(fields['process_type'], 'renderer') + assert.equal(fields['platform'], process.platform) + assert.equal(fields['extra1'], 'extra1') + assert.equal(fields['extra2'], 'extra2') + assert.equal(fields['_productName'], 'Zombies') + assert.equal(fields['_companyName'], 'Umbrella Corporation') + assert.equal(fields['_version'], app.getVersion()) + res.end('abc-123-def') + done() + }) + }) + var port = remote.process.port + server.listen(port, '127.0.0.1', function () { + port = server.address().port + remote.process.port = port + const crashUrl = url.format({ protocol: 'file', pathname: path.join(fixtures, 'api', 'crash.html'), - search: "?port=" + port - }); + search: '?port=' + port + }) if (process.platform === 'darwin') { crashReporter.start({ companyName: 'Umbrella Corporation', - submitURL: "http://127.0.0.1:" + port - }); + submitURL: 'http://127.0.0.1:' + port + }) } - return w.loadURL(url); - }); - }); - return describe(".start(options)", function() { - return it('requires that the companyName and submitURL options be specified', function() { - assert.throws(function() { - return crashReporter.start({ + w.loadURL(crashUrl) + }) + }) + + describe('.start(options)', function () { + it('requires that the companyName and submitURL options be specified', function () { + assert.throws(function () { + crashReporter.start({ companyName: 'Missing submitURL' - }); - }); - return assert.throws(function() { - return crashReporter.start({ + }) + }) + assert.throws(function () { + crashReporter.start({ submitURL: 'Missing companyName' - }); - }); - }); - }); -}); + }) + }) + }) + }) +}) diff --git a/spec/api-debugger-spec.js b/spec/api-debugger-spec.js new file mode 100644 index 00000000000..27aacc7671c --- /dev/null +++ b/spec/api-debugger-spec.js @@ -0,0 +1,134 @@ +const assert = require('assert') +const path = require('path') +const BrowserWindow = require('electron').remote.BrowserWindow + +describe('debugger module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + var w = null + + beforeEach(function () { + if (w != null) { + w.destroy() + } + w = new BrowserWindow({ + show: false, + width: 400, + height: 400 + }) + }) + + afterEach(function () { + if (w != null) { + w.destroy() + } + w = null + }) + + describe('debugger.attach', function () { + it('fails when devtools is already open', function (done) { + w.webContents.on('did-finish-load', function () { + w.webContents.openDevTools() + try { + w.webContents.debugger.attach() + } catch (err) { + assert(w.webContents.debugger.isAttached()) + done() + } + }) + w.webContents.loadURL('file://' + path.join(fixtures, 'pages', 'a.html')) + }) + + it('fails when protocol version is not supported', function (done) { + try { + w.webContents.debugger.attach('2.0') + } catch (err) { + assert(!w.webContents.debugger.isAttached()) + done() + } + }) + + it('attaches when no protocol version is specified', function (done) { + try { + w.webContents.debugger.attach() + } catch (err) { + done('unexpected error : ' + err) + } + assert(w.webContents.debugger.isAttached()) + done() + }) + }) + + describe('debugger.detach', function () { + it('fires detach event', function (done) { + w.webContents.debugger.on('detach', function (e, reason) { + assert.equal(reason, 'target closed') + assert(!w.webContents.debugger.isAttached()) + done() + }) + try { + w.webContents.debugger.attach() + } catch (err) { + done('unexpected error : ' + err) + } + w.webContents.debugger.detach() + }) + }) + + describe('debugger.sendCommand', function () { + it('retuns response', function (done) { + w.webContents.loadURL('about:blank') + try { + w.webContents.debugger.attach() + } catch (err) { + return done('unexpected error : ' + err) + } + var callback = function (err, res) { + assert(!err.message) + assert(!res.wasThrown) + assert.equal(res.result.value, 6) + w.webContents.debugger.detach() + done() + } + const params = { + 'expression': '4+2' + } + w.webContents.debugger.sendCommand('Runtime.evaluate', params, callback) + }) + + it('fires message event', function (done) { + var url = process.platform !== 'win32' + ? 'file://' + path.join(fixtures, 'pages', 'a.html') + : 'file:///' + path.join(fixtures, 'pages', 'a.html').replace(/\\/g, '/') + w.webContents.loadURL(url) + try { + w.webContents.debugger.attach() + } catch (err) { + done('unexpected error : ' + err) + } + w.webContents.debugger.on('message', function (e, method, params) { + if (method === 'Console.messageAdded') { + assert.equal(params.message.type, 'log') + assert.equal(params.message.url, url) + assert.equal(params.message.text, 'a') + w.webContents.debugger.detach() + done() + } + }) + w.webContents.debugger.sendCommand('Console.enable') + }) + + it('returns error message when command fails', function (done) { + w.webContents.loadURL('about:blank') + try { + w.webContents.debugger.attach() + } catch (err) { + done('unexpected error : ' + err) + } + w.webContents.debugger.sendCommand('Test', function (err) { + assert.equal(err.message, "'Test' wasn't found") + w.webContents.debugger.detach() + done() + }) + }) + }) +}) diff --git a/spec/api-deprecations-spec.js b/spec/api-deprecations-spec.js new file mode 100644 index 00000000000..375de5895fe --- /dev/null +++ b/spec/api-deprecations-spec.js @@ -0,0 +1,27 @@ +const assert = require('assert') +const deprecations = require('electron').deprecations + +describe('deprecations', function () { + beforeEach(function () { + deprecations.setHandler(null) + process.throwDeprecation = true + }) + + it('allows a deprecation handler function to be specified', function () { + var messages = [] + + deprecations.setHandler(function (message) { + messages.push(message) + }) + + require('electron').webFrame.registerUrlSchemeAsSecure('some-scheme') + + assert.deepEqual(messages, ['registerUrlSchemeAsSecure is deprecated. Use registerURLSchemeAsSecure instead.']) + }) + + it('throws an exception if no deprecation handler is specified', function () { + assert.throws(function () { + require('electron').webFrame.registerUrlSchemeAsPrivileged('some-scheme') + }, 'registerUrlSchemeAsPrivileged is deprecated. Use registerURLSchemeAsPrivileged instead.') + }) +}) diff --git a/spec/api-desktop-capturer-spec.js b/spec/api-desktop-capturer-spec.js index 02eda9003b8..9e85a48fbcc 100644 --- a/spec/api-desktop-capturer-spec.js +++ b/spec/api-desktop-capturer-spec.js @@ -1,27 +1,27 @@ -const assert = require('assert'); -const desktopCapturer = require('electron').desktopCapturer; +const assert = require('assert') +const desktopCapturer = require('electron').desktopCapturer -describe('desktopCapturer', function() { - it('should return a non-empty array of sources', function(done) { +describe('desktopCapturer', function () { + it('should return a non-empty array of sources', function (done) { desktopCapturer.getSources({ types: ['window', 'screen'] - }, function(error, sources) { - assert.equal(error, null); - assert.notEqual(sources.length, 0); - done(); - }); - }); + }, function (error, sources) { + assert.equal(error, null) + assert.notEqual(sources.length, 0) + done() + }) + }) - it('does not throw an error when called more than once (regression)', function(done) { - var callCount = 0; + it('does not throw an error when called more than once (regression)', function (done) { + var callCount = 0 var callback = function (error, sources) { - callCount++; - assert.equal(error, null); - assert.notEqual(sources.length, 0); - if (callCount === 2) done(); - }; + callCount++ + assert.equal(error, null) + assert.notEqual(sources.length, 0) + if (callCount === 2) done() + } - desktopCapturer.getSources({types: ['window', 'screen']}, callback); - desktopCapturer.getSources({types: ['window', 'screen']}, callback); - }); -}); + desktopCapturer.getSources({types: ['window', 'screen']}, callback) + desktopCapturer.getSources({types: ['window', 'screen']}, callback) + }) +}) diff --git a/spec/api-ipc-spec.js b/spec/api-ipc-spec.js index 66f392a577c..c98689287ce 100644 --- a/spec/api-ipc-spec.js +++ b/spec/api-ipc-spec.js @@ -1,145 +1,223 @@ -var BrowserWindow, assert, comparePaths, ipcMain, ipcRenderer, path, ref, ref1, remote; +'use strict' -assert = require('assert'); +const assert = require('assert') +const path = require('path') -path = require('path'); +const ipcRenderer = require('electron').ipcRenderer +const remote = require('electron').remote -ref = require('electron'), ipcRenderer = ref.ipcRenderer, remote = ref.remote; +const ipcMain = remote.require('electron').ipcMain +const BrowserWindow = remote.require('electron').BrowserWindow -ref1 = remote.require('electron'), ipcMain = ref1.ipcMain, BrowserWindow = ref1.BrowserWindow; - -comparePaths = function(path1, path2) { +const comparePaths = function (path1, path2) { if (process.platform === 'win32') { - path1 = path1.toLowerCase(); - path2 = path2.toLowerCase(); + path1 = path1.toLowerCase() + path2 = path2.toLowerCase() } - return assert.equal(path1, path2); -}; + assert.equal(path1, path2) +} -describe('ipc module', function() { - var fixtures; - fixtures = path.join(__dirname, 'fixtures'); - describe('remote.require', function() { - it('should returns same object for the same module', function() { - var dialog1, dialog2; - dialog1 = remote.require('electron'); - dialog2 = remote.require('electron'); - return assert.equal(dialog1, dialog2); +describe('ipc module', function () { + var fixtures = path.join(__dirname, 'fixtures') + + describe('remote.require', function () { + it('should returns same object for the same module', function () { + var dialog1 = remote.require('electron') + var dialog2 = remote.require('electron') + assert.equal(dialog1, dialog2) + }) + + it('should work when object contains id property', function () { + var a = remote.require(path.join(fixtures, 'module', 'id.js')) + assert.equal(a.id, 1127) + }) + + it('should search module from the user app', function () { + comparePaths(path.normalize(remote.process.mainModule.filename), path.resolve(__dirname, 'static', 'main.js')) + comparePaths(path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules')) + }) + }) + + describe('remote.createFunctionWithReturnValue', function () { + it('should be called in browser synchronously', function () { + var buf = new Buffer('test') + var call = remote.require(path.join(fixtures, 'module', 'call.js')) + var result = call.call(remote.createFunctionWithReturnValue(buf)) + assert.equal(result.constructor.name, 'Buffer') + }) + }) + + 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 = 1007 + assert.equal(property.property, 1007) + var property2 = remote.require(path.join(fixtures, 'module', 'property.js')) + assert.equal(property2.property, 1007) + property.property = 1127 + }) + + it('can construct an object from its member', function () { + var call = remote.require(path.join(fixtures, 'module', 'call.js')) + var obj = new call.constructor() + assert.equal(obj.test, 'test') + }) + + it('can reassign and delete its member functions', function () { + var remoteFunctions = remote.require(path.join(fixtures, 'module', 'function.js')) + assert.equal(remoteFunctions.aFunction(), 1127) + + remoteFunctions.aFunction = function () { return 1234 } + assert.equal(remoteFunctions.aFunction(), 1234) + + assert.equal(delete remoteFunctions.aFunction, true) + }) + + it('is referenced by its members', function () { + let stringify = remote.getGlobal('JSON').stringify + gc(); + stringify({}) }); - it('should work when object contains id property', function() { - var a; - a = remote.require(path.join(fixtures, 'module', 'id.js')); - return assert.equal(a.id, 1127); + }) + + describe('remote value in browser', function () { + var print = path.join(fixtures, 'module', 'print_name.js') + + it('keeps its constructor name for objects', function () { + var buf = new Buffer('test') + var print_name = remote.require(print) + assert.equal(print_name.print(buf), 'Buffer') + }) + + it('supports instanceof Date', function () { + var now = new Date() + var print_name = remote.require(print) + assert.equal(print_name.print(now), 'Date') + assert.deepEqual(print_name.echo(now), now) + }) + }) + + describe('remote promise', function () { + it('can be used as promise in each side', function (done) { + var promise = remote.require(path.join(fixtures, 'module', 'promise.js')) + promise.twicePromise(Promise.resolve(1234)).then(function (value) { + assert.equal(value, 2468) + done() + }) + }) + }) + + describe('remote webContents', function () { + it('can return same object with different getters', function () { + var contents1 = remote.getCurrentWindow().webContents + var contents2 = remote.getCurrentWebContents() + assert(contents1 === contents2) + }) + }) + + describe('remote class', function () { + let cl = remote.require(path.join(fixtures, 'module', 'class.js')) + let base = cl.base + let derived = cl.derived + + it('can get methods', function () { + assert.equal(base.method(), 'method') + }) + + it('can get properties', function () { + assert.equal(base.readonly, 'readonly') + }) + + it('can change properties', function () { + assert.equal(base.value, 'old') + base.value = 'new' + assert.equal(base.value, 'new') + base.value = 'old' + }) + + it('has unenumerable methods', function () { + assert(!base.hasOwnProperty('method')) + assert(Object.getPrototypeOf(base).hasOwnProperty('method')) + }) + + it('keeps prototype chain in derived class', function () { + assert.equal(derived.method(), 'method') + assert.equal(derived.readonly, 'readonly') + assert(!derived.hasOwnProperty('method')) + let proto = Object.getPrototypeOf(derived) + assert(!proto.hasOwnProperty('method')) + assert(Object.getPrototypeOf(proto).hasOwnProperty('method')) + }) + + it('is referenced by methods in prototype chain', function () { + let method = derived.method + derived = null + gc() + assert.equal(method(), 'method') }); - return it('should search module from the user app', function() { - comparePaths(path.normalize(remote.process.mainModule.filename), path.resolve(__dirname, 'static', 'main.js')); - return comparePaths(path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules')); - }); - }); - describe('remote.createFunctionWithReturnValue', function() { - return it('should be called in browser synchronously', function() { - var buf, call, result; - buf = new Buffer('test'); - call = remote.require(path.join(fixtures, 'module', 'call.js')); - result = call.call(remote.createFunctionWithReturnValue(buf)); - return assert.equal(result.constructor.name, 'Buffer'); - }); - }); - describe('remote object in renderer', function() { - it('can change its properties', function() { - var property, property2; - property = remote.require(path.join(fixtures, 'module', 'property.js')); - assert.equal(property.property, 1127); - property.property = 1007; - assert.equal(property.property, 1007); - property2 = remote.require(path.join(fixtures, 'module', 'property.js')); - assert.equal(property2.property, 1007); - return property.property = 1127; - }); - return it('can construct an object from its member', function() { - var call, obj; - call = remote.require(path.join(fixtures, 'module', 'call.js')); - obj = new call.constructor; - return assert.equal(obj.test, 'test'); - }); - }); - describe('remote value in browser', function() { - var print; - print = path.join(fixtures, 'module', 'print_name.js'); - it('keeps its constructor name for objects', function() { - var buf, print_name; - buf = new Buffer('test'); - print_name = remote.require(print); - return assert.equal(print_name.print(buf), 'Buffer'); - }); - return it('supports instanceof Date', function() { - var now, print_name; - now = new Date(); - print_name = remote.require(print); - assert.equal(print_name.print(now), 'Date'); - return assert.deepEqual(print_name.echo(now), now); - }); - }); - describe('remote promise', function() { - return it('can be used as promise in each side', function(done) { - var promise; - promise = remote.require(path.join(fixtures, 'module', 'promise.js')); - return promise.twicePromise(Promise.resolve(1234)).then(function(value) { - assert.equal(value, 2468); - return done(); - }); - }); - }); - describe('ipc.sender.send', function() { - return it('should work when sending an object containing id property', function(done) { - var obj; - obj = { + }) + + describe('ipc.sender.send', function () { + it('should work when sending an object containing id property', function (done) { + var obj = { id: 1, name: 'ly' - }; - ipcRenderer.once('message', function(event, message) { - assert.deepEqual(message, obj); - return done(); - }); - return ipcRenderer.send('message', obj); - }); - }); - describe('ipc.sendSync', function() { - it('can be replied by setting event.returnValue', function() { - var msg; - msg = ipcRenderer.sendSync('echo', 'test'); - return assert.equal(msg, 'test'); - }); - return it('does not crash when reply is not sent and browser is destroyed', function(done) { - var w; - this.timeout(10000); + } + ipcRenderer.once('message', function (event, message) { + assert.deepEqual(message, obj) + done() + }) + ipcRenderer.send('message', obj) + }) + + it('can send instance of Date', function (done) { + const currentDate = new Date() + ipcRenderer.once('message', function (event, value) { + assert.equal(value, currentDate.toISOString()) + done() + }) + ipcRenderer.send('message', currentDate) + }) + }) + + describe('ipc.sendSync', function () { + it('can be replied by setting event.returnValue', function () { + var msg = ipcRenderer.sendSync('echo', 'test') + assert.equal(msg, 'test') + }) + + it('does not crash when reply is not sent and browser is destroyed', function (done) { + this.timeout(10000) + + var w = new BrowserWindow({ + show: false + }) + ipcMain.once('send-sync-message', function (event) { + event.returnValue = null + w.destroy() + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'send-sync-message.html')) + }) + }) + + describe('remote listeners', function () { + var w = null + + afterEach(function () { + w.destroy() + }) + + it('can be added and removed correctly', function () { w = new BrowserWindow({ show: false - }); - ipcMain.once('send-sync-message', function(event) { - event.returnValue = null; - w.destroy(); - return done(); - }); - return w.loadURL('file://' + path.join(fixtures, 'api', 'send-sync-message.html')); - }); - }); - return describe('remote listeners', function() { - var w; - w = null; - afterEach(function() { - return w.destroy(); - }); - return it('can be added and removed correctly', function() { - var listener; - w = new BrowserWindow({ - show: false - }); - listener = function() {}; - w.on('test', listener); - assert.equal(w.listenerCount('test'), 1); - w.removeListener('test', listener); - return assert.equal(w.listenerCount('test'), 0); - }); - }); -}); + }) + var listener = function () {} + w.on('test', listener) + assert.equal(w.listenerCount('test'), 1) + w.removeListener('test', listener) + assert.equal(w.listenerCount('test'), 0) + }) + }) +}) diff --git a/spec/api-menu-spec.js b/spec/api-menu-spec.js index 5771358337e..6866448e0fd 100644 --- a/spec/api-menu-spec.js +++ b/spec/api-menu-spec.js @@ -1,27 +1,26 @@ -var Menu, MenuItem, assert, ipcRenderer, ref, ref1, remote; +const assert = require('assert') -assert = require('assert'); +const remote = require('electron').remote +const ipcRenderer = require('electron').ipcRenderer -ref = require('electron'), remote = ref.remote, ipcRenderer = ref.ipcRenderer; +const Menu = remote.require('electron').Menu +const MenuItem = remote.require('electron').MenuItem -ref1 = remote.require('electron'), Menu = ref1.Menu, MenuItem = ref1.MenuItem; - -describe('menu module', function() { - describe('Menu.buildFromTemplate', function() { - it('should be able to attach extra fields', function() { - var menu; - menu = Menu.buildFromTemplate([ +describe('menu module', function () { + describe('Menu.buildFromTemplate', function () { + it('should be able to attach extra fields', function () { + var menu = Menu.buildFromTemplate([ { label: 'text', extra: 'field' } - ]); - return assert.equal(menu.items[0].extra, 'field'); - }); - it('does not modify the specified template', function() { - var template; - template = ipcRenderer.sendSync('eval', "var template = [{label: 'text', submenu: [{label: 'sub'}]}];\nrequire('electron').Menu.buildFromTemplate(template);\ntemplate;"); - return assert.deepStrictEqual(template, [ + ]) + assert.equal(menu.items[0].extra, 'field') + }) + + it('does not modify the specified template', function () { + var template = ipcRenderer.sendSync('eval', "var template = [{label: 'text', submenu: [{label: 'sub'}]}];\nrequire('electron').Menu.buildFromTemplate(template);\ntemplate;") + assert.deepStrictEqual(template, [ { label: 'text', submenu: [ @@ -30,12 +29,27 @@ describe('menu module', function() { } ] } - ]); - }); - return describe('Menu.buildFromTemplate should reorder based on item position specifiers', function() { - it('should position before existing item', function() { - var menu; - menu = Menu.buildFromTemplate([ + ]) + }) + + it('does not throw exceptions for undefined/null values', function () { + assert.doesNotThrow(function () { + Menu.buildFromTemplate([ + { + label: 'text', + accelerator: undefined + }, + { + label: 'text again', + accelerator: null + } + ]) + }) + }) + + describe('Menu.buildFromTemplate should reorder based on item position specifiers', function () { + it('should position before existing item', function () { + var menu = Menu.buildFromTemplate([ { label: '2', id: '2' @@ -47,14 +61,14 @@ describe('menu module', function() { id: '1', position: 'before=2' } - ]); - assert.equal(menu.items[0].label, '1'); - assert.equal(menu.items[1].label, '2'); - return assert.equal(menu.items[2].label, '3'); - }); - it('should position after existing item', function() { - var menu; - menu = Menu.buildFromTemplate([ + ]) + assert.equal(menu.items[0].label, '1') + assert.equal(menu.items[1].label, '2') + assert.equal(menu.items[2].label, '3') + }) + + it('should position after existing item', function () { + var menu = Menu.buildFromTemplate([ { label: '1', id: '1' @@ -66,14 +80,14 @@ describe('menu module', function() { id: '2', position: 'after=1' } - ]); - assert.equal(menu.items[0].label, '1'); - assert.equal(menu.items[1].label, '2'); - return assert.equal(menu.items[2].label, '3'); - }); - it('should position at endof existing separator groups', function() { - var menu; - menu = Menu.buildFromTemplate([ + ]) + assert.equal(menu.items[0].label, '1') + assert.equal(menu.items[1].label, '2') + assert.equal(menu.items[2].label, '3') + }) + + it('should position at endof existing separator groups', function () { + var menu = Menu.buildFromTemplate([ { type: 'separator', id: 'numbers' @@ -105,19 +119,19 @@ describe('menu module', function() { id: '3', position: 'endof=numbers' } - ]); - assert.equal(menu.items[0].id, 'numbers'); - assert.equal(menu.items[1].label, '1'); - assert.equal(menu.items[2].label, '2'); - assert.equal(menu.items[3].label, '3'); - assert.equal(menu.items[4].id, 'letters'); - assert.equal(menu.items[5].label, 'a'); - assert.equal(menu.items[6].label, 'b'); - return assert.equal(menu.items[7].label, 'c'); - }); - it('should create separator group if endof does not reference existing separator group', function() { - var menu; - menu = Menu.buildFromTemplate([ + ]) + assert.equal(menu.items[0].id, 'numbers') + assert.equal(menu.items[1].label, '1') + assert.equal(menu.items[2].label, '2') + assert.equal(menu.items[3].label, '3') + assert.equal(menu.items[4].id, 'letters') + assert.equal(menu.items[5].label, 'a') + assert.equal(menu.items[6].label, 'b') + assert.equal(menu.items[7].label, 'c') + }) + + it('should create separator group if endof does not reference existing separator group', function () { + var menu = Menu.buildFromTemplate([ { label: 'a', id: 'a', @@ -143,19 +157,19 @@ describe('menu module', function() { id: '3', position: 'endof=numbers' } - ]); - assert.equal(menu.items[0].id, 'letters'); - assert.equal(menu.items[1].label, 'a'); - assert.equal(menu.items[2].label, 'b'); - assert.equal(menu.items[3].label, 'c'); - assert.equal(menu.items[4].id, 'numbers'); - assert.equal(menu.items[5].label, '1'); - assert.equal(menu.items[6].label, '2'); - return assert.equal(menu.items[7].label, '3'); - }); - return it('should continue inserting items at next index when no specifier is present', function() { - var menu; - menu = Menu.buildFromTemplate([ + ]) + assert.equal(menu.items[0].id, 'letters') + assert.equal(menu.items[1].label, 'a') + assert.equal(menu.items[2].label, 'b') + assert.equal(menu.items[3].label, 'c') + assert.equal(menu.items[4].id, 'numbers') + assert.equal(menu.items[5].label, '1') + assert.equal(menu.items[6].label, '2') + assert.equal(menu.items[7].label, '3') + }) + + it('should continue inserting items at next index when no specifier is present', function () { + var menu = Menu.buildFromTemplate([ { label: '4', id: '4' @@ -173,19 +187,19 @@ describe('menu module', function() { label: '3', id: '3' } - ]); - assert.equal(menu.items[0].label, '1'); - assert.equal(menu.items[1].label, '2'); - assert.equal(menu.items[2].label, '3'); - assert.equal(menu.items[3].label, '4'); - return assert.equal(menu.items[4].label, '5'); - }); - }); - }); - describe('Menu.insert', function() { - return it('should store item in @items by its index', function() { - var item, menu; - menu = Menu.buildFromTemplate([ + ]) + assert.equal(menu.items[0].label, '1') + assert.equal(menu.items[1].label, '2') + assert.equal(menu.items[2].label, '3') + assert.equal(menu.items[3].label, '4') + assert.equal(menu.items[4].label, '5') + }) + }) + }) + + describe('Menu.insert', function () { + it('should store item in @items by its index', function () { + var menu = Menu.buildFromTemplate([ { label: '1' }, { @@ -193,157 +207,156 @@ describe('menu module', function() { }, { label: '3' } - ]); - item = new MenuItem({ + ]) + var item = new MenuItem({ label: 'inserted' - }); - menu.insert(1, item); - assert.equal(menu.items[0].label, '1'); - assert.equal(menu.items[1].label, 'inserted'); - assert.equal(menu.items[2].label, '2'); - return assert.equal(menu.items[3].label, '3'); - }); - }); - describe('MenuItem.click', function() { - return it('should be called with the item object passed', function(done) { - var menu; - menu = Menu.buildFromTemplate([ + }) + menu.insert(1, item) + assert.equal(menu.items[0].label, '1') + assert.equal(menu.items[1].label, 'inserted') + assert.equal(menu.items[2].label, '2') + assert.equal(menu.items[3].label, '3') + }) + }) + + describe('MenuItem.click', function () { + it('should be called with the item object passed', function (done) { + var menu = Menu.buildFromTemplate([ { label: 'text', - click: function(item) { - assert.equal(item.constructor.name, 'MenuItem'); - assert.equal(item.label, 'text'); - return done(); + click: function (item) { + assert.equal(item.constructor.name, 'MenuItem') + assert.equal(item.label, 'text') + done() } } - ]); - return menu.delegate.executeCommand(menu.items[0].commandId); - }); - }); - return describe('MenuItem with checked property', function() { - it('clicking an checkbox item should flip the checked property', function() { - var menu; - menu = Menu.buildFromTemplate([ + ]) + menu.delegate.executeCommand(menu.items[0].commandId) + }) + }) + + describe('MenuItem with checked property', function () { + it('clicking an checkbox item should flip the checked property', function () { + var menu = Menu.buildFromTemplate([ { label: 'text', type: 'checkbox' } - ]); - assert.equal(menu.items[0].checked, false); - menu.delegate.executeCommand(menu.items[0].commandId); - return assert.equal(menu.items[0].checked, true); - }); - it('clicking an radio item should always make checked property true', function() { - var menu; - menu = Menu.buildFromTemplate([ + ]) + assert.equal(menu.items[0].checked, false) + menu.delegate.executeCommand(menu.items[0].commandId) + assert.equal(menu.items[0].checked, true) + }) + + it('clicking an radio item should always make checked property true', function () { + var menu = Menu.buildFromTemplate([ { label: 'text', type: 'radio' } - ]); - menu.delegate.executeCommand(menu.items[0].commandId); - assert.equal(menu.items[0].checked, true); - menu.delegate.executeCommand(menu.items[0].commandId); - return assert.equal(menu.items[0].checked, true); - }); - it('at least have one item checked in each group', function() { - var i, j, k, menu, template; - template = []; + ]) + menu.delegate.executeCommand(menu.items[0].commandId) + assert.equal(menu.items[0].checked, true) + menu.delegate.executeCommand(menu.items[0].commandId) + assert.equal(menu.items[0].checked, true) + }) + + it('at least have one item checked in each group', function () { + var i, j, k, menu, template + template = [] for (i = j = 0; j <= 10; i = ++j) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } template.push({ type: 'separator' - }); + }) for (i = k = 12; k <= 20; i = ++k) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } - menu = Menu.buildFromTemplate(template); - menu.delegate.menuWillShow(); - assert.equal(menu.items[0].checked, true); - return assert.equal(menu.items[12].checked, true); - }); - it('should assign groupId automatically', function() { - var groupId, i, j, k, l, m, menu, results, template; - template = []; + menu = Menu.buildFromTemplate(template) + menu.delegate.menuWillShow() + assert.equal(menu.items[0].checked, true) + assert.equal(menu.items[12].checked, true) + }) + + it('should assign groupId automatically', function () { + var groupId, i, j, k, l, m, menu, template + template = [] for (i = j = 0; j <= 10; i = ++j) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } template.push({ type: 'separator' - }); + }) for (i = k = 12; k <= 20; i = ++k) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } - menu = Menu.buildFromTemplate(template); - groupId = menu.items[0].groupId; + menu = Menu.buildFromTemplate(template) + groupId = menu.items[0].groupId for (i = l = 0; l <= 10; i = ++l) { - assert.equal(menu.items[i].groupId, groupId); + assert.equal(menu.items[i].groupId, groupId) } - results = []; for (i = m = 12; m <= 20; i = ++m) { - results.push(assert.equal(menu.items[i].groupId, groupId + 1)); + assert.equal(menu.items[i].groupId, groupId + 1) } - return results; - }); - return it("setting 'checked' should flip other items' 'checked' property", function() { - var i, j, k, l, m, menu, n, o, p, q, results, template; - template = []; + }) + + it("setting 'checked' should flip other items' 'checked' property", function () { + var i, j, k, l, m, menu, n, o, p, q, template + template = [] for (i = j = 0; j <= 10; i = ++j) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } template.push({ type: 'separator' - }); + }) for (i = k = 12; k <= 20; i = ++k) { template.push({ - label: "" + i, + label: '' + i, type: 'radio' - }); + }) } - menu = Menu.buildFromTemplate(template); + menu = Menu.buildFromTemplate(template) for (i = l = 0; l <= 10; i = ++l) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } - menu.items[0].checked = true; - assert.equal(menu.items[0].checked, true); + menu.items[0].checked = true + assert.equal(menu.items[0].checked, true) for (i = m = 1; m <= 10; i = ++m) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } - menu.items[10].checked = true; - assert.equal(menu.items[10].checked, true); + menu.items[10].checked = true + assert.equal(menu.items[10].checked, true) for (i = n = 0; n <= 9; i = ++n) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } for (i = o = 12; o <= 20; i = ++o) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } - menu.items[12].checked = true; - assert.equal(menu.items[10].checked, true); + menu.items[12].checked = true + assert.equal(menu.items[10].checked, true) for (i = p = 0; p <= 9; i = ++p) { - assert.equal(menu.items[i].checked, false); + assert.equal(menu.items[i].checked, false) } - assert.equal(menu.items[12].checked, true); - results = []; + assert.equal(menu.items[12].checked, true) for (i = q = 13; q <= 20; i = ++q) { - results.push(assert.equal(menu.items[i].checked, false)); + assert.equal(menu.items[i].checked, false) } - return results; - }); - }); -}); + }) + }) +}) diff --git a/spec/api-native-image-spec.js b/spec/api-native-image-spec.js new file mode 100644 index 00000000000..196ed1356d7 --- /dev/null +++ b/spec/api-native-image-spec.js @@ -0,0 +1,51 @@ +'use strict' + +const assert = require('assert') +const nativeImage = require('electron').nativeImage +const path = require('path') + +describe('nativeImage module', () => { + describe('createFromPath(path)', () => { + it('returns an empty image for invalid paths', () => { + assert(nativeImage.createFromPath('').isEmpty()) + assert(nativeImage.createFromPath('does-not-exist.png').isEmpty()) + }) + + it('loads images from paths relative to the current working directory', () => { + const imagePath = `.${path.sep}${path.join('spec', 'fixtures', 'assets', 'logo.png')}` + const image = nativeImage.createFromPath(imagePath) + assert(!image.isEmpty()) + assert.equal(image.getSize().height, 190) + assert.equal(image.getSize().width, 538) + }) + + it('loads images from paths with `.` segments', () => { + const imagePath = `${path.join(__dirname, 'fixtures')}${path.sep}.${path.sep}${path.join('assets', 'logo.png')}` + const image = nativeImage.createFromPath(imagePath) + assert(!image.isEmpty()) + assert.equal(image.getSize().height, 190) + assert.equal(image.getSize().width, 538) + }) + + it('loads images from paths with `..` segments', () => { + const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}` + const image = nativeImage.createFromPath(imagePath) + assert(!image.isEmpty()) + assert.equal(image.getSize().height, 190) + assert.equal(image.getSize().width, 538) + }) + + it('Gets an NSImage pointer on OS X', () => { + if (process.platform !== 'darwin') return + + const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}` + const image = nativeImage.createFromPath(imagePath) + const nsimage = image.getNativeHandle() + + assert.equal(nsimage.length, 8) + + // If all bytes are null, that's Bad + assert.equal(nsimage.reduce((acc, x) => acc || (x !== 0), false), true) + }) + }) +}) diff --git a/spec/api-protocol-spec.js b/spec/api-protocol-spec.js index 284033b967d..9f2a25eb4a5 100644 --- a/spec/api-protocol-spec.js +++ b/spec/api-protocol-spec.js @@ -1,769 +1,773 @@ -const assert = require('assert'); -const http = require('http'); -const path = require('path'); -const qs = require('querystring'); -const remote = require('electron').remote; -const protocol = remote.require('electron').protocol; +const assert = require('assert') +const http = require('http') +const path = require('path') +const qs = require('querystring') +const remote = require('electron').remote +const protocol = remote.require('electron').protocol -describe('protocol module', function() { - var postData, protocolName, text; - protocolName = 'sp'; - text = 'valar morghulis'; - postData = { +describe('protocol module', function () { + var protocolName = 'sp' + var text = 'valar morghulis' + var postData = { name: 'post test', type: 'string' - }; - afterEach(function(done) { - return protocol.unregisterProtocol(protocolName, function() { - return protocol.uninterceptProtocol('http', function() { - return done(); - }); - }); - }); - describe('protocol.register(Any)Protocol', function() { - var emptyHandler; - emptyHandler = function(request, callback) { - return callback(); - }; - it('throws error when scheme is already registered', function(done) { - return protocol.registerStringProtocol(protocolName, emptyHandler, function(error) { - assert.equal(error, null); - return protocol.registerBufferProtocol(protocolName, emptyHandler, function(error) { - assert.notEqual(error, null); - return done(); - }); - }); - }); - it('does not crash when handler is called twice', function(done) { - var doubleHandler; - doubleHandler = function(request, callback) { + } + + afterEach(function (done) { + protocol.unregisterProtocol(protocolName, function () { + protocol.uninterceptProtocol('http', function () { + done() + }) + }) + }) + + describe('protocol.register(Any)Protocol', function () { + var emptyHandler = function (request, callback) { + callback() + } + + it('throws error when scheme is already registered', function (done) { + protocol.registerStringProtocol(protocolName, emptyHandler, function (error) { + assert.equal(error, null) + protocol.registerBufferProtocol(protocolName, emptyHandler, function (error) { + assert.notEqual(error, null) + done() + }) + }) + }) + + it('does not crash when handler is called twice', function (done) { + var doubleHandler = function (request, callback) { try { - callback(text); - return callback(); + callback(text) + callback() } catch (error) { // Ignore error } - }; - return protocol.registerStringProtocol(protocolName, doubleHandler, function(error) { + } + protocol.registerStringProtocol(protocolName, doubleHandler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - it('sends error when callback is called with nothing', function(done) { - return protocol.registerBufferProtocol(protocolName, emptyHandler, function(error) { + }) + }) + }) + + it('sends error when callback is called with nothing', function (done) { + protocol.registerBufferProtocol(protocolName, emptyHandler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function() { - return done('request succeeded but it should not'); + $.ajax({ + url: protocolName + '://fake-host', + success: function () { + return done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - return done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + return done() } - }); - }); - }); - return it('does not crash when callback is called in next tick', function(done) { - var handler; - handler = function(request, callback) { - return setImmediate(function() { - return callback(text); - }); - }; - return protocol.registerStringProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('does not crash when callback is called in next tick', function (done) { + var handler = function (request, callback) { + setImmediate(function () { + callback(text) + }) + } + protocol.registerStringProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); - describe('protocol.unregisterProtocol', function() { - return it('returns error when scheme does not exist', function(done) { - return protocol.unregisterProtocol('not-exist', function(error) { - assert.notEqual(error, null); - return done(); - }); - }); - }); - describe('protocol.registerStringProtocol', function() { - it('sends string as response', function(done) { - var handler; - handler = function(request, callback) { - return callback(text); - }; - return protocol.registerStringProtocol(protocolName, handler, function(error) { + }) + }) + }) + }) + + describe('protocol.unregisterProtocol', function () { + it('returns error when scheme does not exist', function (done) { + protocol.unregisterProtocol('not-exist', function (error) { + assert.notEqual(error, null) + done() + }) + }) + }) + + describe('protocol.registerStringProtocol', function () { + it('sends string as response', function (done) { + var handler = function (request, callback) { + callback(text) + } + protocol.registerStringProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - it('sets Access-Control-Allow-Origin', function(done) { - var handler; - handler = function(request, callback) { - return callback(text); - }; - return protocol.registerStringProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('sets Access-Control-Allow-Origin', function (done) { + var handler = function (request, callback) { + callback(text) + } + protocol.registerStringProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data, status, request) { - assert.equal(data, text); - assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*'); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data, status, request) { + assert.equal(data, text) + assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*') + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - it('sends object as response', function(done) { - var handler; - handler = function(request, callback) { - return callback({ + }) + }) + }) + + it('sends object as response', function (done) { + var handler = function (request, callback) { + callback({ data: text, mimeType: 'text/html' - }); - }; - return protocol.registerStringProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerStringProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - return it('fails when sending object other than string', function(done) { - var handler; - handler = function(request, callback) { - return callback(new Date); - }; - return protocol.registerBufferProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('fails when sending object other than string', function (done) { + var handler = function (request, callback) { + callback(new Date()) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function() { - return done('request succeeded but it should not'); + $.ajax({ + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - return done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - }); - describe('protocol.registerBufferProtocol', function() { - var buffer; - buffer = new Buffer(text); - it('sends Buffer as response', function(done) { - var handler; - handler = function(request, callback) { - return callback(buffer); - }; - return protocol.registerBufferProtocol(protocolName, handler, function(error) { + }) + }) + }) + }) + + describe('protocol.registerBufferProtocol', function () { + var buffer = new Buffer(text) + + it('sends Buffer as response', function (done) { + var handler = function (request, callback) { + callback(buffer) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - it('sets Access-Control-Allow-Origin', function(done) { - var handler; - handler = function(request, callback) { - return callback(buffer); - }; - return protocol.registerBufferProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('sets Access-Control-Allow-Origin', function (done) { + var handler = function (request, callback) { + callback(buffer) + } + + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data, status, request) { - assert.equal(data, text); - assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*'); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data, status, request) { + assert.equal(data, text) + assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*') + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - it('sends object as response', function(done) { - var handler; - handler = function(request, callback) { - return callback({ + }) + }) + }) + + it('sends object as response', function (done) { + var handler = function (request, callback) { + callback({ data: buffer, mimeType: 'text/html' - }); - }; - return protocol.registerBufferProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - return it('fails when sending string', function(done) { - var handler; - handler = function(request, callback) { - return callback(text); - }; - return protocol.registerBufferProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('fails when sending string', function (done) { + var handler = function (request, callback) { + callback(text) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function() { - return done('request succeeded but it should not'); + $.ajax({ + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - return done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - }); - describe('protocol.registerFileProtocol', function() { - var fileContent, filePath, normalContent, normalPath; - filePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'file1'); - fileContent = require('fs').readFileSync(filePath); - normalPath = path.join(__dirname, 'fixtures', 'pages', 'a.html'); - normalContent = require('fs').readFileSync(normalPath); - it('sends file path as response', function(done) { - var handler; - handler = function(request, callback) { - return callback(filePath); - }; - return protocol.registerFileProtocol(protocolName, handler, function(error) { + }) + }) + }) + }) + + describe('protocol.registerFileProtocol', function () { + var filePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'file1') + var fileContent = require('fs').readFileSync(filePath) + var normalPath = path.join(__dirname, 'fixtures', 'pages', 'a.html') + var normalContent = require('fs').readFileSync(normalPath) + + it('sends file path as response', function (done) { + var handler = function (request, callback) { + callback(filePath) + } + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, String(fileContent)); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, String(fileContent)) + return done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + return done(error) } - }); - }); - }); - it('sets Access-Control-Allow-Origin', function(done) { - var handler; - handler = function(request, callback) { - return callback(filePath); - }; - return protocol.registerFileProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('sets Access-Control-Allow-Origin', function (done) { + var handler = function (request, callback) { + callback(filePath) + } + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data, status, request) { - assert.equal(data, String(fileContent)); - assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*'); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data, status, request) { + assert.equal(data, String(fileContent)) + assert.equal(request.getResponseHeader('Access-Control-Allow-Origin'), '*') + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - it('sends object as response', function(done) { - var handler; - handler = function(request, callback) { - return callback({ + }) + }) + }) + it('sends object as response', function (done) { + var handler = function (request, callback) { + callback({ path: filePath - }); - }; - return protocol.registerFileProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, String(fileContent)); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, String(fileContent)) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - it('can send normal file', function(done) { - var handler; - handler = function(request, callback) { - return callback(normalPath); - }; - return protocol.registerFileProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('can send normal file', function (done) { + var handler = function (request, callback) { + callback(normalPath) + } + + protocol.registerFileProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, String(normalContent)); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, String(normalContent)) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - it('fails when sending unexist-file', function(done) { - var fakeFilePath, handler; - fakeFilePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'not-exist'); - handler = function(request, callback) { - return callback(fakeFilePath); - }; - return protocol.registerBufferProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('fails when sending unexist-file', function (done) { + var fakeFilePath = path.join(__dirname, 'fixtures', 'asar', 'a.asar', 'not-exist') + var handler = function (request, callback) { + callback(fakeFilePath) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function() { - return done('request succeeded but it should not'); + $.ajax({ + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - return done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - return it('fails when sending unsupported content', function(done) { - var handler; - handler = function(request, callback) { - return callback(new Date); - }; - return protocol.registerBufferProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('fails when sending unsupported content', function (done) { + var handler = function (request, callback) { + callback(new Date()) + } + protocol.registerBufferProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function() { - return done('request succeeded but it should not'); + $.ajax({ + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - return done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - }); - describe('protocol.registerHttpProtocol', function() { - it('sends url as response', function(done) { - var server; - server = http.createServer(function(req, res) { - assert.notEqual(req.headers.accept, ''); - res.end(text); - return server.close(); - }); - return server.listen(0, '127.0.0.1', function() { - var handler, port, url; - port = server.address().port; - url = "http://127.0.0.1:" + port; - handler = function(request, callback) { - return callback({ + }) + }) + }) + }) + + describe('protocol.registerHttpProtocol', function () { + it('sends url as response', function (done) { + var server = http.createServer(function (req, res) { + assert.notEqual(req.headers.accept, '') + res.end(text) + server.close() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + var url = 'http://127.0.0.1:' + port + var handler = function (request, callback) { + callback({ url: url - }); - }; - return protocol.registerHttpProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerHttpProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function(data) { - assert.equal(data, text); - return done(); + $.ajax({ + url: protocolName + '://fake-host', + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); - it('fails when sending invalid url', function(done) { - var handler; - handler = function(request, callback) { - return callback({ + }) + }) + }) + }) + + it('fails when sending invalid url', function (done) { + var handler = function (request, callback) { + callback({ url: 'url' - }); - }; - return protocol.registerHttpProtocol(protocolName, handler, function(error) { + }) + } + protocol.registerHttpProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function() { - return done('request succeeded but it should not'); + $.ajax({ + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - return done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - return it('fails when sending unsupported content', function(done) { - var handler; - handler = function(request, callback) { - return callback(new Date); - }; - return protocol.registerHttpProtocol(protocolName, handler, function(error) { + }) + }) + }) + + it('fails when sending unsupported content', function (done) { + var handler = function (request, callback) { + callback(new Date()) + } + protocol.registerHttpProtocol(protocolName, handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: protocolName + "://fake-host", - success: function() { - return done('request succeeded but it should not'); + $.ajax({ + url: protocolName + '://fake-host', + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - return done(); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - }); - describe('protocol.isProtocolHandled', function() { - it('returns true for file:', function(done) { - return protocol.isProtocolHandled('file', function(result) { - assert.equal(result, true); - return done(); - }); - }); - it('returns true for http:', function(done) { - return protocol.isProtocolHandled('http', function(result) { - assert.equal(result, true); - return done(); - }); - }); - it('returns true for https:', function(done) { - return protocol.isProtocolHandled('https', function(result) { - assert.equal(result, true); - return done(); - }); - }); - it('returns false when scheme is not registred', function(done) { - return protocol.isProtocolHandled('no-exist', function(result) { - assert.equal(result, false); - return done(); - }); - }); - it('returns true for custom protocol', function(done) { - var emptyHandler; - emptyHandler = function(request, callback) { - return callback(); - }; - return protocol.registerStringProtocol(protocolName, emptyHandler, function(error) { - assert.equal(error, null); - return protocol.isProtocolHandled(protocolName, function(result) { - assert.equal(result, true); - return done(); - }); - }); - }); - return it('returns true for intercepted protocol', function(done) { - var emptyHandler; - emptyHandler = function(request, callback) { - return callback(); - }; - return protocol.interceptStringProtocol('http', emptyHandler, function(error) { - assert.equal(error, null); - return protocol.isProtocolHandled('http', function(result) { - assert.equal(result, true); - return done(); - }); - }); - }); - }); - describe('protocol.intercept(Any)Protocol', function() { - var emptyHandler; - emptyHandler = function(request, callback) { - return callback(); - }; - it('throws error when scheme is already intercepted', function(done) { - return protocol.interceptStringProtocol('http', emptyHandler, function(error) { - assert.equal(error, null); - return protocol.interceptBufferProtocol('http', emptyHandler, function(error) { - assert.notEqual(error, null); - return done(); - }); - }); - }); - it('does not crash when handler is called twice', function(done) { - var doubleHandler; - doubleHandler = function(request, callback) { + }) + }) + }) + }) + + describe('protocol.isProtocolHandled', function () { + it('returns true for file:', function (done) { + protocol.isProtocolHandled('file', function (result) { + assert.equal(result, true) + done() + }) + }) + + it('returns true for http:', function (done) { + protocol.isProtocolHandled('http', function (result) { + assert.equal(result, true) + done() + }) + }) + + it('returns true for https:', function (done) { + protocol.isProtocolHandled('https', function (result) { + assert.equal(result, true) + done() + }) + }) + + it('returns false when scheme is not registred', function (done) { + protocol.isProtocolHandled('no-exist', function (result) { + assert.equal(result, false) + done() + }) + }) + + it('returns true for custom protocol', function (done) { + var emptyHandler = function (request, callback) { + callback() + } + protocol.registerStringProtocol(protocolName, emptyHandler, function (error) { + assert.equal(error, null) + protocol.isProtocolHandled(protocolName, function (result) { + assert.equal(result, true) + done() + }) + }) + }) + + it('returns true for intercepted protocol', function (done) { + var emptyHandler = function (request, callback) { + callback() + } + protocol.interceptStringProtocol('http', emptyHandler, function (error) { + assert.equal(error, null) + protocol.isProtocolHandled('http', function (result) { + assert.equal(result, true) + done() + }) + }) + }) + }) + + describe('protocol.intercept(Any)Protocol', function () { + var emptyHandler = function (request, callback) { + callback() + } + + it('throws error when scheme is already intercepted', function (done) { + protocol.interceptStringProtocol('http', emptyHandler, function (error) { + assert.equal(error, null) + protocol.interceptBufferProtocol('http', emptyHandler, function (error) { + assert.notEqual(error, null) + done() + }) + }) + }) + + it('does not crash when handler is called twice', function (done) { + var doubleHandler = function (request, callback) { try { - callback(text); - return callback(); + callback(text) + callback() } catch (error) { // Ignore error } - }; - return protocol.interceptStringProtocol('http', doubleHandler, function(error) { - if (error) { - return done(error); - } - return $.ajax({ - url: 'http://fake-host', - success: function(data) { - assert.equal(data, text); - return done(); - }, - error: function(xhr, errorType, error) { - return done(error); - } - }); - }); - }); - return it('sends error when callback is called with nothing', function(done) { - if (process.env.TRAVIS === 'true') { - return done(); } - return protocol.interceptBufferProtocol('http', emptyHandler, function(error) { + protocol.interceptStringProtocol('http', doubleHandler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ + $.ajax({ url: 'http://fake-host', - success: function() { - return done('request succeeded but it should not'); + success: function (data) { + assert.equal(data, text) + done() }, - error: function(xhr, errorType) { - assert.equal(errorType, 'error'); - return done(); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); - describe('protocol.interceptStringProtocol', function() { - it('can intercept http protocol', function(done) { - var handler; - handler = function(request, callback) { - return callback(text); - }; - return protocol.interceptStringProtocol('http', handler, function(error) { + }) + }) + }) + + it('sends error when callback is called with nothing', function (done) { + if (process.env.TRAVIS === 'true') { + return done() + } + protocol.interceptBufferProtocol('http', emptyHandler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ + $.ajax({ url: 'http://fake-host', - success: function(data) { - assert.equal(data, text); - return done(); + success: function () { + done('request succeeded but it should not') }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType) { + assert.equal(errorType, 'error') + done() } - }); - }); - }); - it('can set content-type', function(done) { - var handler; - handler = function(request, callback) { - return callback({ + }) + }) + }) + }) + + describe('protocol.interceptStringProtocol', function () { + it('can intercept http protocol', function (done) { + var handler = function (request, callback) { + callback(text) + } + protocol.interceptStringProtocol('http', handler, function (error) { + if (error) { + return done(error) + } + $.ajax({ + url: 'http://fake-host', + success: function (data) { + assert.equal(data, text) + done() + }, + error: function (xhr, errorType, error) { + done(error) + } + }) + }) + }) + + it('can set content-type', function (done) { + var handler = function (request, callback) { + callback({ mimeType: 'application/json', data: '{"value": 1}' - }); - }; - return protocol.interceptStringProtocol('http', handler, function(error) { + }) + } + protocol.interceptStringProtocol('http', handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ + $.ajax({ url: 'http://fake-host', - success: function(data) { - assert.equal(typeof data, 'object'); - assert.equal(data.value, 1); - return done(); + success: function (data) { + assert.equal(typeof data, 'object') + assert.equal(data.value, 1) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - return it('can receive post data', function(done) { - var handler; - handler = function(request, callback) { - var uploadData; - uploadData = request.uploadData[0].bytes.toString(); - return callback({ + }) + }) + }) + + it('can receive post data', function (done) { + var handler = function (request, callback) { + var uploadData = request.uploadData[0].bytes.toString() + callback({ data: uploadData - }); - }; - return protocol.interceptStringProtocol('http', handler, function(error) { + }) + } + protocol.interceptStringProtocol('http', handler, function (error) { if (error) { - return done(error); + return done(error) } - return $.ajax({ - url: "http://fake-host", - type: "POST", - data: postData, - success: function(data) { - assert.deepEqual(qs.parse(data), postData); - return done(); - }, - error: function(xhr, errorType, error) { - return done(error); - } - }); - }); - }); - }); - describe('protocol.interceptBufferProtocol', function() { - it('can intercept http protocol', function(done) { - var handler; - handler = function(request, callback) { - return callback(new Buffer(text)); - }; - return protocol.interceptBufferProtocol('http', handler, function(error) { - if (error) { - return done(error); - } - return $.ajax({ + $.ajax({ url: 'http://fake-host', - success: function(data) { - assert.equal(data, text); - return done(); - }, - error: function(xhr, errorType, error) { - return done(error); - } - }); - }); - }); - return it('can receive post data', function(done) { - var handler; - handler = function(request, callback) { - var uploadData; - uploadData = request.uploadData[0].bytes; - return callback(uploadData); - }; - return protocol.interceptBufferProtocol('http', handler, function(error) { - if (error) { - return done(error); - } - return $.ajax({ - url: "http://fake-host", - type: "POST", + type: 'POST', data: postData, - success: function(data) { - assert.equal(data, $.param(postData)); - return done(); + success: function (data) { + assert.deepEqual(qs.parse(data), postData) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); - describe('protocol.interceptHttpProtocol', function() { - return it('can send POST request', function(done) { - var server; - server = http.createServer(function(req, res) { - var body; - body = ''; - req.on('data', function(chunk) { - return body += chunk; - }); - req.on('end', function() { - return res.end(body); - }); - return server.close(); - }); - return server.listen(0, '127.0.0.1', function() { - var handler, port, url; - port = server.address().port; - url = "http://127.0.0.1:" + port; - handler = function(request, callback) { - var data; - data = { + }) + }) + }) + }) + + describe('protocol.interceptBufferProtocol', function () { + it('can intercept http protocol', function (done) { + var handler = function (request, callback) { + callback(new Buffer(text)) + } + protocol.interceptBufferProtocol('http', handler, function (error) { + if (error) { + return done(error) + } + $.ajax({ + url: 'http://fake-host', + success: function (data) { + assert.equal(data, text) + done() + }, + error: function (xhr, errorType, error) { + done(error) + } + }) + }) + }) + + it('can receive post data', function (done) { + var handler = function (request, callback) { + var uploadData = request.uploadData[0].bytes + callback(uploadData) + } + protocol.interceptBufferProtocol('http', handler, function (error) { + if (error) { + return done(error) + } + $.ajax({ + url: 'http://fake-host', + type: 'POST', + data: postData, + success: function (data) { + assert.equal(data, $.param(postData)) + done() + }, + error: function (xhr, errorType, error) { + done(error) + } + }) + }) + }) + }) + + describe('protocol.interceptHttpProtocol', function () { + it('can send POST request', function (done) { + var server = http.createServer(function (req, res) { + var body = '' + req.on('data', function (chunk) { + body += chunk + }) + req.on('end', function () { + res.end(body) + }) + server.close() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + var url = 'http://127.0.0.1:' + port + var handler = function (request, callback) { + var data = { url: url, method: 'POST', uploadData: { @@ -771,41 +775,43 @@ describe('protocol module', function() { data: request.uploadData[0].bytes.toString() }, session: null - }; - return callback(data); - }; - return protocol.interceptHttpProtocol('http', handler, function(error) { - if (error) { - return done(error); } - return $.ajax({ - url: "http://fake-host", - type: "POST", + callback(data) + } + protocol.interceptHttpProtocol('http', handler, function (error) { + if (error) { + return done(error) + } + $.ajax({ + url: 'http://fake-host', + type: 'POST', data: postData, - success: function(data) { - assert.deepEqual(qs.parse(data), postData); - return done(); + success: function (data) { + assert.deepEqual(qs.parse(data), postData) + done() }, - error: function(xhr, errorType, error) { - return done(error); + error: function (xhr, errorType, error) { + done(error) } - }); - }); - }); - }); - }); - return describe('protocol.uninterceptProtocol', function() { - it('returns error when scheme does not exist', function(done) { - return protocol.uninterceptProtocol('not-exist', function(error) { - assert.notEqual(error, null); - return done(); - }); - }); - return it('returns error when scheme is not intercepted', function(done) { - return protocol.uninterceptProtocol('http', function(error) { - assert.notEqual(error, null); - return done(); - }); - }); - }); -}); + }) + }) + }) + }) + }) + + describe('protocol.uninterceptProtocol', function () { + it('returns error when scheme does not exist', function (done) { + protocol.uninterceptProtocol('not-exist', function (error) { + assert.notEqual(error, null) + done() + }) + }) + + it('returns error when scheme is not intercepted', function (done) { + protocol.uninterceptProtocol('http', function (error) { + assert.notEqual(error, null) + done() + }) + }) + }) +}) diff --git a/spec/api-screen-spec.js b/spec/api-screen-spec.js index b393d4b99ee..8c4f4305baf 100644 --- a/spec/api-screen-spec.js +++ b/spec/api-screen-spec.js @@ -1,25 +1,21 @@ -var assert, screen; +const assert = require('assert') +const screen = require('electron').screen -assert = require('assert'); +describe('screen module', function () { + describe('screen.getCursorScreenPoint()', function () { + it('returns a point object', function () { + var point = screen.getCursorScreenPoint() + assert.equal(typeof point.x, 'number') + assert.equal(typeof point.y, 'number') + }) + }) -screen = require('electron').screen; - -describe('screen module', function() { - describe('screen.getCursorScreenPoint()', function() { - return it('returns a point object', function() { - var point; - point = screen.getCursorScreenPoint(); - assert.equal(typeof point.x, 'number'); - return assert.equal(typeof point.y, 'number'); - }); - }); - return describe('screen.getPrimaryDisplay()', function() { - return it('returns a display object', function() { - var display; - display = screen.getPrimaryDisplay(); - assert.equal(typeof display.scaleFactor, 'number'); - assert(display.size.width > 0); - return assert(display.size.height > 0); - }); - }); -}); + describe('screen.getPrimaryDisplay()', function () { + it('returns a display object', function () { + var display = screen.getPrimaryDisplay() + assert.equal(typeof display.scaleFactor, 'number') + assert(display.size.width > 0) + assert(display.size.height > 0) + }) + }) +}) diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index 93402047390..d655788c79b 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -1,220 +1,265 @@ -const assert = require('assert'); -const http = require('http'); -const path = require('path'); -const fs = require('fs'); +const assert = require('assert') +const http = require('http') +const path = require('path') +const fs = require('fs') -const ipcRenderer = require('electron').ipcRenderer; -const remote = require('electron').remote; +const ipcRenderer = require('electron').ipcRenderer +const remote = require('electron').remote -const ipcMain = remote.ipcMain; -const session = remote.session; -const BrowserWindow = remote.BrowserWindow; +const ipcMain = remote.ipcMain +const session = remote.session +const BrowserWindow = remote.BrowserWindow -describe('session module', function() { - var fixtures, url, w; - this.timeout(10000); - fixtures = path.resolve(__dirname, 'fixtures'); - w = null; - url = "http://127.0.0.1"; - beforeEach(function() { - return w = new BrowserWindow({ +describe('session module', function () { + this.timeout(10000) + + var fixtures = path.resolve(__dirname, 'fixtures') + var w = null + var url = 'http://127.0.0.1' + + beforeEach(function () { + w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - }); - afterEach(function() { - return w.destroy(); - }); + }) + }) - describe('session.cookies', function() { - it('should get cookies', function(done) { - var server; - server = http.createServer(function(req, res) { - res.setHeader('Set-Cookie', ['0=0']); - res.end('finished'); - return server.close(); - }); - return server.listen(0, '127.0.0.1', function() { - var port; - port = server.address().port; - w.loadURL(url + ":" + port); - return w.webContents.on('did-finish-load', function() { - return w.webContents.session.cookies.get({ + afterEach(function () { + w.destroy() + }) + + describe('session.cookies', function () { + it('should get cookies', function (done) { + var server = http.createServer(function (req, res) { + res.setHeader('Set-Cookie', ['0=0']) + res.end('finished') + server.close() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + w.loadURL(url + ':' + port) + w.webContents.on('did-finish-load', function () { + w.webContents.session.cookies.get({ url: url - }, function(error, list) { - var cookie, i, len; + }, function (error, list) { + var cookie, i, len if (error) { - return done(error); + return done(error) } for (i = 0, len = list.length; i < len; i++) { - cookie = list[i]; + cookie = list[i] if (cookie.name === '0') { if (cookie.value === '0') { - return done(); + return done() } else { - return done("cookie value is " + cookie.value + " while expecting 0"); + return done('cookie value is ' + cookie.value + ' while expecting 0') } } } - return done('Can not find cookie'); - }); - }); - }); - }); - it('should over-write the existent cookie', function(done) { - return session.defaultSession.cookies.set({ + done('Can not find cookie') + }) + }) + }) + }) + + it('should over-write the existent cookie', function (done) { + session.defaultSession.cookies.set({ url: url, name: '1', value: '1' - }, function(error) { + }, function (error) { if (error) { - return done(error); + return done(error) } - return session.defaultSession.cookies.get({ + session.defaultSession.cookies.get({ url: url - }, function(error, list) { - var cookie, i, len; + }, function (error, list) { + var cookie, i, len if (error) { - return done(error); + return done(error) } for (i = 0, len = list.length; i < len; i++) { - cookie = list[i]; + cookie = list[i] if (cookie.name === '1') { if (cookie.value === '1') { - return done(); + return done() } else { - return done("cookie value is " + cookie.value + " while expecting 1"); + return done('cookie value is ' + cookie.value + ' while expecting 1') } } } - return done('Can not find cookie'); - }); - }); - }); - it('should remove cookies', function(done) { - return session.defaultSession.cookies.set({ + done('Can not find cookie') + }) + }) + }) + + it('should remove cookies', function (done) { + session.defaultSession.cookies.set({ url: url, name: '2', value: '2' - }, function(error) { + }, function (error) { if (error) { - return done(error); + return done(error) } - return session.defaultSession.cookies.remove(url, '2', function() { - return session.defaultSession.cookies.get({ + session.defaultSession.cookies.remove(url, '2', function () { + session.defaultSession.cookies.get({ url: url - }, function(error, list) { - var cookie, i, len; + }, function (error, list) { + var cookie, i, len if (error) { - return done(error); + return done(error) } for (i = 0, len = list.length; i < len; i++) { - cookie = list[i]; + cookie = list[i] if (cookie.name === '2') { - return done('Cookie not deleted'); + return done('Cookie not deleted') } } - return done(); - }); - }); - }); - }); - }); + done() + }) + }) + }) + }) + }) - describe('session.clearStorageData(options)', function() { - fixtures = path.resolve(__dirname, 'fixtures'); - return it('clears localstorage data', function(done) { - ipcMain.on('count', function(event, count) { - ipcMain.removeAllListeners('count'); - assert(!count); - return done(); - }); - w.loadURL('file://' + path.join(fixtures, 'api', 'localstorage.html')); - return w.webContents.on('did-finish-load', function() { - var options; - options = { - origin: "file://", + describe('session.clearStorageData(options)', function () { + fixtures = path.resolve(__dirname, 'fixtures') + it('clears localstorage data', function (done) { + ipcMain.on('count', function (event, count) { + ipcMain.removeAllListeners('count') + assert(!count) + done() + }) + w.loadURL('file://' + path.join(fixtures, 'api', 'localstorage.html')) + w.webContents.on('did-finish-load', function () { + var options = { + origin: 'file://', storages: ['localstorage'], quotas: ['persistent'] - }; - return w.webContents.session.clearStorageData(options, function() { - return w.webContents.send('getcount'); - }); - }); - }); - }); - return describe('DownloadItem', function() { - var assertDownload, contentDisposition, downloadFilePath, downloadServer, mockPDF; - mockPDF = new Buffer(1024 * 1024 * 5); - contentDisposition = 'inline; filename="mock.pdf"'; - downloadFilePath = path.join(fixtures, 'mock.pdf'); - downloadServer = http.createServer(function(req, res) { + } + w.webContents.session.clearStorageData(options, function () { + w.webContents.send('getcount') + }) + }) + }) + }) + + describe('session will-download event', function () { + var w = null + + beforeEach(function () { + w = new BrowserWindow({ + show: false, + width: 400, + height: 400 + }) + }) + + afterEach(function () { + w.destroy() + }) + + it('can cancel default download behavior', function (done) { + const mockFile = new Buffer(1024) + const contentDisposition = 'inline; filename="mockFile.txt"' + const downloadServer = http.createServer(function (req, res) { + res.writeHead(200, { + 'Content-Length': mockFile.length, + 'Content-Type': 'application/plain', + 'Content-Disposition': contentDisposition + }) + res.end(mockFile) + downloadServer.close() + }) + + downloadServer.listen(0, '127.0.0.1', function () { + const port = downloadServer.address().port + const url = 'http://127.0.0.1:' + port + '/' + + ipcRenderer.sendSync('set-download-option', false, true) + w.loadURL(url) + ipcRenderer.once('download-error', function (event, downloadUrl, filename, error) { + assert.equal(downloadUrl, url) + assert.equal(filename, 'mockFile.txt') + assert.equal(error, 'Object has been destroyed') + done() + }) + }) + }) + }) + + describe('DownloadItem', function () { + var mockPDF = new Buffer(1024 * 1024 * 5) + var contentDisposition = 'inline; filename="mock.pdf"' + var downloadFilePath = path.join(fixtures, 'mock.pdf') + var downloadServer = http.createServer(function (req, res) { res.writeHead(200, { 'Content-Length': mockPDF.length, 'Content-Type': 'application/pdf', 'Content-Disposition': contentDisposition - }); - res.end(mockPDF); - return downloadServer.close(); - }); - assertDownload = function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) { - assert.equal(state, 'completed'); - assert.equal(filename, 'mock.pdf'); - assert.equal(url, "http://127.0.0.1:" + port + "/"); - assert.equal(mimeType, 'application/pdf'); - assert.equal(receivedBytes, mockPDF.length); - assert.equal(totalBytes, mockPDF.length); - assert.equal(disposition, contentDisposition); - assert(fs.existsSync(downloadFilePath)); - return fs.unlinkSync(downloadFilePath); - }; - it('can download using BrowserWindow.loadURL', function(done) { - return downloadServer.listen(0, '127.0.0.1', function() { - var port; - port = downloadServer.address().port; - ipcRenderer.sendSync('set-download-option', false); - w.loadURL(url + ":" + port); - return ipcRenderer.once('download-done', function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { - assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port); - return done(); - }); - }); - }); - it('can download using WebView.downloadURL', function(done) { - return downloadServer.listen(0, '127.0.0.1', function() { - var port, webview; - port = downloadServer.address().port; - ipcRenderer.sendSync('set-download-option', false); - webview = new WebView; - webview.src = "file://" + fixtures + "/api/blank.html"; - webview.addEventListener('did-finish-load', function() { - return webview.downloadURL(url + ":" + port + "/"); - }); - ipcRenderer.once('download-done', function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { - assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port); - document.body.removeChild(webview); - return done(); - }); - return document.body.appendChild(webview); - }); - }); - return it('can cancel download', function(done) { - return downloadServer.listen(0, '127.0.0.1', function() { - var port; - port = downloadServer.address().port; - ipcRenderer.sendSync('set-download-option', true); - w.loadURL(url + ":" + port + "/"); - return ipcRenderer.once('download-done', function(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { - assert.equal(state, 'cancelled'); - assert.equal(filename, 'mock.pdf'); - assert.equal(mimeType, 'application/pdf'); - assert.equal(receivedBytes, 0); - assert.equal(totalBytes, mockPDF.length); - assert.equal(disposition, contentDisposition); - return done(); - }); - }); - }); - }); -}); + }) + res.end(mockPDF) + downloadServer.close() + }) + var assertDownload = function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) { + assert.equal(state, 'completed') + assert.equal(filename, 'mock.pdf') + assert.equal(url, 'http://127.0.0.1:' + port + '/') + assert.equal(mimeType, 'application/pdf') + assert.equal(receivedBytes, mockPDF.length) + assert.equal(totalBytes, mockPDF.length) + assert.equal(disposition, contentDisposition) + assert(fs.existsSync(downloadFilePath)) + fs.unlinkSync(downloadFilePath) + } + + it('can download using BrowserWindow.loadURL', 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) + ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) + done() + }) + }) + }) + + it('can download using WebView.downloadURL', function (done) { + downloadServer.listen(0, '127.0.0.1', function () { + var port = downloadServer.address().port + ipcRenderer.sendSync('set-download-option', false, false) + var webview = new WebView() + webview.src = 'file://' + fixtures + '/api/blank.html' + webview.addEventListener('did-finish-load', function () { + webview.downloadURL(url + ':' + port + '/') + }) + ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port) + document.body.removeChild(webview) + done() + }) + document.body.appendChild(webview) + }) + }) + + it('can cancel download', function (done) { + downloadServer.listen(0, '127.0.0.1', function () { + var port = downloadServer.address().port + ipcRenderer.sendSync('set-download-option', true, false) + w.loadURL(url + ':' + port + '/') + ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) { + assert.equal(state, 'cancelled') + assert.equal(filename, 'mock.pdf') + assert.equal(mimeType, 'application/pdf') + assert.equal(receivedBytes, 0) + assert.equal(totalBytes, mockPDF.length) + assert.equal(disposition, contentDisposition) + done() + }) + }) + }) + }) +}) diff --git a/spec/api-web-frame-spec.js b/spec/api-web-frame-spec.js index 3d287a6acba..450bf1c33ec 100644 --- a/spec/api-web-frame-spec.js +++ b/spec/api-web-frame-spec.js @@ -1,25 +1,19 @@ -var assert, path, webFrame; +const assert = require('assert') +const path = require('path') +const webFrame = require('electron').webFrame -assert = require('assert'); - -path = require('path'); - -webFrame = require('electron').webFrame; - -describe('webFrame module', function() { - var fixtures; - fixtures = path.resolve(__dirname, 'fixtures'); - return describe('webFrame.registerURLSchemeAsPrivileged', function() { - return it('supports fetch api', function(done) { - var url; - webFrame.registerURLSchemeAsPrivileged('file'); - url = "file://" + fixtures + "/assets/logo.png"; - return fetch(url).then(function(response) { - assert(response.ok); - return done(); - })["catch"](function(err) { - return done('unexpected error : ' + err); - }); - }); - }); -}); +describe('webFrame module', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + describe('webFrame.registerURLSchemeAsPrivileged', function () { + it('supports fetch api', function (done) { + webFrame.registerURLSchemeAsPrivileged('file') + var url = 'file://' + fixtures + '/assets/logo.png' + window.fetch(url).then(function (response) { + assert(response.ok) + done() + }).catch(function (err) { + done('unexpected error : ' + err) + }) + }) + }) +}) diff --git a/spec/api-web-request-spec.js b/spec/api-web-request-spec.js index 6731d995a61..abc4f9568cd 100644 --- a/spec/api-web-request-spec.js +++ b/spec/api-web-request-spec.js @@ -1,367 +1,412 @@ -const assert = require('assert'); -const http = require('http'); -const remote = require('electron').remote; -const session = remote.session; +const assert = require('assert') +const http = require('http') +const qs = require('querystring') +const remote = require('electron').remote +const session = remote.session -describe('webRequest module', function() { - var defaultURL, server, ses; - ses = session.defaultSession; - server = http.createServer(function(req, res) { - var content; - res.setHeader('Custom', ['Header']); - content = req.url; +describe('webRequest module', function () { + var ses = session.defaultSession + var server = http.createServer(function (req, res) { + res.setHeader('Custom', ['Header']) + var content = req.url if (req.headers.accept === '*/*;test/header') { - content += 'header/received'; + content += 'header/received' } - return res.end(content); - }); - defaultURL = null; - before(function(done) { - return server.listen(0, '127.0.0.1', function() { - var port; - port = server.address().port; - defaultURL = "http://127.0.0.1:" + port + "/"; - return done(); - }); - }); - after(function() { - return server.close(); - }); - describe('webRequest.onBeforeRequest', function() { - afterEach(function() { - return ses.webRequest.onBeforeRequest(null); - }); - it('can cancel the request', function(done) { - ses.webRequest.onBeforeRequest(function(details, callback) { - return callback({ + res.end(content) + }) + var defaultURL = null + + before(function (done) { + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + defaultURL = 'http://127.0.0.1:' + port + '/' + done() + }) + }) + + after(function () { + server.close() + }) + + describe('webRequest.onBeforeRequest', function () { + afterEach(function () { + ses.webRequest.onBeforeRequest(null) + }) + + it('can cancel the request', function (done) { + ses.webRequest.onBeforeRequest(function (details, callback) { + callback({ cancel: true - }); - }); - return $.ajax({ + }) + }) + $.ajax({ url: defaultURL, - success: function() { - return done('unexpected success'); + success: function () { + done('unexpected success') }, - error: function() { - return done(); + error: function () { + done() } - }); - }); - it('can filter URLs', function(done) { - var filter; - filter = { - urls: [defaultURL + "filter/*"] - }; - ses.webRequest.onBeforeRequest(filter, function(details, callback) { - return callback({ + }) + }) + + it('can filter URLs', function (done) { + var filter = { + urls: [defaultURL + 'filter/*'] + } + ses.webRequest.onBeforeRequest(filter, function (details, callback) { + callback({ cancel: true - }); - }); - return $.ajax({ - url: defaultURL + "nofilter/test", - success: function(data) { - assert.equal(data, '/nofilter/test'); - return $.ajax({ - url: defaultURL + "filter/test", - success: function() { - return done('unexpected success'); + }) + }) + $.ajax({ + url: defaultURL + 'nofilter/test', + success: function (data) { + assert.equal(data, '/nofilter/test') + $.ajax({ + url: defaultURL + 'filter/test', + success: function () { + done('unexpected success') }, - error: function() { - return done(); + error: function () { + done() } - }); + }) }, - error: function(xhr, errorType) { - return done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); - it('receives details object', function(done) { - ses.webRequest.onBeforeRequest(function(details, callback) { - assert.equal(typeof details.id, 'number'); - assert.equal(typeof details.timestamp, 'number'); - assert.equal(details.url, defaultURL); - assert.equal(details.method, 'GET'); - assert.equal(details.resourceType, 'xhr'); - return callback({}); - }); - return $.ajax({ + }) + }) + + it('receives details object', function (done) { + ses.webRequest.onBeforeRequest(function (details, callback) { + assert.equal(typeof details.id, 'number') + assert.equal(typeof details.timestamp, 'number') + assert.equal(details.url, defaultURL) + assert.equal(details.method, 'GET') + assert.equal(details.resourceType, 'xhr') + assert(!details.uploadData) + callback({}) + }) + $.ajax({ url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - return done(); + success: function (data) { + assert.equal(data, '/') + done() }, - error: function(xhr, errorType) { - return done(errorType); + error: function (xhr, errorType) { + done(errorType) } - }); - }); - return it('can redirect the request', function(done) { - ses.webRequest.onBeforeRequest(function(details, callback) { - if (details.url === defaultURL) { - return callback({ - redirectURL: defaultURL + "redirect" - }); - } else { - return callback({}); - } - }); - return $.ajax({ - url: defaultURL, - success: function(data) { - assert.equal(data, '/redirect'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - }); - describe('webRequest.onBeforeSendHeaders', function() { - afterEach(function() { - return ses.webRequest.onBeforeSendHeaders(null); - }); - it('receives details object', function(done) { - ses.webRequest.onBeforeSendHeaders(function(details, callback) { - assert.equal(typeof details.requestHeaders, 'object'); - return callback({}); - }); - return $.ajax({ - url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - it('can change the request headers', function(done) { - ses.webRequest.onBeforeSendHeaders(function(details, callback) { - var requestHeaders; - requestHeaders = details.requestHeaders; - requestHeaders.Accept = '*/*;test/header'; - return callback({ - requestHeaders: requestHeaders - }); - }); - return $.ajax({ - url: defaultURL, - success: function(data) { - assert.equal(data, '/header/received'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - return it('resets the whole headers', function(done) { - var requestHeaders; - requestHeaders = { - Test: 'header' - }; - ses.webRequest.onBeforeSendHeaders(function(details, callback) { - return callback({ - requestHeaders: requestHeaders - }); - }); - ses.webRequest.onSendHeaders(function(details) { - assert.deepEqual(details.requestHeaders, requestHeaders); - return done(); - }); - return $.ajax({ - url: defaultURL, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - }); - describe('webRequest.onSendHeaders', function() { - afterEach(function() { - return ses.webRequest.onSendHeaders(null); - }); - return it('receives details object', function(done) { - ses.webRequest.onSendHeaders(function(details) { - return assert.equal(typeof details.requestHeaders, 'object'); - }); - return $.ajax({ - url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - }); - describe('webRequest.onHeadersReceived', function() { - afterEach(function() { - return ses.webRequest.onHeadersReceived(null); - }); - it('receives details object', function(done) { - ses.webRequest.onHeadersReceived(function(details, callback) { - assert.equal(details.statusLine, 'HTTP/1.1 200 OK'); - assert.equal(details.statusCode, 200); - assert.equal(details.responseHeaders['Custom'], 'Header'); - return callback({}); - }); - return $.ajax({ - url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - it('can change the response header', function(done) { - ses.webRequest.onHeadersReceived(function(details, callback) { - var responseHeaders; - responseHeaders = details.responseHeaders; - responseHeaders['Custom'] = ['Changed']; - return callback({ - responseHeaders: responseHeaders - }); - }); - return $.ajax({ - url: defaultURL, - success: function(data, status, xhr) { - assert.equal(xhr.getResponseHeader('Custom'), 'Changed'); - assert.equal(data, '/'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - return it('does not change header by default', function(done) { - ses.webRequest.onHeadersReceived(function(details, callback) { - return callback({}); - }); - return $.ajax({ - url: defaultURL, - success: function(data, status, xhr) { - assert.equal(xhr.getResponseHeader('Custom'), 'Header'); - assert.equal(data, '/'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - }); - describe('webRequest.onResponseStarted', function() { - afterEach(function() { - return ses.webRequest.onResponseStarted(null); - }); - return it('receives details object', function(done) { - ses.webRequest.onResponseStarted(function(details) { - assert.equal(typeof details.fromCache, 'boolean'); - assert.equal(details.statusLine, 'HTTP/1.1 200 OK'); - assert.equal(details.statusCode, 200); - return assert.equal(details.responseHeaders['Custom'], 'Header'); - }); - return $.ajax({ - url: defaultURL, - success: function(data, status, xhr) { - assert.equal(xhr.getResponseHeader('Custom'), 'Header'); - assert.equal(data, '/'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - }); - describe('webRequest.onBeforeRedirect', function() { - afterEach(function() { - ses.webRequest.onBeforeRedirect(null); - return ses.webRequest.onBeforeRequest(null); - }); - return it('receives details object', function(done) { - var redirectURL; - redirectURL = defaultURL + "redirect"; - ses.webRequest.onBeforeRequest(function(details, callback) { - if (details.url === defaultURL) { - return callback({ - redirectURL: redirectURL - }); - } else { - return callback({}); - } - }); - ses.webRequest.onBeforeRedirect(function(details) { - assert.equal(typeof details.fromCache, 'boolean'); - assert.equal(details.statusLine, 'HTTP/1.1 307 Internal Redirect'); - assert.equal(details.statusCode, 307); - return assert.equal(details.redirectURL, redirectURL); - }); - return $.ajax({ - url: defaultURL, - success: function(data) { - assert.equal(data, '/redirect'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - }); - describe('webRequest.onCompleted', function() { - afterEach(function() { - return ses.webRequest.onCompleted(null); - }); - return it('receives details object', function(done) { - ses.webRequest.onCompleted(function(details) { - assert.equal(typeof details.fromCache, 'boolean'); - assert.equal(details.statusLine, 'HTTP/1.1 200 OK'); - return assert.equal(details.statusCode, 200); - }); - return $.ajax({ - url: defaultURL, - success: function(data) { - assert.equal(data, '/'); - return done(); - }, - error: function(xhr, errorType) { - return done(errorType); - } - }); - }); - }); - return describe('webRequest.onErrorOccurred', function() { - afterEach(function() { - ses.webRequest.onErrorOccurred(null); - return ses.webRequest.onBeforeRequest(null); - }); - return it('receives details object', function(done) { - ses.webRequest.onBeforeRequest(function(details, callback) { - return callback({ + }) + }) + + it('receives post data in details object', function (done) { + var postData = { + name: 'post test', + type: 'string' + } + ses.webRequest.onBeforeRequest(function (details, callback) { + assert.equal(details.url, defaultURL) + assert.equal(details.method, 'POST') + assert.equal(details.uploadData.length, 1) + var data = qs.parse(details.uploadData[0].bytes.toString()) + assert.deepEqual(data, postData) + callback({ cancel: true - }); - }); - ses.webRequest.onErrorOccurred(function(details) { - assert.equal(details.error, 'net::ERR_BLOCKED_BY_CLIENT'); - return done(); - }); - return $.ajax({ + }) + }) + $.ajax({ url: defaultURL, - success: function() { - return done('unexpected success'); + type: 'POST', + data: postData, + success: function () {}, + error: function () { + done() } - }); - }); - }); -}); + }) + }) + + it('can redirect the request', function (done) { + ses.webRequest.onBeforeRequest(function (details, callback) { + if (details.url === defaultURL) { + callback({ + redirectURL: defaultURL + 'redirect' + }) + } else { + callback({}) + } + }) + $.ajax({ + url: defaultURL, + success: function (data) { + assert.equal(data, '/redirect') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + }) + + describe('webRequest.onBeforeSendHeaders', function () { + afterEach(function () { + ses.webRequest.onBeforeSendHeaders(null) + }) + + it('receives details object', function (done) { + ses.webRequest.onBeforeSendHeaders(function (details, callback) { + assert.equal(typeof details.requestHeaders, 'object') + callback({}) + }) + $.ajax({ + url: defaultURL, + success: function (data) { + assert.equal(data, '/') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + + it('can change the request headers', function (done) { + ses.webRequest.onBeforeSendHeaders(function (details, callback) { + var requestHeaders = details.requestHeaders + requestHeaders.Accept = '*/*;test/header' + callback({ + requestHeaders: requestHeaders + }) + }) + $.ajax({ + url: defaultURL, + success: function (data) { + assert.equal(data, '/header/received') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + + it('resets the whole headers', function (done) { + var requestHeaders = { + Test: 'header' + } + ses.webRequest.onBeforeSendHeaders(function (details, callback) { + callback({ + requestHeaders: requestHeaders + }) + }) + ses.webRequest.onSendHeaders(function (details) { + assert.deepEqual(details.requestHeaders, requestHeaders) + done() + }) + $.ajax({ + url: defaultURL, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + }) + + describe('webRequest.onSendHeaders', function () { + afterEach(function () { + ses.webRequest.onSendHeaders(null) + }) + + it('receives details object', function (done) { + ses.webRequest.onSendHeaders(function (details) { + assert.equal(typeof details.requestHeaders, 'object') + }) + $.ajax({ + url: defaultURL, + success: function (data) { + assert.equal(data, '/') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + }) + + describe('webRequest.onHeadersReceived', function () { + afterEach(function () { + ses.webRequest.onHeadersReceived(null) + }) + + it('receives details object', function (done) { + ses.webRequest.onHeadersReceived(function (details, callback) { + assert.equal(details.statusLine, 'HTTP/1.1 200 OK') + assert.equal(details.statusCode, 200) + assert.equal(details.responseHeaders['Custom'], 'Header') + callback({}) + }) + $.ajax({ + url: defaultURL, + success: function (data) { + assert.equal(data, '/') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + + it('can change the response header', function (done) { + ses.webRequest.onHeadersReceived(function (details, callback) { + var responseHeaders = details.responseHeaders + responseHeaders['Custom'] = ['Changed'] + callback({ + responseHeaders: responseHeaders + }) + }) + $.ajax({ + url: defaultURL, + success: function (data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Changed') + assert.equal(data, '/') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + + it('does not change header by default', function (done) { + ses.webRequest.onHeadersReceived(function (details, callback) { + callback({}) + }) + $.ajax({ + url: defaultURL, + success: function (data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Header') + assert.equal(data, '/') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + }) + + describe('webRequest.onResponseStarted', function () { + afterEach(function () { + ses.webRequest.onResponseStarted(null) + }) + + it('receives details object', function (done) { + ses.webRequest.onResponseStarted(function (details) { + assert.equal(typeof details.fromCache, 'boolean') + assert.equal(details.statusLine, 'HTTP/1.1 200 OK') + assert.equal(details.statusCode, 200) + assert.equal(details.responseHeaders['Custom'], 'Header') + }) + $.ajax({ + url: defaultURL, + success: function (data, status, xhr) { + assert.equal(xhr.getResponseHeader('Custom'), 'Header') + assert.equal(data, '/') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + }) + + describe('webRequest.onBeforeRedirect', function () { + afterEach(function () { + ses.webRequest.onBeforeRedirect(null) + ses.webRequest.onBeforeRequest(null) + }) + + it('receives details object', function (done) { + var redirectURL = defaultURL + 'redirect' + ses.webRequest.onBeforeRequest(function (details, callback) { + if (details.url === defaultURL) { + callback({ + redirectURL: redirectURL + }) + } else { + callback({}) + } + }) + ses.webRequest.onBeforeRedirect(function (details) { + assert.equal(typeof details.fromCache, 'boolean') + assert.equal(details.statusLine, 'HTTP/1.1 307 Internal Redirect') + assert.equal(details.statusCode, 307) + assert.equal(details.redirectURL, redirectURL) + }) + $.ajax({ + url: defaultURL, + success: function (data) { + assert.equal(data, '/redirect') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + }) + + describe('webRequest.onCompleted', function () { + afterEach(function () { + ses.webRequest.onCompleted(null) + }) + + it('receives details object', function (done) { + ses.webRequest.onCompleted(function (details) { + assert.equal(typeof details.fromCache, 'boolean') + assert.equal(details.statusLine, 'HTTP/1.1 200 OK') + assert.equal(details.statusCode, 200) + }) + $.ajax({ + url: defaultURL, + success: function (data) { + assert.equal(data, '/') + done() + }, + error: function (xhr, errorType) { + done(errorType) + } + }) + }) + }) + + describe('webRequest.onErrorOccurred', function () { + afterEach(function () { + ses.webRequest.onErrorOccurred(null) + ses.webRequest.onBeforeRequest(null) + }) + + it('receives details object', function (done) { + ses.webRequest.onBeforeRequest(function (details, callback) { + callback({ + cancel: true + }) + }) + ses.webRequest.onErrorOccurred(function (details) { + assert.equal(details.error, 'net::ERR_BLOCKED_BY_CLIENT') + done() + }) + $.ajax({ + url: defaultURL, + success: function () { + done('unexpected success') + } + }) + }) + }) +}) diff --git a/spec/asar-spec.js b/spec/asar-spec.js index 81d881f7eb4..a0cb7b4aabd 100644 --- a/spec/asar-spec.js +++ b/spec/asar-spec.js @@ -1,791 +1,815 @@ -const assert = require('assert'); -const child_process = require('child_process'); -const fs = require('fs'); -const path = require('path'); +const assert = require('assert') +const child_process = require('child_process') +const fs = require('fs') +const path = require('path') -const nativeImage = require('electron').nativeImage; -const remote = require('electron').remote; +const nativeImage = require('electron').nativeImage +const remote = require('electron').remote -const ipcMain = remote.require('electron').ipcMain; -const BrowserWindow = remote.require('electron').BrowserWindow; +const ipcMain = remote.require('electron').ipcMain +const BrowserWindow = remote.require('electron').BrowserWindow -describe('asar package', function() { - var fixtures; - fixtures = path.join(__dirname, 'fixtures'); - describe('node api', function() { - describe('fs.readFileSync', function() { - it('does not leak fd', function() { - var readCalls = 1; - while(readCalls <= 10000) { - fs.readFileSync(path.join(process.resourcesPath, 'atom.asar', 'renderer', 'api', 'lib', 'ipc.js')); - readCalls++; +describe('asar package', function () { + var fixtures = path.join(__dirname, 'fixtures') + + describe('node api', function () { + describe('fs.readFileSync', function () { + it('does not leak fd', function () { + var readCalls = 1 + while (readCalls <= 10000) { + fs.readFileSync(path.join(process.resourcesPath, 'electron.asar', 'renderer', 'api', 'ipc.js')) + readCalls++ } - }); - it('reads a normal file', function() { - var file1, file2, file3; - file1 = path.join(fixtures, 'asar', 'a.asar', 'file1'); - assert.equal(fs.readFileSync(file1).toString().trim(), 'file1'); - file2 = path.join(fixtures, 'asar', 'a.asar', 'file2'); - assert.equal(fs.readFileSync(file2).toString().trim(), 'file2'); - file3 = path.join(fixtures, 'asar', 'a.asar', 'file3'); - return assert.equal(fs.readFileSync(file3).toString().trim(), 'file3'); - }); - it('reads from a empty file', function() { - var buffer, file; - file = path.join(fixtures, 'asar', 'empty.asar', 'file1'); - buffer = fs.readFileSync(file); - assert.equal(buffer.length, 0); - return assert.equal(buffer.toString(), ''); - }); - it('reads a linked file', function() { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'link1'); - return assert.equal(fs.readFileSync(p).toString().trim(), 'file1'); - }); - it('reads a file from linked directory', function() { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1'); - assert.equal(fs.readFileSync(p).toString().trim(), 'file1'); - p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); - return assert.equal(fs.readFileSync(p).toString().trim(), 'file1'); - }); - it('throws ENOENT error when can not find file', function() { - var p, throws; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - throws = function() { - return fs.readFileSync(p); - }; - return assert.throws(throws, /ENOENT/); - }); - it('passes ENOENT error to callback when can not find file', function() { - var async, p; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - async = false; - fs.readFile(p, function(e) { - assert(async); - return assert(/ENOENT/.test(e)); - }); - return async = true; - }); - return it('reads a normal file with unpacked files', function() { - var p; - p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt'); - return assert.equal(fs.readFileSync(p).toString().trim(), 'a'); - }); - }); - describe('fs.readFile', function() { - it('reads a normal file', function(done) { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'file1'); - return fs.readFile(p, function(err, content) { - assert.equal(err, null); - assert.equal(String(content).trim(), 'file1'); - return done(); - }); - }); - it('reads from a empty file', function(done) { - var p; - p = path.join(fixtures, 'asar', 'empty.asar', 'file1'); - return fs.readFile(p, function(err, content) { - assert.equal(err, null); - assert.equal(String(content), ''); - return done(); - }); - }); - it('reads a linked file', function(done) { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'link1'); - return fs.readFile(p, function(err, content) { - assert.equal(err, null); - assert.equal(String(content).trim(), 'file1'); - return done(); - }); - }); - it('reads a file from linked directory', function(done) { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); - return fs.readFile(p, function(err, content) { - assert.equal(err, null); - assert.equal(String(content).trim(), 'file1'); - return done(); - }); - }); - return it('throws ENOENT error when can not find file', function(done) { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - return fs.readFile(p, function(err) { - assert.equal(err.code, 'ENOENT'); - return done(); - }); - }); - }); - describe('fs.lstatSync', function() { - it('handles path with trailing slash correctly', function() { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); - fs.lstatSync(p); - return fs.lstatSync(p + '/'); - }); - it('returns information of root', function() { - var p, stats; - p = path.join(fixtures, 'asar', 'a.asar'); - stats = fs.lstatSync(p); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), true); - assert.equal(stats.isSymbolicLink(), false); - return assert.equal(stats.size, 0); - }); - it('returns information of a normal file', function() { - var file, j, len, p, ref2, results, stats; - ref2 = ['file1', 'file2', 'file3', path.join('dir1', 'file1'), path.join('link2', 'file1')]; - results = []; + }) + + it('reads a normal file', function () { + var file1 = path.join(fixtures, 'asar', 'a.asar', 'file1') + assert.equal(fs.readFileSync(file1).toString().trim(), 'file1') + var file2 = path.join(fixtures, 'asar', 'a.asar', 'file2') + assert.equal(fs.readFileSync(file2).toString().trim(), 'file2') + var file3 = path.join(fixtures, 'asar', 'a.asar', 'file3') + assert.equal(fs.readFileSync(file3).toString().trim(), 'file3') + }) + + it('reads from a empty file', function () { + var file = path.join(fixtures, 'asar', 'empty.asar', 'file1') + var buffer = fs.readFileSync(file) + assert.equal(buffer.length, 0) + assert.equal(buffer.toString(), '') + }) + + it('reads a linked file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link1') + assert.equal(fs.readFileSync(p).toString().trim(), 'file1') + }) + + it('reads a file from linked directory', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1') + assert.equal(fs.readFileSync(p).toString().trim(), 'file1') + p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1') + assert.equal(fs.readFileSync(p).toString().trim(), 'file1') + }) + + it('throws ENOENT error when can not find file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + var throws = function () { + fs.readFileSync(p) + } + assert.throws(throws, /ENOENT/) + }) + + it('passes ENOENT error to callback when can not find file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + var async = false + fs.readFile(p, function (e) { + assert(async) + assert(/ENOENT/.test(e)) + }) + async = true + }) + + it('reads a normal file with unpacked files', function () { + var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt') + assert.equal(fs.readFileSync(p).toString().trim(), 'a') + }) + }) + + describe('fs.readFile', function () { + it('reads a normal file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'file1') + fs.readFile(p, function (err, content) { + assert.equal(err, null) + assert.equal(String(content).trim(), 'file1') + done() + }) + }) + + it('reads from a empty file', function (done) { + var p = path.join(fixtures, 'asar', 'empty.asar', 'file1') + fs.readFile(p, function (err, content) { + assert.equal(err, null) + assert.equal(String(content), '') + done() + }) + }) + + it('reads a linked file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link1') + fs.readFile(p, function (err, content) { + assert.equal(err, null) + assert.equal(String(content).trim(), 'file1') + done() + }) + }) + + it('reads a file from linked directory', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1') + fs.readFile(p, function (err, content) { + assert.equal(err, null) + assert.equal(String(content).trim(), 'file1') + done() + }) + }) + + it('throws ENOENT error when can not find file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + fs.readFile(p, function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) + + describe('fs.lstatSync', function () { + it('handles path with trailing slash correctly', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1') + fs.lstatSync(p) + fs.lstatSync(p + '/') + }) + + it('returns information of root', function () { + var p = path.join(fixtures, 'asar', 'a.asar') + var stats = fs.lstatSync(p) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), true) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 0) + }) + + it('returns information of a normal file', function () { + var file, j, len, p, ref2, stats + ref2 = ['file1', 'file2', 'file3', path.join('dir1', 'file1'), path.join('link2', 'file1')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - stats = fs.lstatSync(p); - assert.equal(stats.isFile(), true); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), false); - results.push(assert.equal(stats.size, 6)); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + stats = fs.lstatSync(p) + assert.equal(stats.isFile(), true) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 6) } - return results; - }); - it('returns information of a normal directory', function() { - var file, j, len, p, ref2, results, stats; - ref2 = ['dir1', 'dir2', 'dir3']; - results = []; + }) + + it('returns information of a normal directory', function () { + var file, j, len, p, ref2, stats + ref2 = ['dir1', 'dir2', 'dir3'] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - stats = fs.lstatSync(p); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), true); - assert.equal(stats.isSymbolicLink(), false); - results.push(assert.equal(stats.size, 0)); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + stats = fs.lstatSync(p) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), true) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 0) } - return results; - }); - it('returns information of a linked file', function() { - var file, j, len, p, ref2, results, stats; - ref2 = ['link1', path.join('dir1', 'link1'), path.join('link2', 'link2')]; - results = []; + }) + + it('returns information of a linked file', function () { + var file, j, len, p, ref2, stats + ref2 = ['link1', path.join('dir1', 'link1'), path.join('link2', 'link2')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - stats = fs.lstatSync(p); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), true); - results.push(assert.equal(stats.size, 0)); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + stats = fs.lstatSync(p) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), true) + assert.equal(stats.size, 0) } - return results; - }); - it('returns information of a linked directory', function() { - var file, j, len, p, ref2, results, stats; - ref2 = ['link2', path.join('dir1', 'link2'), path.join('link2', 'link2')]; - results = []; + }) + + it('returns information of a linked directory', function () { + var file, j, len, p, ref2, stats + ref2 = ['link2', path.join('dir1', 'link2'), path.join('link2', 'link2')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - stats = fs.lstatSync(p); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), true); - results.push(assert.equal(stats.size, 0)); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + stats = fs.lstatSync(p) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), true) + assert.equal(stats.size, 0) } - return results; - }); - return it('throws ENOENT error when can not find file', function() { - var file, j, len, p, ref2, results, throws; - ref2 = ['file4', 'file5', path.join('dir1', 'file4')]; - results = []; + }) + + it('throws ENOENT error when can not find file', function () { + var file, j, len, p, ref2, throws + ref2 = ['file4', 'file5', path.join('dir1', 'file4')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - throws = function() { - return fs.lstatSync(p); - }; - results.push(assert.throws(throws, /ENOENT/)); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + throws = function () { + fs.lstatSync(p) + } + assert.throws(throws, /ENOENT/) } - return results; - }); - }); - describe('fs.lstat', function() { - it('handles path with trailing slash correctly', function(done) { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1'); - return fs.lstat(p + '/', done); - }); - it('returns information of root', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), true); - assert.equal(stats.isSymbolicLink(), false); - assert.equal(stats.size, 0); - return done(); - }); - }); - it('returns information of a normal file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), true); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), false); - assert.equal(stats.size, 6); - return done(); - }); - }); - it('returns information of a normal directory', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), true); - assert.equal(stats.isSymbolicLink(), false); - assert.equal(stats.size, 0); - return done(); - }); - }); - it('returns information of a linked file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link1'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), true); - assert.equal(stats.size, 0); - return done(); - }); - }); - it('returns information of a linked directory', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2'); - fs.lstat(p, function(err, stats) { - assert.equal(err, null); - assert.equal(stats.isFile(), false); - assert.equal(stats.isDirectory(), false); - assert.equal(stats.isSymbolicLink(), true); - assert.equal(stats.size, 0); - return done(); - }); - }); - return it('throws ENOENT error when can not find file', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'file4'); - fs.lstat(p, function(err) { - assert.equal(err.code, 'ENOENT'); - return done(); - }); - }); - }); - describe('fs.realpathSync', function() { - it('returns real path root', function() { - var p, parent, r; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = 'a.asar'; - r = fs.realpathSync(path.join(parent, p)); - return assert.equal(r, path.join(parent, p)); - }); - it('returns real path of a normal file', function() { - var p, parent, r; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'file1'); - r = fs.realpathSync(path.join(parent, p)); - return assert.equal(r, path.join(parent, p)); - }); - it('returns real path of a normal directory', function() { - var p, parent, r; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'dir1'); - r = fs.realpathSync(path.join(parent, p)); - return assert.equal(r, path.join(parent, p)); - }); - it('returns real path of a linked file', function() { - var p, parent, r; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'link2', 'link1'); - r = fs.realpathSync(path.join(parent, p)); - return assert.equal(r, path.join(parent, 'a.asar', 'file1')); - }); - it('returns real path of a linked directory', function() { - var p, parent, r; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'link2', 'link2'); - r = fs.realpathSync(path.join(parent, p)); - return assert.equal(r, path.join(parent, 'a.asar', 'dir1')); - }); - return it('throws ENOENT error when can not find file', function() { - var p, parent, throws; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'not-exist'); - throws = function() { - return fs.realpathSync(path.join(parent, p)); - }; - return assert.throws(throws, /ENOENT/); - }); - }); - describe('fs.realpath', function() { - it('returns real path root', function(done) { - var p, parent; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = 'a.asar'; - return fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, p)); - return done(); - }); - }); - it('returns real path of a normal file', function(done) { - var p, parent; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'file1'); - return fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, p)); - return done(); - }); - }); - it('returns real path of a normal directory', function(done) { - var p, parent; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'dir1'); - return fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, p)); - return done(); - }); - }); - it('returns real path of a linked file', function(done) { - var p, parent; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'link2', 'link1'); - return fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, 'a.asar', 'file1')); - return done(); - }); - }); - it('returns real path of a linked directory', function(done) { - var p, parent; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'link2', 'link2'); - return fs.realpath(path.join(parent, p), function(err, r) { - assert.equal(err, null); - assert.equal(r, path.join(parent, 'a.asar', 'dir1')); - return done(); - }); - }); - return it('throws ENOENT error when can not find file', function(done) { - var p, parent; - parent = fs.realpathSync(path.join(fixtures, 'asar')); - p = path.join('a.asar', 'not-exist'); - return fs.realpath(path.join(parent, p), function(err) { - assert.equal(err.code, 'ENOENT'); - return done(); - }); - }); - }); - describe('fs.readdirSync', function() { - it('reads dirs from root', function() { - var dirs, p; - p = path.join(fixtures, 'asar', 'a.asar'); - dirs = fs.readdirSync(p); - return assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); - }); - it('reads dirs from a normal dir', function() { - var dirs, p; - p = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - dirs = fs.readdirSync(p); - return assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); - }); - it('reads dirs from a linked dir', function() { - var dirs, p; - p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2'); - dirs = fs.readdirSync(p); - return assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); - }); - return it('throws ENOENT error when can not find file', function() { - var p, throws; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - throws = function() { - return fs.readdirSync(p); - }; - return assert.throws(throws, /ENOENT/); - }); - }); - describe('fs.readdir', function() { - it('reads dirs from root', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar'); - fs.readdir(p, function(err, dirs) { - assert.equal(err, null); - assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); - return done(); - }); - }); - it('reads dirs from a normal dir', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - fs.readdir(p, function(err, dirs) { - assert.equal(err, null); - assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); - return done(); - }); - }); - it('reads dirs from a linked dir', function(done) { - var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2'); - fs.readdir(p, function(err, dirs) { - assert.equal(err, null); - assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']); - return done(); - }); - }); - return it('throws ENOENT error when can not find file', function(done) { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - return fs.readdir(p, function(err) { - assert.equal(err.code, 'ENOENT'); - return done(); - }); - }); - }); - describe('fs.openSync', function() { - it('opens a normal/linked/under-linked-directory file', function() { - var buffer, fd, file, j, len, p, ref2, results; - ref2 = ['file1', 'link1', path.join('link2', 'file1')]; - results = []; + }) + }) + + describe('fs.lstat', function () { + it('handles path with trailing slash correctly', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1') + fs.lstat(p + '/', done) + }) + + it('returns information of root', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), true) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 0) + done() + }) + }) + + it('returns information of a normal file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'file1') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), true) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 6) + done() + }) + }) + + it('returns information of a normal directory', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'dir1') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), true) + assert.equal(stats.isSymbolicLink(), false) + assert.equal(stats.size, 0) + done() + }) + }) + + it('returns information of a linked file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link1') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), true) + assert.equal(stats.size, 0) + done() + }) + }) + + it('returns information of a linked directory', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2') + fs.lstat(p, function (err, stats) { + assert.equal(err, null) + assert.equal(stats.isFile(), false) + assert.equal(stats.isDirectory(), false) + assert.equal(stats.isSymbolicLink(), true) + assert.equal(stats.size, 0) + done() + }) + }) + + it('throws ENOENT error when can not find file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'file4') + fs.lstat(p, function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) + + describe('fs.realpathSync', function () { + it('returns real path root', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = 'a.asar' + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, p)) + }) + + it('returns real path of a normal file', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'file1') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, p)) + }) + + it('returns real path of a normal directory', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'dir1') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, p)) + }) + + it('returns real path of a linked file', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'link2', 'link1') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, 'a.asar', 'file1')) + }) + + it('returns real path of a linked directory', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'link2', 'link2') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, 'a.asar', 'dir1')) + }) + + it('returns real path of an unpacked file', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('unpack.asar', 'a.txt') + var r = fs.realpathSync(path.join(parent, p)) + assert.equal(r, path.join(parent, p)) + }) + + it('throws ENOENT error when can not find file', function () { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'not-exist') + var throws = function () { + fs.realpathSync(path.join(parent, p)) + } + assert.throws(throws, /ENOENT/) + }) + }) + + describe('fs.realpath', function () { + it('returns real path root', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = 'a.asar' + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, p)) + done() + }) + }) + + it('returns real path of a normal file', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'file1') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, p)) + done() + }) + }) + + it('returns real path of a normal directory', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'dir1') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, p)) + done() + }) + }) + + it('returns real path of a linked file', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'link2', 'link1') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, 'a.asar', 'file1')) + done() + }) + }) + + it('returns real path of a linked directory', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'link2', 'link2') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, 'a.asar', 'dir1')) + done() + }) + }) + + it('returns real path of an unpacked file', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('unpack.asar', 'a.txt') + fs.realpath(path.join(parent, p), function (err, r) { + assert.equal(err, null) + assert.equal(r, path.join(parent, p)) + done() + }) + }) + + it('throws ENOENT error when can not find file', function (done) { + var parent = fs.realpathSync(path.join(fixtures, 'asar')) + var p = path.join('a.asar', 'not-exist') + fs.realpath(path.join(parent, p), function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) + describe('fs.readdirSync', function () { + it('reads dirs from root', function () { + var p = path.join(fixtures, 'asar', 'a.asar') + var dirs = fs.readdirSync(p) + assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']) + }) + + it('reads dirs from a normal dir', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'dir1') + var dirs = fs.readdirSync(p) + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']) + }) + + it('reads dirs from a linked dir', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2') + var dirs = fs.readdirSync(p) + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']) + }) + + it('throws ENOENT error when can not find file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + var throws = function () { + fs.readdirSync(p) + } + assert.throws(throws, /ENOENT/) + }) + }) + + describe('fs.readdir', function () { + it('reads dirs from root', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar') + fs.readdir(p, function (err, dirs) { + assert.equal(err, null) + assert.deepEqual(dirs, ['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']) + done() + }) + }) + + it('reads dirs from a normal dir', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'dir1') + fs.readdir(p, function (err, dirs) { + assert.equal(err, null) + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']) + done() + }) + }) + it('reads dirs from a linked dir', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'link2', 'link2') + fs.readdir(p, function (err, dirs) { + assert.equal(err, null) + assert.deepEqual(dirs, ['file1', 'file2', 'file3', 'link1', 'link2']) + done() + }) + }) + + it('throws ENOENT error when can not find file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + fs.readdir(p, function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) + + describe('fs.openSync', function () { + it('opens a normal/linked/under-linked-directory file', function () { + var buffer, fd, file, j, len, p, ref2 + ref2 = ['file1', 'link1', path.join('link2', 'file1')] for (j = 0, len = ref2.length; j < len; j++) { - file = ref2[j]; - p = path.join(fixtures, 'asar', 'a.asar', file); - fd = fs.openSync(p, 'r'); - buffer = new Buffer(6); - fs.readSync(fd, buffer, 0, 6, 0); - assert.equal(String(buffer).trim(), 'file1'); - results.push(fs.closeSync(fd)); + file = ref2[j] + p = path.join(fixtures, 'asar', 'a.asar', file) + fd = fs.openSync(p, 'r') + buffer = new Buffer(6) + fs.readSync(fd, buffer, 0, 6, 0) + assert.equal(String(buffer).trim(), 'file1') + fs.closeSync(fd) } - return results; - }); - return it('throws ENOENT error when can not find file', function() { - var p, throws; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - throws = function() { - return fs.openSync(p); - }; - return assert.throws(throws, /ENOENT/); - }); - }); - describe('fs.open', function() { - it('opens a normal file', function(done) { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'file1'); - return fs.open(p, 'r', function(err, fd) { - var buffer; - assert.equal(err, null); - buffer = new Buffer(6); - return fs.read(fd, buffer, 0, 6, 0, function(err) { - assert.equal(err, null); - assert.equal(String(buffer).trim(), 'file1'); - return fs.close(fd, done); - }); - }); - }); - return it('throws ENOENT error when can not find file', function(done) { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - return fs.open(p, 'r', function(err) { - assert.equal(err.code, 'ENOENT'); - return done(); - }); - }); - }); - describe('fs.mkdir', function() { - return it('throws error when calling inside asar archive', function(done) { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - return fs.mkdir(p, function(err) { - assert.equal(err.code, 'ENOTDIR'); - return done(); - }); - }); - }); - describe('fs.mkdirSync', function() { - return it('throws error when calling inside asar archive', function() { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - return assert.throws((function() { - return fs.mkdirSync(p); - }), new RegExp('ENOTDIR')); - }); - }); - describe('child_process.fork', function() { - child_process = require('child_process'); - it('opens a normal js file', function(done) { - var child; - child = child_process.fork(path.join(fixtures, 'asar', 'a.asar', 'ping.js')); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - return done(); - }); - return child.send('message'); - }); - return it('supports asar in the forked js', function(done) { - var child, file; - file = path.join(fixtures, 'asar', 'a.asar', 'file1'); - child = child_process.fork(path.join(fixtures, 'module', 'asar.js')); - child.on('message', function(content) { - assert.equal(content, fs.readFileSync(file).toString()); - return done(); - }); - return child.send(file); - }); - }); - describe('child_process.execFile', function() { - var echo, execFile, execFileSync, ref2; + }) + + it('throws ENOENT error when can not find file', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + var throws = function () { + fs.openSync(p) + } + assert.throws(throws, /ENOENT/) + }) + }) + + describe('fs.open', function () { + it('opens a normal file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'file1') + fs.open(p, 'r', function (err, fd) { + assert.equal(err, null) + var buffer = new Buffer(6) + fs.read(fd, buffer, 0, 6, 0, function (err) { + assert.equal(err, null) + assert.equal(String(buffer).trim(), 'file1') + fs.close(fd, done) + }) + }) + }) + + it('throws ENOENT error when can not find file', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + fs.open(p, 'r', function (err) { + assert.equal(err.code, 'ENOENT') + done() + }) + }) + }) + + describe('fs.mkdir', function () { + it('throws error when calling inside asar archive', function (done) { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + fs.mkdir(p, function (err) { + assert.equal(err.code, 'ENOTDIR') + done() + }) + }) + }) + + describe('fs.mkdirSync', function () { + it('throws error when calling inside asar archive', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + assert.throws(function () { + fs.mkdirSync(p) + }, new RegExp('ENOTDIR')) + }) + }) + + describe('child_process.fork', function () { + it('opens a normal js file', function (done) { + var child = child_process.fork(path.join(fixtures, 'asar', 'a.asar', 'ping.js')) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) + + it('supports asar in the forked js', function (done) { + var file = path.join(fixtures, 'asar', 'a.asar', 'file1') + var child = child_process.fork(path.join(fixtures, 'module', 'asar.js')) + child.on('message', function (content) { + assert.equal(content, fs.readFileSync(file).toString()) + done() + }) + child.send(file) + }) + }) + + describe('child_process.execFile', function () { + var echo, execFile, execFileSync, ref2 if (process.platform !== 'darwin') { - return; + return } - ref2 = require('child_process'), execFile = ref2.execFile, execFileSync = ref2.execFileSync; - echo = path.join(fixtures, 'asar', 'echo.asar', 'echo'); - it('executes binaries', function(done) { - execFile(echo, ['test'], function(error, stdout) { - assert.equal(error, null); - assert.equal(stdout, 'test\n'); - return done(); - }); - }); - return xit('execFileSync executes binaries', function() { - var output; - output = execFileSync(echo, ['test']); - return assert.equal(String(output), 'test\n'); - }); - }); - describe('internalModuleReadFile', function() { - var internalModuleReadFile; - internalModuleReadFile = process.binding('fs').internalModuleReadFile; - it('read a normal file', function() { - var file1, file2, file3; - file1 = path.join(fixtures, 'asar', 'a.asar', 'file1'); - assert.equal(internalModuleReadFile(file1).toString().trim(), 'file1'); - file2 = path.join(fixtures, 'asar', 'a.asar', 'file2'); - assert.equal(internalModuleReadFile(file2).toString().trim(), 'file2'); - file3 = path.join(fixtures, 'asar', 'a.asar', 'file3'); - return assert.equal(internalModuleReadFile(file3).toString().trim(), 'file3'); - }); - return it('reads a normal file with unpacked files', function() { - var p; - p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt'); - return assert.equal(internalModuleReadFile(p).toString().trim(), 'a'); - }); - }); - return describe('process.noAsar', function() { - var errorName; - errorName = process.platform === 'win32' ? 'ENOENT' : 'ENOTDIR'; - beforeEach(function() { - return process.noAsar = true; - }); - afterEach(function() { - return process.noAsar = false; - }); - it('disables asar support in sync API', function() { - var dir, file; - file = path.join(fixtures, 'asar', 'a.asar', 'file1'); - dir = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - assert.throws((function() { - return fs.readFileSync(file); - }), new RegExp(errorName)); - assert.throws((function() { - return fs.lstatSync(file); - }), new RegExp(errorName)); - assert.throws((function() { - return fs.realpathSync(file); - }), new RegExp(errorName)); - return assert.throws((function() { - return fs.readdirSync(dir); - }), new RegExp(errorName)); - }); - it('disables asar support in async API', function(done) { - var dir, file; - file = path.join(fixtures, 'asar', 'a.asar', 'file1'); - dir = path.join(fixtures, 'asar', 'a.asar', 'dir1'); - return fs.readFile(file, function(error) { - assert.equal(error.code, errorName); - return fs.lstat(file, function(error) { - assert.equal(error.code, errorName); - return fs.realpath(file, function(error) { - assert.equal(error.code, errorName); - return fs.readdir(dir, function(error) { - assert.equal(error.code, errorName); - return done(); - }); - }); - }); - }); - }); - return it('treats *.asar as normal file', function() { - var asar, content1, content2, originalFs; - originalFs = require('original-fs'); - asar = path.join(fixtures, 'asar', 'a.asar'); - content1 = fs.readFileSync(asar); - content2 = originalFs.readFileSync(asar); - assert.equal(content1.compare(content2), 0); - return assert.throws((function() { - return fs.readdirSync(asar); - }), /ENOTDIR/); - }); - }); - }); - describe('asar protocol', function() { - var url; - url = require('url'); - it('can request a file in package', function(done) { - var p; - p = path.resolve(fixtures, 'asar', 'a.asar', 'file1'); - return $.get("file://" + p, function(data) { - assert.equal(data.trim(), 'file1'); - return done(); - }); - }); - it('can request a file in package with unpacked files', function(done) { - var p; - p = path.resolve(fixtures, 'asar', 'unpack.asar', 'a.txt'); - return $.get("file://" + p, function(data) { - assert.equal(data.trim(), 'a'); - return done(); - }); - }); - it('can request a linked file in package', function(done) { - var p; - p = path.resolve(fixtures, 'asar', 'a.asar', 'link2', 'link1'); - return $.get("file://" + p, function(data) { - assert.equal(data.trim(), 'file1'); - return done(); - }); - }); - it('can request a file in filesystem', function(done) { - var p; - p = path.resolve(fixtures, 'asar', 'file'); - return $.get("file://" + p, function(data) { - assert.equal(data.trim(), 'file'); - return done(); - }); - }); - it('gets 404 when file is not found', function(done) { - var p; - p = path.resolve(fixtures, 'asar', 'a.asar', 'no-exist'); - return $.ajax({ - url: "file://" + p, - error: function(err) { - assert.equal(err.status, 404); - return done(); + ref2 = require('child_process') + execFile = ref2.execFile + execFileSync = ref2.execFileSync + echo = path.join(fixtures, 'asar', 'echo.asar', 'echo') + + it('executes binaries', function (done) { + execFile(echo, ['test'], function (error, stdout) { + assert.equal(error, null) + assert.equal(stdout, 'test\n') + done() + }) + }) + + xit('execFileSync executes binaries', function () { + var output = execFileSync(echo, ['test']) + assert.equal(String(output), 'test\n') + }) + }) + + describe('internalModuleReadFile', function () { + var internalModuleReadFile = process.binding('fs').internalModuleReadFile + + it('read a normal file', function () { + var file1 = path.join(fixtures, 'asar', 'a.asar', 'file1') + assert.equal(internalModuleReadFile(file1).toString().trim(), 'file1') + var file2 = path.join(fixtures, 'asar', 'a.asar', 'file2') + assert.equal(internalModuleReadFile(file2).toString().trim(), 'file2') + var file3 = path.join(fixtures, 'asar', 'a.asar', 'file3') + assert.equal(internalModuleReadFile(file3).toString().trim(), 'file3') + }) + + it('reads a normal file with unpacked files', function () { + var p = path.join(fixtures, 'asar', 'unpack.asar', 'a.txt') + assert.equal(internalModuleReadFile(p).toString().trim(), 'a') + }) + }) + + describe('process.noAsar', function () { + var errorName = process.platform === 'win32' ? 'ENOENT' : 'ENOTDIR' + + beforeEach(function () { + process.noAsar = true + }) + + afterEach(function () { + process.noAsar = false + }) + + it('disables asar support in sync API', function () { + var file = path.join(fixtures, 'asar', 'a.asar', 'file1') + var dir = path.join(fixtures, 'asar', 'a.asar', 'dir1') + assert.throws(function () { + fs.readFileSync(file) + }, new RegExp(errorName)) + assert.throws(function () { + fs.lstatSync(file) + }, new RegExp(errorName)) + assert.throws(function () { + fs.realpathSync(file) + }, new RegExp(errorName)) + assert.throws(function () { + fs.readdirSync(dir) + }, new RegExp(errorName)) + }) + + it('disables asar support in async API', function (done) { + var file = path.join(fixtures, 'asar', 'a.asar', 'file1') + var dir = path.join(fixtures, 'asar', 'a.asar', 'dir1') + fs.readFile(file, function (error) { + assert.equal(error.code, errorName) + fs.lstat(file, function (error) { + assert.equal(error.code, errorName) + fs.realpath(file, function (error) { + assert.equal(error.code, errorName) + fs.readdir(dir, function (error) { + assert.equal(error.code, errorName) + done() + }) + }) + }) + }) + }) + + it('treats *.asar as normal file', function () { + var originalFs = require('original-fs') + var asar = path.join(fixtures, 'asar', 'a.asar') + var content1 = fs.readFileSync(asar) + var content2 = originalFs.readFileSync(asar) + assert.equal(content1.compare(content2), 0) + assert.throws(function () { + fs.readdirSync(asar) + }, /ENOTDIR/) + }) + }) + }) + + describe('asar protocol', function () { + var url = require('url') + + it('can request a file in package', function (done) { + var p = path.resolve(fixtures, 'asar', 'a.asar', 'file1') + $.get('file://' + p, function (data) { + assert.equal(data.trim(), 'file1') + done() + }) + }) + + it('can request a file in package with unpacked files', function (done) { + var p = path.resolve(fixtures, 'asar', 'unpack.asar', 'a.txt') + $.get('file://' + p, function (data) { + assert.equal(data.trim(), 'a') + done() + }) + }) + + it('can request a linked file in package', function (done) { + var p = path.resolve(fixtures, 'asar', 'a.asar', 'link2', 'link1') + $.get('file://' + p, function (data) { + assert.equal(data.trim(), 'file1') + done() + }) + }) + + it('can request a file in filesystem', function (done) { + var p = path.resolve(fixtures, 'asar', 'file') + $.get('file://' + p, function (data) { + assert.equal(data.trim(), 'file') + done() + }) + }) + + it('gets 404 when file is not found', function (done) { + var p = path.resolve(fixtures, 'asar', 'a.asar', 'no-exist') + $.ajax({ + url: 'file://' + p, + error: function (err) { + assert.equal(err.status, 404) + done() } - }); - }); - it('sets __dirname correctly', function(done) { - var p, u, w; - after(function() { - w.destroy(); - return ipcMain.removeAllListeners('dirname'); - }); - w = new BrowserWindow({ + }) + }) + + it('sets __dirname correctly', function (done) { + after(function () { + w.destroy() + ipcMain.removeAllListeners('dirname') + }) + + var w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - p = path.resolve(fixtures, 'asar', 'web.asar', 'index.html'); - u = url.format({ + }) + var p = path.resolve(fixtures, 'asar', 'web.asar', 'index.html') + var u = url.format({ protocol: 'file', slashed: true, pathname: p - }); - ipcMain.once('dirname', function(event, dirname) { - assert.equal(dirname, path.dirname(p)); - return done(); - }); - return w.loadURL(u); - }); - return it('loads script tag in html', function(done) { - var p, u, w; - after(function() { - w.destroy(); - return ipcMain.removeAllListeners('ping'); - }); - w = new BrowserWindow({ + }) + ipcMain.once('dirname', function (event, dirname) { + assert.equal(dirname, path.dirname(p)) + done() + }) + w.loadURL(u) + }) + + it('loads script tag in html', function (done) { + after(function () { + w.destroy() + ipcMain.removeAllListeners('ping') + }) + + var w = new BrowserWindow({ show: false, width: 400, height: 400 - }); - p = path.resolve(fixtures, 'asar', 'script.asar', 'index.html'); - u = url.format({ + }) + var p = path.resolve(fixtures, 'asar', 'script.asar', 'index.html') + var u = url.format({ protocol: 'file', slashed: true, pathname: p - }); - w.loadURL(u); - return ipcMain.once('ping', function(event, message) { - assert.equal(message, 'pong'); - return done(); - }); - }); - }); - describe('original-fs module', function() { - var originalFs; - originalFs = require('original-fs'); - it('treats .asar as file', function() { - var file, stats; - file = path.join(fixtures, 'asar', 'a.asar'); - stats = originalFs.statSync(file); - return assert(stats.isFile()); - }); - return it('is available in forked scripts', function(done) { - var child; - child = child_process.fork(path.join(fixtures, 'module', 'original-fs.js')); - child.on('message', function(msg) { - assert.equal(msg, 'object'); - return done(); - }); - return child.send('message'); - }); - }); - describe('graceful-fs module', function() { - var gfs; - gfs = require('graceful-fs'); - it('recognize asar archvies', function() { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'link1'); - return assert.equal(gfs.readFileSync(p).toString().trim(), 'file1'); - }); - return it('does not touch global fs object', function() { - return assert.notEqual(fs.readdir, gfs.readdir); - }); - }); - describe('mkdirp module', function() { - var mkdirp; - mkdirp = require('mkdirp'); - return it('throws error when calling inside asar archive', function() { - var p; - p = path.join(fixtures, 'asar', 'a.asar', 'not-exist'); - return assert.throws((function() { - return mkdirp.sync(p); - }), new RegExp('ENOTDIR')); - }); - }); - return describe('native-image', function() { - it('reads image from asar archive', function() { - var logo, p; - p = path.join(fixtures, 'asar', 'logo.asar', 'logo.png'); - logo = nativeImage.createFromPath(p); - return assert.deepEqual(logo.getSize(), { + }) + w.loadURL(u) + ipcMain.once('ping', function (event, message) { + assert.equal(message, 'pong') + done() + }) + }) + }) + + describe('original-fs module', function () { + var originalFs = require('original-fs') + + it('treats .asar as file', function () { + var file = path.join(fixtures, 'asar', 'a.asar') + var stats = originalFs.statSync(file) + assert(stats.isFile()) + }) + + it('is available in forked scripts', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'original-fs.js')) + child.on('message', function (msg) { + assert.equal(msg, 'object') + done() + }) + child.send('message') + }) + }) + + describe('graceful-fs module', function () { + var gfs = require('graceful-fs') + + it('recognize asar archvies', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'link1') + assert.equal(gfs.readFileSync(p).toString().trim(), 'file1') + }) + it('does not touch global fs object', function () { + assert.notEqual(fs.readdir, gfs.readdir) + }) + }) + + describe('mkdirp module', function () { + var mkdirp = require('mkdirp') + + it('throws error when calling inside asar archive', function () { + var p = path.join(fixtures, 'asar', 'a.asar', 'not-exist') + assert.throws(function () { + mkdirp.sync(p) + }, new RegExp('ENOTDIR')) + }) + }) + + describe('native-image', function () { + it('reads image from asar archive', function () { + var p = path.join(fixtures, 'asar', 'logo.asar', 'logo.png') + var logo = nativeImage.createFromPath(p) + assert.deepEqual(logo.getSize(), { width: 55, height: 55 - }); - }); - return it('reads image from asar archive with unpacked files', function() { - var logo, p; - p = path.join(fixtures, 'asar', 'unpack.asar', 'atom.png'); - logo = nativeImage.createFromPath(p); - return assert.deepEqual(logo.getSize(), { + }) + }) + + it('reads image from asar archive with unpacked files', function () { + var p = path.join(fixtures, 'asar', 'unpack.asar', 'atom.png') + var logo = nativeImage.createFromPath(p) + assert.deepEqual(logo.getSize(), { width: 1024, height: 1024 - }); - }); - }); -}); + }) + }) + }) +}) diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index 56fa21c89a7..1d37d0fe051 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -1,360 +1,459 @@ -const assert = require('assert'); -const http = require('http'); -const path = require('path'); -const ws = require('ws'); -const remote = require('electron').remote; -const BrowserWindow = remote.require('electron').BrowserWindow; -const session = remote.require('electron').session; +const assert = require('assert') +const http = require('http') +const path = require('path') +const ws = require('ws') +const remote = require('electron').remote -describe('chromium feature', function() { - var fixtures, listener; - fixtures = path.resolve(__dirname, 'fixtures'); - listener = null; - afterEach(function() { +const BrowserWindow = remote.require('electron').BrowserWindow +const session = remote.require('electron').session + +const isCI = remote.getGlobal('isCi') + +describe('chromium feature', function () { + var fixtures = path.resolve(__dirname, 'fixtures') + var listener = null + + afterEach(function () { if (listener != null) { - window.removeEventListener('message', listener); + window.removeEventListener('message', listener) } - return listener = null; - }); - xdescribe('heap snapshot', function() { - return it('does not crash', function() { - return process.atomBinding('v8_util').takeHeapSnapshot(); - }); - }); - describe('sending request of http protocol urls', function() { - return it('does not crash', function(done) { - var server; - this.timeout(5000); - server = http.createServer(function(req, res) { - res.end(); - server.close(); - return done(); - }); - return server.listen(0, '127.0.0.1', function() { - var port; - port = server.address().port; - return $.get("http://127.0.0.1:" + port); - }); - }); - }); - describe('document.hidden', function() { - var url, w; - url = "file://" + fixtures + "/pages/document-hidden.html"; - w = null; - afterEach(function() { - return w != null ? w.destroy() : void 0; - }); - it('is set correctly when window is not shown', function(done) { + listener = null + }) + + xdescribe('heap snapshot', function () { + it('does not crash', function () { + process.atomBinding('v8_util').takeHeapSnapshot() + }) + }) + + describe('sending request of http protocol urls', function () { + it('does not crash', function (done) { + this.timeout(5000) + + var server = http.createServer(function (req, res) { + res.end() + server.close() + done() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + $.get('http://127.0.0.1:' + port) + }) + }) + }) + + describe('document.hidden', function () { + var url = 'file://' + fixtures + '/pages/document-hidden.html' + var w = null + + afterEach(function () { + w != null ? w.destroy() : void 0 + }) + + it('is set correctly when window is not shown', function (done) { w = new BrowserWindow({ show: false - }); - w.webContents.on('ipc-message', function(event, args) { - assert.deepEqual(args, ['hidden', true]); - return done(); - }); - return w.loadURL(url); - }); - return it('is set correctly when window is inactive', function(done) { + }) + w.webContents.on('ipc-message', function (event, args) { + assert.deepEqual(args, ['hidden', true]) + done() + }) + w.loadURL(url) + }) + + it('is set correctly when window is inactive', function (done) { w = new BrowserWindow({ show: false - }); - w.webContents.on('ipc-message', function(event, args) { - assert.deepEqual(args, ['hidden', false]); - return done(); - }); - w.showInactive(); - return w.loadURL(url); - }); - }); - xdescribe('navigator.webkitGetUserMedia', function() { - return it('calls its callbacks', function(done) { - this.timeout(5000); - return navigator.webkitGetUserMedia({ + }) + w.webContents.on('ipc-message', function (event, args) { + assert.deepEqual(args, ['hidden', false]) + done() + }) + w.showInactive() + w.loadURL(url) + }) + }) + + xdescribe('navigator.webkitGetUserMedia', function () { + it('calls its callbacks', function (done) { + this.timeout(5000) + + navigator.webkitGetUserMedia({ audio: true, video: false - }, function() { - return done(); - }, function() { - return done(); - }); - }); - }); - describe('navigator.language', function() { - return it('should not be empty', function() { - return assert.notEqual(navigator.language, ''); - }); - }); - describe('navigator.serviceWorker', function() { - var url, w; - url = "file://" + fixtures + "/pages/service-worker/index.html"; - w = null; - afterEach(function() { - return w != null ? w.destroy() : void 0; - }); - return it('should register for file scheme', function(done) { + }, function () { + done() + }, function () { + done() + }) + }) + }) + + describe('navigator.mediaDevices', function () { + if (process.env.TRAVIS === 'true') { + return + } + if (isCI && process.platform === 'linux') { + return + } + + it('can return labels of enumerated devices', function (done) { + navigator.mediaDevices.enumerateDevices().then((devices) => { + const labels = devices.map((device) => device.label) + const labelFound = labels.some((label) => !!label) + if (labelFound) { + done() + } else { + done('No device labels found: ' + JSON.stringify(labels)) + } + }).catch(done) + }) + }) + + describe('navigator.language', function () { + it('should not be empty', function () { + assert.notEqual(navigator.language, '') + }) + }) + + describe('navigator.serviceWorker', function () { + var url = 'file://' + fixtures + '/pages/service-worker/index.html' + var w = null + + afterEach(function () { + w != null ? w.destroy() : void 0 + }) + + it('should register for file scheme', function (done) { w = new BrowserWindow({ show: false - }); - w.webContents.on('ipc-message', function(event, args) { + }) + w.webContents.on('ipc-message', function (event, args) { if (args[0] === 'reload') { - return w.webContents.reload(); + w.webContents.reload() } else if (args[0] === 'error') { - return done('unexpected error : ' + args[1]); + done('unexpected error : ' + args[1]) } else if (args[0] === 'response') { - assert.equal(args[1], 'Hello from serviceWorker!'); - return session.defaultSession.clearStorageData({ + assert.equal(args[1], 'Hello from serviceWorker!') + session.defaultSession.clearStorageData({ storages: ['serviceworkers'] - }, function() { - return done(); - }); + }, function () { + done() + }) } - }); - return w.loadURL(url); - }); - }); - describe('window.open', function() { - this.timeout(20000); - it('returns a BrowserWindowProxy object', function() { - var b; - b = window.open('about:blank', '', 'show=no'); - assert.equal(b.closed, false); - assert.equal(b.constructor.name, 'BrowserWindowProxy'); - return b.close(); - }); - it('accepts "node-integration" as feature', function(done) { - var b; - listener = function(event) { - assert.equal(event.data, 'undefined'); - b.close(); - return done(); - }; - window.addEventListener('message', listener); - return b = window.open("file://" + fixtures + "/pages/window-opener-node.html", '', 'nodeIntegration=no,show=no'); - }); - it('inherit options of parent window', function(done) { - var b; - listener = function(event) { - var height, ref1, width; - ref1 = remote.getCurrentWindow().getSize(), width = ref1[0], height = ref1[1]; - assert.equal(event.data, "size: " + width + " " + height); - b.close(); - return done(); - }; - window.addEventListener('message', listener); - return b = window.open("file://" + fixtures + "/pages/window-open-size.html", '', 'show=no'); - }); - return it('does not override child options', function(done) { - var b, size; + }) + w.loadURL(url) + }) + }) + + describe('window.open', function () { + this.timeout(20000) + + it('returns a BrowserWindowProxy object', function () { + var b = window.open('about:blank', '', 'show=no') + assert.equal(b.closed, false) + assert.equal(b.constructor.name, 'BrowserWindowProxy') + b.close() + }) + + it('accepts "nodeIntegration" as feature', function (done) { + var b + listener = function (event) { + assert.equal(event.data.isProcessGlobalUndefined, true) + b.close() + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-opener-node.html', '', 'nodeIntegration=no,show=no') + }) + + it('inherit options of parent window', function (done) { + var b + listener = function (event) { + var ref1 = remote.getCurrentWindow().getSize() + var width = ref1[0] + var height = ref1[1] + assert.equal(event.data, 'size: ' + width + ' ' + height) + b.close() + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no') + }) + + it('disables node integration when it is disabled on the parent window', function (done) { + var b + listener = function (event) { + assert.equal(event.data.isProcessGlobalUndefined, true) + b.close() + done() + } + window.addEventListener('message', listener) + + var windowUrl = require('url').format({ + pathname: `${fixtures}/pages/window-opener-no-node-integration.html`, + protocol: 'file', + query: { + p: `${fixtures}/pages/window-opener-node.html` + }, + slashes: true + }) + b = window.open(windowUrl, '', 'nodeIntegration=no,show=no') + }) + + it('does not override child options', function (done) { + var b, size size = { width: 350, height: 450 - }; - listener = function(event) { - assert.equal(event.data, "size: " + size.width + " " + size.height); - b.close(); - return done(); - }; - window.addEventListener('message', listener); - return b = window.open("file://" + fixtures + "/pages/window-open-size.html", '', "show=no,width=" + size.width + ",height=" + size.height); - }); - }); - describe('window.opener', function() { - var url, w; - this.timeout(10000); - url = "file://" + fixtures + "/pages/window-opener.html"; - w = null; - afterEach(function() { - return w != null ? w.destroy() : void 0; - }); - it('is null for main window', function(done) { + } + listener = function (event) { + assert.equal(event.data, 'size: ' + size.width + ' ' + size.height) + b.close() + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no,width=' + size.width + ',height=' + size.height) + }) + + it('defines a window.location getter', function (done) { + var b, targetURL + if (process.platform == 'win32') + targetURL = 'file:///' + fixtures.replace(/\\/g, '/') + '/pages/base-page.html' + else + targetURL = 'file://' + fixtures + '/pages/base-page.html' + b = window.open(targetURL) + BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { + assert.equal(b.location, targetURL) + b.close() + done() + }) + }) + + it('defines a window.location setter', function (done) { + // Load a page that definitely won't redirect + var b + b = window.open('about:blank') + BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { + // When it loads, redirect + b.location = 'file://' + fixtures + '/pages/base-page.html' + BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { + // After our second redirect, cleanup and callback + b.close() + done() + }) + }) + }) + }) + + describe('window.opener', function () { + this.timeout(10000) + + var url = 'file://' + fixtures + '/pages/window-opener.html' + var w = null + + afterEach(function () { + w != null ? w.destroy() : void 0 + }) + + it('is null for main window', function (done) { w = new BrowserWindow({ show: false - }); - w.webContents.on('ipc-message', function(event, args) { - assert.deepEqual(args, ['opener', null]); - return done(); - }); - return w.loadURL(url); - }); - return it('is not null for window opened by window.open', function(done) { - var b; - listener = function(event) { - assert.equal(event.data, 'object'); - b.close(); - return done(); - }; - window.addEventListener('message', listener); - return b = window.open(url, '', 'show=no'); - }); - }); - describe('window.postMessage', function() { - return it('sets the source and origin correctly', function(done) { - var b, sourceId; - sourceId = remote.getCurrentWindow().id; - listener = function(event) { - var message; - window.removeEventListener('message', listener); - b.close(); - message = JSON.parse(event.data); - assert.equal(message.data, 'testing'); - assert.equal(message.origin, 'file://'); - assert.equal(message.sourceEqualsOpener, true); - assert.equal(message.sourceId, sourceId); - assert.equal(event.origin, 'file://'); - return done(); - }; - window.addEventListener('message', listener); - b = window.open("file://" + fixtures + "/pages/window-open-postMessage.html", '', 'show=no'); - return BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function() { - return b.postMessage('testing', '*'); - }); - }); - }); - describe('window.opener.postMessage', function() { - return it('sets source and origin correctly', function(done) { - var b; - listener = function(event) { - window.removeEventListener('message', listener); - b.close(); - assert.equal(event.source, b); - assert.equal(event.origin, 'file://'); - return done(); - }; - window.addEventListener('message', listener); - return b = window.open("file://" + fixtures + "/pages/window-opener-postMessage.html", '', 'show=no'); - }); - }); - describe('creating a Uint8Array under browser side', function() { - return it('does not crash', function() { - var RUint8Array; - RUint8Array = remote.getGlobal('Uint8Array'); - return new RUint8Array; - }); - }); - describe('webgl', function() { - return it('can be get as context in canvas', function() { - var webgl; - if (process.platform === 'linux') { - return; + }) + w.webContents.on('ipc-message', function (event, args) { + assert.deepEqual(args, ['opener', null]) + done() + }) + w.loadURL(url) + }) + + it('is not null for window opened by window.open', function (done) { + var b + listener = function (event) { + assert.equal(event.data, 'object') + b.close() + done() } - webgl = document.createElement('canvas').getContext('webgl'); - return assert.notEqual(webgl, null); - }); - }); - describe('web workers', function() { - it('Worker can work', function(done) { - var message, worker; - worker = new Worker('../fixtures/workers/worker.js'); - message = 'ping'; - worker.onmessage = function(event) { - assert.equal(event.data, message); - worker.terminate(); - return done(); - }; - return worker.postMessage(message); - }); - return it('SharedWorker can work', function(done) { - var message, worker; - worker = new SharedWorker('../fixtures/workers/shared_worker.js'); - message = 'ping'; - worker.port.onmessage = function(event) { - assert.equal(event.data, message); - return done(); - }; - return worker.port.postMessage(message); - }); - }); - describe('iframe', function() { - var iframe; - iframe = null; - beforeEach(function() { - return iframe = document.createElement('iframe'); - }); - afterEach(function() { - return document.body.removeChild(iframe); - }); - return it('does not have node integration', function(done) { - iframe.src = "file://" + fixtures + "/pages/set-global.html"; - document.body.appendChild(iframe); - return iframe.onload = function() { - assert.equal(iframe.contentWindow.test, 'undefined undefined undefined'); - return done(); - }; - }); - }); - describe('storage', function() { - return it('requesting persitent quota works', function(done) { - return navigator.webkitPersistentStorage.requestQuota(1024 * 1024, function(grantedBytes) { - assert.equal(grantedBytes, 1048576); - return done(); - }); - }); - }); - describe('websockets', function() { - var WebSocketServer, server, wss; - wss = null; - server = null; - WebSocketServer = ws.Server; - afterEach(function() { - wss.close(); - return server.close(); - }); - return it('has user agent', function(done) { - server = http.createServer(); - return server.listen(0, '127.0.0.1', function() { - var port = server.address().port; + window.addEventListener('message', listener) + b = window.open(url, '', 'show=no') + }) + }) + + describe('window.postMessage', function () { + it('sets the source and origin correctly', function (done) { + var b, sourceId + sourceId = remote.getCurrentWindow().id + listener = function (event) { + window.removeEventListener('message', listener) + b.close() + var message = JSON.parse(event.data) + assert.equal(message.data, 'testing') + assert.equal(message.origin, 'file://') + assert.equal(message.sourceEqualsOpener, true) + assert.equal(message.sourceId, sourceId) + assert.equal(event.origin, 'file://') + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no') + BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () { + b.postMessage('testing', '*') + }) + }) + }) + + describe('window.opener.postMessage', function () { + it('sets source and origin correctly', function (done) { + var b + listener = function (event) { + window.removeEventListener('message', listener) + b.close() + assert.equal(event.source, b) + assert.equal(event.origin, 'file://') + done() + } + window.addEventListener('message', listener) + b = window.open('file://' + fixtures + '/pages/window-opener-postMessage.html', '', 'show=no') + }) + }) + + describe('creating a Uint8Array under browser side', function () { + it('does not crash', function () { + var RUint8Array = remote.getGlobal('Uint8Array') + var arr = new RUint8Array() + assert(arr) + }) + }) + + describe('webgl', function () { + it('can be get as context in canvas', function () { + if (process.platform === 'linux') return + + var webgl = document.createElement('canvas').getContext('webgl') + assert.notEqual(webgl, null) + }) + }) + + describe('web workers', function () { + it('Worker can work', function (done) { + var worker = new Worker('../fixtures/workers/worker.js') + var message = 'ping' + worker.onmessage = function (event) { + assert.equal(event.data, message) + worker.terminate() + done() + } + worker.postMessage(message) + }) + + it('SharedWorker can work', function (done) { + var worker = new SharedWorker('../fixtures/workers/shared_worker.js') + var message = 'ping' + worker.port.onmessage = function (event) { + assert.equal(event.data, message) + done() + } + worker.port.postMessage(message) + }) + }) + + describe('iframe', function () { + var iframe = null + + beforeEach(function () { + iframe = document.createElement('iframe') + }) + + afterEach(function () { + document.body.removeChild(iframe) + }) + + it('does not have node integration', function (done) { + iframe.src = 'file://' + fixtures + '/pages/set-global.html' + document.body.appendChild(iframe) + iframe.onload = function () { + assert.equal(iframe.contentWindow.test, 'undefined undefined undefined') + done() + } + }) + }) + + describe('storage', function () { + it('requesting persitent quota works', function (done) { + navigator.webkitPersistentStorage.requestQuota(1024 * 1024, function (grantedBytes) { + assert.equal(grantedBytes, 1048576) + done() + }) + }) + }) + + describe('websockets', function () { + var wss = null + var server = null + var WebSocketServer = ws.Server + + afterEach(function () { + wss.close() + server.close() + }) + + it('has user agent', function (done) { + server = http.createServer() + server.listen(0, '127.0.0.1', function () { + var port = server.address().port wss = new WebSocketServer({ server: server - }); - wss.on('error', done); - wss.on('connection', function(ws) { + }) + wss.on('error', done) + wss.on('connection', function (ws) { if (ws.upgradeReq.headers['user-agent']) { - return done(); + done() } else { - return done('user agent is empty'); + done('user agent is empty') } - }); - new WebSocket("ws://127.0.0.1:" + port); - }); - }); - }); - return describe('Promise', function() { - it('resolves correctly in Node.js calls', function(done) { + }) + var socket = new WebSocket(`ws://127.0.0.1:${port}`) + assert(socket) + }) + }) + }) + + describe('Promise', function () { + it('resolves correctly in Node.js calls', function (done) { document.registerElement('x-element', { prototype: Object.create(HTMLElement.prototype, { createdCallback: { - value: function() {} + value: function () {} } }) - }); - return setImmediate(function() { - var called; - called = false; - Promise.resolve().then(function() { - return done(called ? void 0 : new Error('wrong sequence')); - }); - document.createElement('x-element'); - return called = true; - }); - }); - return it('resolves correctly in Electron calls', function(done) { + }) + setImmediate(function () { + var called = false + Promise.resolve().then(function () { + done(called ? void 0 : new Error('wrong sequence')) + }) + document.createElement('x-element') + called = true + }) + }) + + it('resolves correctly in Electron calls', function (done) { document.registerElement('y-element', { prototype: Object.create(HTMLElement.prototype, { createdCallback: { - value: function() {} + value: function () {} } }) - }); - return remote.getGlobal('setImmediate')(function() { - var called; - called = false; - Promise.resolve().then(function() { - return done(called ? void 0 : new Error('wrong sequence')); - }); - document.createElement('y-element'); - return called = true; - }); - }); - }); -}); + }) + remote.getGlobal('setImmediate')(function () { + var called = false + Promise.resolve().then(function () { + done(called ? void 0 : new Error('wrong sequence')) + }) + document.createElement('y-element') + called = true + }) + }) + }) +}) diff --git a/spec/fixtures/devtools-extensions/foo/manifest.json b/spec/fixtures/devtools-extensions/foo/manifest.json new file mode 100644 index 00000000000..bde99de9287 --- /dev/null +++ b/spec/fixtures/devtools-extensions/foo/manifest.json @@ -0,0 +1,3 @@ +{ + "name": "foo" +} diff --git a/spec/fixtures/module/asar.js b/spec/fixtures/module/asar.js index 1fb8750878a..e01b64a2eca 100644 --- a/spec/fixtures/module/asar.js +++ b/spec/fixtures/module/asar.js @@ -1,4 +1,4 @@ -var fs = require('fs'); -process.on('message', function(file) { - process.send(fs.readFileSync(file).toString()); -}); +var fs = require('fs') +process.on('message', function (file) { + process.send(fs.readFileSync(file).toString()) +}) diff --git a/spec/fixtures/module/call.js b/spec/fixtures/module/call.js index 4cf232e220d..d09d677199b 100644 --- a/spec/fixtures/module/call.js +++ b/spec/fixtures/module/call.js @@ -1,7 +1,7 @@ -exports.call = function(func) { - return func(); +exports.call = function (func) { + return func() } -exports.constructor = function() { - this.test = 'test'; +exports.constructor = function () { + this.test = 'test' } diff --git a/spec/fixtures/module/class.js b/spec/fixtures/module/class.js new file mode 100644 index 00000000000..9b971e52335 --- /dev/null +++ b/spec/fixtures/module/class.js @@ -0,0 +1,29 @@ +'use strict' + +let value = 'old' + +class BaseClass { + method () { + return 'method' + } + + get readonly () { + return 'readonly' + } + + get value () { + return value + } + + set value (val) { + value = val + } +} + +class DerivedClass extends BaseClass { +} + +module.exports = { + base: new BaseClass(), + derived: new DerivedClass() +} diff --git a/spec/fixtures/module/create_socket.js b/spec/fixtures/module/create_socket.js index 2a8b475c519..2528b993d65 100644 --- a/spec/fixtures/module/create_socket.js +++ b/spec/fixtures/module/create_socket.js @@ -1,4 +1,4 @@ -var net = require('net'); -var server = net.createServer(function() {}); -server.listen(process.argv[2]); -process.exit(0); +var net = require('net') +var server = net.createServer(function () {}) +server.listen(process.argv[2]) +process.exit(0) diff --git a/spec/fixtures/module/fork_ping.js b/spec/fixtures/module/fork_ping.js index a43f7d8dce7..aa515333400 100644 --- a/spec/fixtures/module/fork_ping.js +++ b/spec/fixtures/module/fork_ping.js @@ -1,14 +1,16 @@ -process.on('uncaughtException', function(error) { - process.send(error.stack); -}); +const path = require('path') -var child = require('child_process').fork(__dirname + '/ping.js'); -process.on('message', function(msg) { - child.send(msg); -}); +process.on('uncaughtException', function (error) { + process.send(error.stack) +}) + +var child = require('child_process').fork(path.join(__dirname, '/ping.js')) +process.on('message', function (msg) { + child.send(msg) +}) child.on('message', function (msg) { - process.send(msg); -}); -child.on('exit', function(code) { - process.exit(code); -}); + process.send(msg) +}) +child.on('exit', function (code) { + process.exit(code) +}) diff --git a/spec/fixtures/module/function.js b/spec/fixtures/module/function.js new file mode 100644 index 00000000000..8a2bb6c421e --- /dev/null +++ b/spec/fixtures/module/function.js @@ -0,0 +1 @@ +exports.aFunction = function () { return 1127 } diff --git a/spec/fixtures/module/locale-compare.js b/spec/fixtures/module/locale-compare.js index f99e7c3be11..40275400443 100644 --- a/spec/fixtures/module/locale-compare.js +++ b/spec/fixtures/module/locale-compare.js @@ -1,7 +1,7 @@ -process.on('message', function (msg) { +process.on('message', function () { process.send([ 'a'.localeCompare('a'), 'ä'.localeCompare('z', 'de'), - 'ä'.localeCompare('a', 'sv', { sensitivity: 'base' }), - ]); -}); + 'ä'.localeCompare('a', 'sv', { sensitivity: 'base' }) + ]) +}) diff --git a/spec/fixtures/module/original-fs.js b/spec/fixtures/module/original-fs.js index 90b6abcf9b7..341dcb2e0de 100644 --- a/spec/fixtures/module/original-fs.js +++ b/spec/fixtures/module/original-fs.js @@ -1,3 +1,3 @@ -process.on('message', function (msg) { - process.send(typeof require('original-fs')); -}); +process.on('message', function () { + process.send(typeof require('original-fs')) +}) diff --git a/spec/fixtures/module/ping.js b/spec/fixtures/module/ping.js index fdb9d1ec4ce..90b3d1fb20a 100644 --- a/spec/fixtures/module/ping.js +++ b/spec/fixtures/module/ping.js @@ -1,4 +1,4 @@ -process.on('message', function(msg) { - process.send(msg); - process.exit(0); -}); +process.on('message', function (msg) { + process.send(msg) + process.exit(0) +}) diff --git a/spec/fixtures/module/preload-ipc.js b/spec/fixtures/module/preload-ipc.js index ed95055c124..3f7e5ea35ce 100644 --- a/spec/fixtures/module/preload-ipc.js +++ b/spec/fixtures/module/preload-ipc.js @@ -1,4 +1,4 @@ -var ipcRenderer = require('electron').ipcRenderer; -ipcRenderer.on('ping', function(event, message) { - ipcRenderer.sendToHost('pong', message); -}); +var ipcRenderer = require('electron').ipcRenderer +ipcRenderer.on('ping', function (event, message) { + ipcRenderer.sendToHost('pong', message) +}) diff --git a/spec/fixtures/module/preload-node-off.js b/spec/fixtures/module/preload-node-off.js index 9020f4513a1..54fe343a9ca 100644 --- a/spec/fixtures/module/preload-node-off.js +++ b/spec/fixtures/module/preload-node-off.js @@ -1,7 +1,7 @@ -setImmediate(function() { +setImmediate(function () { try { - console.log([typeof process, typeof setImmediate, typeof global].join(' ')); + console.log([typeof process, typeof setImmediate, typeof global].join(' ')) } catch (e) { - console.log(e.message); + console.log(e.message) } -}); +}) diff --git a/spec/fixtures/module/preload.js b/spec/fixtures/module/preload.js index 205a077ddb1..39c8b11fbe3 100644 --- a/spec/fixtures/module/preload.js +++ b/spec/fixtures/module/preload.js @@ -1 +1 @@ -console.log([typeof require, typeof module, typeof process].join(' ')); +console.log([typeof require, typeof module, typeof process].join(' ')) diff --git a/spec/fixtures/module/print_name.js b/spec/fixtures/module/print_name.js index 01d13f4ba8b..db4f71d407b 100644 --- a/spec/fixtures/module/print_name.js +++ b/spec/fixtures/module/print_name.js @@ -1,7 +1,7 @@ -exports.print = function(obj) { - return obj.constructor.name; +exports.print = function (obj) { + return obj.constructor.name } -exports.echo = function(obj) { - return obj; +exports.echo = function (obj) { + return obj } diff --git a/spec/fixtures/module/process_args.js b/spec/fixtures/module/process_args.js index bba351154f6..56e3906c553 100644 --- a/spec/fixtures/module/process_args.js +++ b/spec/fixtures/module/process_args.js @@ -1,4 +1,4 @@ -process.on('message', function() { - process.send(process.argv); - process.exit(0); -}); +process.on('message', function () { + process.send(process.argv) + process.exit(0) +}) diff --git a/spec/fixtures/module/promise.js b/spec/fixtures/module/promise.js index 2e52ed37440..d34058cc803 100644 --- a/spec/fixtures/module/promise.js +++ b/spec/fixtures/module/promise.js @@ -1,5 +1,5 @@ exports.twicePromise = function (promise) { return promise.then(function (value) { - return value * 2; - }); + return value * 2 + }) } diff --git a/spec/fixtures/module/runas.js b/spec/fixtures/module/runas.js index c4845e026f2..6422fce052a 100644 --- a/spec/fixtures/module/runas.js +++ b/spec/fixtures/module/runas.js @@ -1,6 +1,6 @@ -process.on('uncaughtException', function(err) { - process.send(err.message); -}); +process.on('uncaughtException', function (err) { + process.send(err.message) +}) -require('runas'); -process.send('ok'); +require('runas') +process.send('ok') diff --git a/spec/fixtures/module/send-later.js b/spec/fixtures/module/send-later.js index 13f02452db1..8eb16f72f8c 100644 --- a/spec/fixtures/module/send-later.js +++ b/spec/fixtures/module/send-later.js @@ -1,4 +1,4 @@ -var ipcRenderer = require('electron').ipcRenderer; -window.onload = function() { - ipcRenderer.send('answer', typeof window.process); +var ipcRenderer = require('electron').ipcRenderer +window.onload = function () { + ipcRenderer.send('answer', typeof window.process) } diff --git a/spec/fixtures/module/set-global.js b/spec/fixtures/module/set-global.js index f39919ff9d8..ba4f30d155b 100644 --- a/spec/fixtures/module/set-global.js +++ b/spec/fixtures/module/set-global.js @@ -1 +1 @@ -window.test = 'preload'; +window.test = 'preload' diff --git a/spec/fixtures/module/set-immediate.js b/spec/fixtures/module/set-immediate.js index e7d44a75d1f..69563fd0a83 100644 --- a/spec/fixtures/module/set-immediate.js +++ b/spec/fixtures/module/set-immediate.js @@ -1,11 +1,11 @@ -process.on('uncaughtException', function(error) { - process.send(error.message); - process.exit(1); -}); +process.on('uncaughtException', function (error) { + process.send(error.message) + process.exit(1) +}) -process.on('message', function(msg) { - setImmediate(function() { - process.send('ok'); - process.exit(0); - }); -}); +process.on('message', function () { + setImmediate(function () { + process.send('ok') + process.exit(0) + }) +}) diff --git a/spec/fixtures/pages/permissions/geolocation.html b/spec/fixtures/pages/permissions/geolocation.html new file mode 100644 index 00000000000..1d1b4fc4245 --- /dev/null +++ b/spec/fixtures/pages/permissions/geolocation.html @@ -0,0 +1,5 @@ + diff --git a/spec/fixtures/pages/permissions/media.html b/spec/fixtures/pages/permissions/media.html new file mode 100644 index 00000000000..0d968a9a66b --- /dev/null +++ b/spec/fixtures/pages/permissions/media.html @@ -0,0 +1,7 @@ + diff --git a/spec/fixtures/pages/permissions/midi.html b/spec/fixtures/pages/permissions/midi.html new file mode 100644 index 00000000000..c6bd8c137e3 --- /dev/null +++ b/spec/fixtures/pages/permissions/midi.html @@ -0,0 +1,5 @@ + diff --git a/spec/fixtures/pages/process-exit.html b/spec/fixtures/pages/process-exit.html new file mode 100644 index 00000000000..012c9f85b15 --- /dev/null +++ b/spec/fixtures/pages/process-exit.html @@ -0,0 +1,9 @@ + + + + + diff --git a/spec/fixtures/pages/service-worker/service-worker.js b/spec/fixtures/pages/service-worker/service-worker.js index 854b3558a95..7d80f45e2df 100644 --- a/spec/fixtures/pages/service-worker/service-worker.js +++ b/spec/fixtures/pages/service-worker/service-worker.js @@ -1,9 +1,9 @@ -self.addEventListener('fetch', function(event) { - var requestUrl = new URL(event.request.url); +self.addEventListener('fetch', function (event) { + var requestUrl = new URL(event.request.url) if (requestUrl.pathname === '/echo' && - event.request.headers.has('X-Mock-Response')) { - var mockResponse = new Response('Hello from serviceWorker!'); - event.respondWith(mockResponse); + event.request.headers.has('X-Mock-Response')) { + var mockResponse = new Response('Hello from serviceWorker!') + event.respondWith(mockResponse) } -}); +}) diff --git a/spec/fixtures/pages/webview-opener-no-node-integration.html b/spec/fixtures/pages/webview-opener-no-node-integration.html new file mode 100644 index 00000000000..1275a99b0bf --- /dev/null +++ b/spec/fixtures/pages/webview-opener-no-node-integration.html @@ -0,0 +1,8 @@ + + + + + diff --git a/spec/fixtures/pages/window-opener-no-node-integration.html b/spec/fixtures/pages/window-opener-no-node-integration.html new file mode 100644 index 00000000000..b587e979227 --- /dev/null +++ b/spec/fixtures/pages/window-opener-no-node-integration.html @@ -0,0 +1,15 @@ + + + + + diff --git a/spec/fixtures/pages/window-opener-node.html b/spec/fixtures/pages/window-opener-node.html index 118603c82d3..13036072876 100644 --- a/spec/fixtures/pages/window-opener-node.html +++ b/spec/fixtures/pages/window-opener-node.html @@ -1,7 +1,7 @@ diff --git a/spec/fixtures/workers/shared_worker.js b/spec/fixtures/workers/shared_worker.js index d35b47435e8..35cb40d03b5 100644 --- a/spec/fixtures/workers/shared_worker.js +++ b/spec/fixtures/workers/shared_worker.js @@ -1,7 +1,7 @@ -onconnect = function(event) { - var port = event.ports[0]; - port.start(); - port.onmessage = function(event) { - port.postMessage(event.data); +this.onconnect = function (event) { + var port = event.ports[0] + port.start() + port.onmessage = function (event) { + port.postMessage(event.data) } } diff --git a/spec/fixtures/workers/worker.js b/spec/fixtures/workers/worker.js index 6f9533708a0..884c17ac86e 100644 --- a/spec/fixtures/workers/worker.js +++ b/spec/fixtures/workers/worker.js @@ -1,3 +1,3 @@ -this.onmessage = function(msg) { - this.postMessage(msg.data); +this.onmessage = function (msg) { + this.postMessage(msg.data) } diff --git a/spec/modules-spec.js b/spec/modules-spec.js index 6a0471e4cea..0f694e5e6d8 100644 --- a/spec/modules-spec.js +++ b/spec/modules-spec.js @@ -1,47 +1,47 @@ -const assert = require('assert'); -const path = require('path'); -const temp = require('temp'); +const assert = require('assert') +const path = require('path') +const temp = require('temp') + +describe('third-party module', function () { + var fixtures = path.join(__dirname, 'fixtures') + temp.track() -describe('third-party module', function() { - var fixtures; - 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() { - return require('runas'); - }); - return it('can be required in node binary', function(done) { - var child, runas; - runas = path.join(fixtures, 'module', 'runas.js'); - child = require('child_process').fork(runas); - return child.on('message', function(msg) { - assert.equal(msg, 'ok'); - return done(); - }); - }); - }); - describe('ffi', function() { - return it('does not crash', function() { - var ffi, libm; - ffi = require('ffi'); - libm = ffi.Library('libm', { + 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() + }) + }) + }) + + describe('ffi', function () { + it('does not crash', function () { + var ffi = require('ffi') + var libm = ffi.Library('libm', { ceil: ['double', ['double']] - }); - return assert.equal(libm.ceil(1.5), 2); - }); - }); + }) + assert.equal(libm.ceil(1.5), 2) + }) + }) } - return describe('q', function() { - var Q; - Q = require('q'); - return describe('Q.when', function() { - return it('emits the fullfil callback', function(done) { - return Q(true).then(function(val) { - assert.equal(val, true); - return done(); - }); - }); - }); - }); -}); + + 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() + }) + }) + }) + }) +}) diff --git a/spec/node-spec.js b/spec/node-spec.js index c7a74efd6c7..286c4d6f916 100644 --- a/spec/node-spec.js +++ b/spec/node-spec.js @@ -1,206 +1,220 @@ -const assert = require('assert'); -const child_process = require('child_process'); -const fs = require('fs'); -const path = require('path'); -const os = require('os'); -const remote = require('electron').remote; +const assert = require('assert') +const child_process = require('child_process') +const fs = require('fs') +const path = require('path') +const os = require('os') +const remote = require('electron').remote -describe('node feature', function() { - var fixtures; - fixtures = path.join(__dirname, 'fixtures'); - describe('child_process', function() { - return describe('child_process.fork', function() { - it('works in current process', function(done) { - var child; - child = child_process.fork(path.join(fixtures, 'module', 'ping.js')); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - return done(); - }); - return child.send('message'); - }); - it('preserves args', function(done) { - var args, child; - args = ['--expose_gc', '-test', '1']; - child = child_process.fork(path.join(fixtures, 'module', 'process_args.js'), args); - child.on('message', function(msg) { - assert.deepEqual(args, msg.slice(2)); - return done(); - }); - return child.send('message'); - }); - it('works in forked process', function(done) { - var child; - child = child_process.fork(path.join(fixtures, 'module', 'fork_ping.js')); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - return done(); - }); - return child.send('message'); - }); - it('works in forked process when options.env is specifed', function(done) { - var child; - child = child_process.fork(path.join(fixtures, 'module', 'fork_ping.js'), [], { +describe('node feature', function () { + var fixtures = path.join(__dirname, 'fixtures') + + describe('child_process', function () { + describe('child_process.fork', function () { + it('works in current process', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'ping.js')) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) + + it('preserves args', function (done) { + var args = ['--expose_gc', '-test', '1'] + var child = child_process.fork(path.join(fixtures, 'module', 'process_args.js'), args) + child.on('message', function (msg) { + assert.deepEqual(args, msg.slice(2)) + done() + }) + child.send('message') + }) + + it('works in forked process', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'fork_ping.js')) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) + + it('works in forked process when options.env is specifed', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'fork_ping.js'), [], { path: process.env['PATH'] - }); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - return done(); - }); - return child.send('message'); - }); - it('works in browser process', function(done) { - var child, fork; - fork = remote.require('child_process').fork; - child = fork(path.join(fixtures, 'module', 'ping.js')); - child.on('message', function(msg) { - assert.equal(msg, 'message'); - return done(); - }); - return child.send('message'); - }); - it('has String::localeCompare working in script', function(done) { - var child; - child = child_process.fork(path.join(fixtures, 'module', 'locale-compare.js')); - child.on('message', function(msg) { - assert.deepEqual(msg, [0, -1, 1]); - return done(); - }); - return child.send('message'); - }); - return it('has setImmediate working in script', function(done) { - var child; - child = child_process.fork(path.join(fixtures, 'module', 'set-immediate.js')); - child.on('message', function(msg) { - assert.equal(msg, 'ok'); - return done(); - }); - return child.send('message'); - }); - }); - }); - describe('contexts', function() { - describe('setTimeout in fs callback', function() { + }) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) + + it('works in browser process', function (done) { + var fork = remote.require('child_process').fork + var child = fork(path.join(fixtures, 'module', 'ping.js')) + child.on('message', function (msg) { + assert.equal(msg, 'message') + done() + }) + child.send('message') + }) + + it('has String::localeCompare working in script', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'locale-compare.js')) + child.on('message', function (msg) { + assert.deepEqual(msg, [0, -1, 1]) + done() + }) + child.send('message') + }) + + it('has setImmediate working in script', function (done) { + var child = child_process.fork(path.join(fixtures, 'module', 'set-immediate.js')) + child.on('message', function (msg) { + assert.equal(msg, 'ok') + done() + }) + child.send('message') + }) + }) + }) + + describe('contexts', function () { + describe('setTimeout in fs callback', function () { if (process.env.TRAVIS === 'true') { - return; + return } - return it('does not crash', function(done) { - return fs.readFile(__filename, function() { - return setTimeout(done, 0); - }); - }); - }); - describe('throw error in node context', function() { - return it('gets caught', function(done) { - var error, lsts; - error = new Error('boo!'); - lsts = process.listeners('uncaughtException'); - process.removeAllListeners('uncaughtException'); - process.on('uncaughtException', function() { - var i, len, lst; - process.removeAllListeners('uncaughtException'); + + it('does not crash', function (done) { + fs.readFile(__filename, function () { + setTimeout(done, 0) + }) + }) + }) + + describe('throw error in node context', function () { + it('gets caught', function (done) { + var error = new Error('boo!') + var lsts = process.listeners('uncaughtException') + process.removeAllListeners('uncaughtException') + process.on('uncaughtException', function () { + var i, len, lst + process.removeAllListeners('uncaughtException') for (i = 0, len = lsts.length; i < len; i++) { - lst = lsts[i]; - process.on('uncaughtException', lst); + lst = lsts[i] + process.on('uncaughtException', lst) } - return done(); - }); - return fs.readFile(__filename, function() { - throw error; - }); - }); - }); - describe('setTimeout called under Chromium event loop in browser process', function() { - return it('can be scheduled in time', function(done) { - return remote.getGlobal('setTimeout')(done, 0); - }); - }); - return describe('setInterval called under Chromium event loop in browser process', function() { - return it('can be scheduled in time', function(done) { - var clear, interval; - clear = function() { - remote.getGlobal('clearInterval')(interval); - return done(); - }; - return interval = remote.getGlobal('setInterval')(clear, 10); - }); - }); - }); - describe('message loop', function() { - describe('process.nextTick', function() { - it('emits the callback', function(done) { - return process.nextTick(done); - }); - return it('works in nested calls', function(done) { - return process.nextTick(function() { - return process.nextTick(function() { - return process.nextTick(done); - }); - }); - }); - }); - return describe('setImmediate', function() { - it('emits the callback', function(done) { - return setImmediate(done); - }); - return it('works in nested calls', function(done) { - return setImmediate(function() { - return setImmediate(function() { - return setImmediate(done); - }); - }); - }); - }); - }); - describe('net.connect', function() { + done() + }) + fs.readFile(__filename, function () { + throw error + }) + }) + }) + + describe('setTimeout called under Chromium event loop in browser process', function () { + it('can be scheduled in time', function (done) { + remote.getGlobal('setTimeout')(done, 0) + }) + }) + + describe('setInterval called under Chromium event loop in browser process', function () { + it('can be scheduled in time', function (done) { + var clear, interval + clear = function () { + remote.getGlobal('clearInterval')(interval) + done() + } + interval = remote.getGlobal('setInterval')(clear, 10) + }) + }) + }) + + describe('message loop', function () { + describe('process.nextTick', function () { + it('emits the callback', function (done) { + process.nextTick(done) + }) + + it('works in nested calls', function (done) { + process.nextTick(function () { + process.nextTick(function () { + process.nextTick(done) + }) + }) + }) + }) + + describe('setImmediate', function () { + it('emits the callback', function (done) { + setImmediate(done) + }) + + it('works in nested calls', function (done) { + setImmediate(function () { + setImmediate(function () { + setImmediate(done) + }) + }) + }) + }) + }) + + describe('net.connect', function () { if (process.platform !== 'darwin') { - return; + return } - return it('emit error when connect to a socket path without listeners', function(done) { - var child, script, socketPath; - socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock'); - script = path.join(fixtures, 'module', 'create_socket.js'); - child = child_process.fork(script, [socketPath]); - return child.on('exit', function(code) { - var client; - assert.equal(code, 0); - client = require('net').connect(socketPath); - return client.on('error', function(error) { - assert.equal(error.code, 'ECONNREFUSED'); - return done(); - }); - }); - }); - }); - describe('Buffer', function() { - it('can be created from WebKit external string', function() { - var b, p; - p = document.createElement('p'); - p.innerText = '闲云潭影日悠悠,物换星移几度秋'; - b = new Buffer(p.innerText); - assert.equal(b.toString(), '闲云潭影日悠悠,物换星移几度秋'); - return assert.equal(Buffer.byteLength(p.innerText), 45); - }); - return it('correctly parses external one-byte UTF8 string', function() { - var b, p; - p = document.createElement('p'); - p.innerText = 'Jøhänñéß'; - b = new Buffer(p.innerText); - assert.equal(b.toString(), 'Jøhänñéß'); - return assert.equal(Buffer.byteLength(p.innerText), 13); - }); - }); - describe('process.stdout', function() { - it('should not throw exception', function() { - return process.stdout; - }); - return xit('should have isTTY defined', function() { - return assert.equal(typeof process.stdout.isTTY, 'boolean'); - }); - }); - return describe('vm.createContext', function() { - return it('should not crash', function() { - return require('vm').runInNewContext(''); - }); - }); -}); + + it('emit error when connect to a socket path without listeners', function (done) { + var socketPath = path.join(os.tmpdir(), 'atom-shell-test.sock') + var script = path.join(fixtures, 'module', 'create_socket.js') + var child = child_process.fork(script, [socketPath]) + child.on('exit', function (code) { + assert.equal(code, 0) + var client = require('net').connect(socketPath) + client.on('error', function (error) { + assert.equal(error.code, 'ECONNREFUSED') + done() + }) + }) + }) + }) + + describe('Buffer', function () { + it('can be created from WebKit external string', function () { + var p = document.createElement('p') + p.innerText = '闲云潭影日悠悠,物换星移几度秋' + var b = new Buffer(p.innerText) + assert.equal(b.toString(), '闲云潭影日悠悠,物换星移几度秋') + assert.equal(Buffer.byteLength(p.innerText), 45) + }) + + it('correctly parses external one-byte UTF8 string', function () { + var p = document.createElement('p') + p.innerText = 'Jøhänñéß' + var b = new Buffer(p.innerText) + assert.equal(b.toString(), 'Jøhänñéß') + assert.equal(Buffer.byteLength(p.innerText), 13) + }) + }) + + describe('process.stdout', function () { + it('should not throw exception', function () { + process.stdout + }) + + it('should not throw exception when calling write()', function () { + process.stdout.write('test') + }) + + xit('should have isTTY defined', function () { + assert.equal(typeof process.stdout.isTTY, 'boolean') + }) + }) + + describe('vm.createContext', function () { + it('should not crash', function () { + require('vm').runInNewContext('') + }) + }) +}) diff --git a/spec/package.json b/spec/package.json index 6d49f0da8b8..0439f4b0ec0 100644 --- a/spec/package.json +++ b/spec/package.json @@ -18,5 +18,15 @@ "optionalDependencies": { "ffi": "2.0.0", "runas": "3.x" + }, + "standard": { + "env": { + "mocha": true, + "jquery": true, + "serviceworker": true + }, + "globals": [ + "WebView" + ] } } diff --git a/spec/static/index.html b/spec/static/index.html index e31fcffebb8..3c57f94d144 100644 --- a/spec/static/index.html +++ b/spec/static/index.html @@ -3,6 +3,11 @@ + diff --git a/spec/static/main.js b/spec/static/main.js index b295ba18552..2707f6a686d 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -1,121 +1,151 @@ // Disable use of deprecated functions. -process.throwDeprecation = true; +process.throwDeprecation = true -const electron = require('electron'); -const app = electron.app; -const ipcMain = electron.ipcMain; -const dialog = electron.dialog; -const BrowserWindow = electron.BrowserWindow; +const electron = require('electron') +const app = electron.app +const ipcMain = electron.ipcMain +const dialog = electron.dialog +const BrowserWindow = electron.BrowserWindow -const path = require('path'); -const url = require('url'); +const path = require('path') +const url = require('url') var argv = require('yargs') .boolean('ci') .string('g').alias('g', 'grep') .boolean('i').alias('i', 'invert') - .argv; + .argv -var window = null; -process.port = 0; // will be used by crash-reporter spec. +var window = null +process.port = 0 // will be used by crash-reporter spec. -app.commandLine.appendSwitch('js-flags', '--expose_gc'); -app.commandLine.appendSwitch('ignore-certificate-errors'); -app.commandLine.appendSwitch('disable-renderer-backgrounding'); +app.commandLine.appendSwitch('js-flags', '--expose_gc') +app.commandLine.appendSwitch('ignore-certificate-errors') +app.commandLine.appendSwitch('disable-renderer-backgrounding') // Accessing stdout in the main process will result in the process.stdout // throwing UnknownSystemError in renderer process sometimes. This line makes // sure we can reproduce it in renderer process. -process.stdout; +process.stdout -ipcMain.on('message', function(event, arg) { - event.sender.send('message', arg); -}); +// Access console to reproduce #3482. +console -ipcMain.on('console.log', function(event, args) { - console.error.apply(console, args); -}); +ipcMain.on('message', function (event, arg) { + event.sender.send('message', arg) +}) -ipcMain.on('console.error', function(event, args) { - console.error.apply(console, args); -}); +ipcMain.on('console.log', function (event, args) { + console.error.apply(console, args) +}) -ipcMain.on('process.exit', function(event, code) { - process.exit(code); -}); +ipcMain.on('console.error', function (event, args) { + console.error.apply(console, args) +}) -ipcMain.on('eval', function(event, script) { - event.returnValue = eval(script); -}); +ipcMain.on('process.exit', function (event, code) { + process.exit(code) +}) -ipcMain.on('echo', function(event, msg) { - event.returnValue = msg; -}); +ipcMain.on('eval', function (event, script) { + event.returnValue = eval(script) // eslint-disable-line +}) -global.isCi = !!argv.ci; +ipcMain.on('echo', function (event, msg) { + event.returnValue = msg +}) + +global.isCi = !!argv.ci if (global.isCi) { - process.removeAllListeners('uncaughtException'); - process.on('uncaughtException', function(error) { - console.error(error, error.stack); - process.exit(1); - }); + process.removeAllListeners('uncaughtException') + process.on('uncaughtException', function (error) { + console.error(error, error.stack) + process.exit(1) + }) } -app.on('window-all-closed', function() { - app.quit(); -}); +app.on('window-all-closed', function () { + app.quit() +}) -app.on('ready', function() { +app.on('ready', function () { // Test if using protocol module would crash. - electron.protocol.registerStringProtocol('test-if-crashes', function() {}); + electron.protocol.registerStringProtocol('test-if-crashes', function () {}) + + // Send auto updater errors to window to be verified in specs + electron.autoUpdater.on('error', function (error) { + window.send('auto-updater-error', error.message) + }) window = new BrowserWindow({ title: 'Electron Tests', show: false, width: 800, height: 600, - 'web-preferences': { - javascript: true // Test whether web-preferences crashes. - }, - }); + webPreferences: { + javascript: true // Test whether web preferences crashes. + } + }) window.loadURL(url.format({ - pathname: __dirname + '/index.html', + pathname: path.join(__dirname, '/index.html'), protocol: 'file', query: { grep: argv.grep, - invert: argv.invert ? 'true': '' + invert: argv.invert ? 'true' : '' } - })); - window.on('unresponsive', function() { + })) + window.on('unresponsive', function () { var chosen = dialog.showMessageBox(window, { type: 'warning', buttons: ['Close', 'Keep Waiting'], message: 'Window is not responsing', detail: 'The window is not responding. Would you like to force close it or just keep waiting?' - }); - if (chosen == 0) window.destroy(); - }); + }) + if (chosen === 0) window.destroy() + }) // For session's download test, listen 'will-download' event in browser, and // reply the result to renderer for verifying - var downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf'); - ipcMain.on('set-download-option', function(event, need_cancel) { - window.webContents.session.once('will-download', - function(e, item, webContents) { - item.setSavePath(downloadFilePath); - item.on('done', function(e, state) { - window.webContents.send('download-done', - state, - item.getURL(), - item.getMimeType(), - item.getReceivedBytes(), - item.getTotalBytes(), - item.getContentDisposition(), - item.getFilename()); - }); - if (need_cancel) - item.cancel(); - }); - event.returnValue = "done"; - }); -}); + var downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf') + ipcMain.on('set-download-option', function (event, need_cancel, prevent_default) { + window.webContents.session.once('will-download', function (e, item) { + if (prevent_default) { + e.preventDefault() + const url = item.getURL() + const filename = item.getFilename() + setImmediate(function () { + try { + item.getURL() + } catch (err) { + window.webContents.send('download-error', url, filename, err.message) + } + }) + } else { + item.setSavePath(downloadFilePath) + item.on('done', function (e, state) { + window.webContents.send('download-done', + state, + item.getURL(), + item.getMimeType(), + item.getReceivedBytes(), + item.getTotalBytes(), + item.getContentDisposition(), + item.getFilename()) + }) + if (need_cancel) item.cancel() + } + }) + event.returnValue = 'done' + }) + + ipcMain.on('executeJavaScript', function (event, code, hasCallback) { + if (hasCallback) { + window.webContents.executeJavaScript(code, (result) => { + window.webContents.send('executeJavaScript-response', result) + }) + } else { + window.webContents.executeJavaScript(code) + event.returnValue = 'success' + } + }) +}) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 0eaa59c5fd3..0d32138906e 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1,636 +1,766 @@ -var assert, http, path, url; +const assert = require('assert') +const path = require('path') +const http = require('http') +const url = require('url') +const {app, session} = require('electron').remote -assert = require('assert'); +describe(' tag', function () { + this.timeout(10000) -path = require('path'); + var fixtures = path.join(__dirname, 'fixtures') + var webview = null -http = require('http'); + beforeEach(function () { + webview = new WebView() + }) -url = require('url'); - -describe(' tag', function() { - var fixtures, webview; - this.timeout(10000); - fixtures = path.join(__dirname, 'fixtures'); - webview = null; - beforeEach(function() { - return webview = new WebView; - }); - afterEach(function() { + afterEach(function () { if (document.body.contains(webview)) { - return document.body.removeChild(webview); + document.body.removeChild(webview) } - }); - describe('src attribute', function() { - it('specifies the page to load', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'a'); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/a.html"; - return document.body.appendChild(webview); - }); - return it('navigates to new page when changed', function(done) { - var listener = function() { - webview.src = "file://" + fixtures + "/pages/b.html"; - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'b'); - return done(); - }); - return webview.removeEventListener('did-finish-load', listener); - }; - webview.addEventListener('did-finish-load', listener); - webview.src = "file://" + fixtures + "/pages/a.html"; - return document.body.appendChild(webview); - }); - }); - describe('nodeintegration attribute', function() { - it('inserts no node symbols when not set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'undefined undefined undefined undefined'); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/c.html"; - return document.body.appendChild(webview); - }); - it('inserts node symbols when set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'function object object'); - return done(); - }); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/d.html"; - return document.body.appendChild(webview); - }); - it('loads node symbols after POST navigation when set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'function object object'); - return done(); - }); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/post.html"; - return document.body.appendChild(webview); - }); + }) + + describe('src attribute', function () { + it('specifies the page to load', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'a') + done() + }) + webview.src = 'file://' + fixtures + '/pages/a.html' + document.body.appendChild(webview) + }) + + it('navigates to new page when changed', function (done) { + var listener = function () { + webview.src = 'file://' + fixtures + '/pages/b.html' + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'b') + done() + }) + webview.removeEventListener('did-finish-load', listener) + } + webview.addEventListener('did-finish-load', listener) + webview.src = 'file://' + fixtures + '/pages/a.html' + document.body.appendChild(webview) + }) + }) + + describe('nodeintegration attribute', function () { + it('inserts no node symbols when not set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'undefined undefined undefined undefined') + done() + }) + webview.src = 'file://' + fixtures + '/pages/c.html' + document.body.appendChild(webview) + }) + + it('inserts node symbols when set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'function object object') + done() + }) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/d.html' + document.body.appendChild(webview) + }) + + it('loads node symbols after POST navigation when set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'function object object') + done() + }) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/post.html' + document.body.appendChild(webview) + }) + + + it('disables node integration on child windows when it is disabled on the webview', function (done) { + app.once('browser-window-created', function (event, window) { + assert.equal(window.webContents.getWebPreferences().nodeIntegration, false) + done() + }) + + webview.setAttribute('allowpopups', 'on') + + webview.src = url.format({ + pathname: `${fixtures}/pages/webview-opener-no-node-integration.html`, + protocol: 'file', + query: { + p: `${fixtures}/pages/window-opener-node.html` + }, + slashes: true + }) + document.body.appendChild(webview) + }) + if (process.platform !== 'win32' || process.execPath.toLowerCase().indexOf('\\out\\d\\') === -1) { - return it('loads native modules when navigation happens', function(done) { - var listener = function() { - webview.removeEventListener('did-finish-load', listener); - var listener2 = function(e) { - assert.equal(e.message, 'function'); - return done(); - }; - webview.addEventListener('console-message', listener2); - return webview.reload(); - }; - webview.addEventListener('did-finish-load', listener); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/native-module.html"; - return document.body.appendChild(webview); - }); + it('loads native modules when navigation happens', function (done) { + var listener = function () { + webview.removeEventListener('did-finish-load', listener) + var listener2 = function (e) { + assert.equal(e.message, 'function') + done() + } + webview.addEventListener('console-message', listener2) + webview.reload() + } + webview.addEventListener('did-finish-load', listener) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/native-module.html' + document.body.appendChild(webview) + }) } - }); - describe('preload attribute', function() { - it('loads the script before other scripts in window', function(done) { - var listener; - listener = function(e) { - assert.equal(e.message, 'function object object'); - webview.removeEventListener('console-message', listener); - return done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('preload', fixtures + "/module/preload.js"); - webview.src = "file://" + fixtures + "/pages/e.html"; - return document.body.appendChild(webview); - }); - it('preload script can still use "process" in required modules when nodeintegration is off', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'object undefined object'); - return done(); - }); - webview.setAttribute('preload', fixtures + "/module/preload-node-off.js"); - webview.src = "file://" + fixtures + "/api/blank.html"; - return document.body.appendChild(webview); - }); - return it('receives ipc message in preload script', function(done) { - var listener, listener2, message; - message = 'boom!'; - listener = function(e) { - assert.equal(e.channel, 'pong'); - assert.deepEqual(e.args, [message]); - webview.removeEventListener('ipc-message', listener); - return done(); - }; - listener2 = function() { - webview.send('ping', message); - return webview.removeEventListener('did-finish-load', listener2); - }; - webview.addEventListener('ipc-message', listener); - webview.addEventListener('did-finish-load', listener2); - webview.setAttribute('preload', fixtures + "/module/preload-ipc.js"); - webview.src = "file://" + fixtures + "/pages/e.html"; - return document.body.appendChild(webview); - }); - }); - describe('httpreferrer attribute', function() { - return it('sets the referrer url', function(done) { - var listener, referrer; - referrer = 'http://github.com/'; - listener = function(e) { - assert.equal(e.message, referrer); - webview.removeEventListener('console-message', listener); - return done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('httpreferrer', referrer); - webview.src = "file://" + fixtures + "/pages/referrer.html"; - return document.body.appendChild(webview); - }); - }); - describe('useragent attribute', function() { - return it('sets the user agent', function(done) { - var listener, referrer; - referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko'; - listener = function(e) { - assert.equal(e.message, referrer); - webview.removeEventListener('console-message', listener); - return done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('useragent', referrer); - webview.src = "file://" + fixtures + "/pages/useragent.html"; - return document.body.appendChild(webview); - }); - }); - describe('disablewebsecurity attribute', function() { - it('does not disable web security when not set', function(done) { - var encoded, listener, src; - src = " "; - encoded = btoa(unescape(encodeURIComponent(src))); - listener = function(e) { - assert(/Not allowed to load local resource/.test(e.message)); - webview.removeEventListener('console-message', listener); - return done(); - }; - webview.addEventListener('console-message', listener); - webview.src = "data:text/html;base64," + encoded; - return document.body.appendChild(webview); - }); - return it('disables web security when set', function(done) { - var encoded, listener, src; - src = " "; - encoded = btoa(unescape(encodeURIComponent(src))); - listener = function(e) { - assert.equal(e.message, 'ok'); - webview.removeEventListener('console-message', listener); - return done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('disablewebsecurity', ''); - webview.src = "data:text/html;base64," + encoded; - return document.body.appendChild(webview); - }); - }); - describe('partition attribute', function() { - it('inserts no node symbols when not set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'undefined undefined undefined undefined'); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/c.html"; - webview.partition = 'test1'; - return document.body.appendChild(webview); - }); - it('inserts node symbols when set', function(done) { - webview.addEventListener('console-message', function(e) { - assert.equal(e.message, 'function object object'); - return done(); - }); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/d.html"; - webview.partition = 'test2'; - return document.body.appendChild(webview); - }); - it('isolates storage for different id', function(done) { - var listener; - listener = function(e) { - assert.equal(e.message, " 0"); - webview.removeEventListener('console-message', listener); - return done(); - }; - window.localStorage.setItem('test', 'one'); - webview.addEventListener('console-message', listener); - webview.src = "file://" + fixtures + "/pages/partition/one.html"; - webview.partition = 'test3'; - return document.body.appendChild(webview); - }); - return it('uses current session storage when no id is provided', function(done) { - var listener; - listener = function(e) { - assert.equal(e.message, "one 1"); - webview.removeEventListener('console-message', listener); - return done(); - }; - window.localStorage.setItem('test', 'one'); - webview.addEventListener('console-message', listener); - webview.src = "file://" + fixtures + "/pages/partition/one.html"; - return document.body.appendChild(webview); - }); - }); - describe('allowpopups attribute', function() { - it('can not open new window when not set', function(done) { - var listener; - listener = function(e) { - assert.equal(e.message, 'null'); - webview.removeEventListener('console-message', listener); - return done(); - }; - webview.addEventListener('console-message', listener); - webview.src = "file://" + fixtures + "/pages/window-open-hide.html"; - return document.body.appendChild(webview); - }); - return it('can open new window when set', function(done) { - var listener; - listener = function(e) { - assert.equal(e.message, 'window'); - webview.removeEventListener('console-message', listener); - return done(); - }; - webview.addEventListener('console-message', listener); - webview.setAttribute('allowpopups', 'on'); - webview.src = "file://" + fixtures + "/pages/window-open-hide.html"; - return document.body.appendChild(webview); - }); - }); - describe('new-window event', function() { - it('emits when window.open is called', function(done) { - webview.addEventListener('new-window', function(e) { - assert.equal(e.url, 'http://host/'); - assert.equal(e.frameName, 'host'); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/window-open.html"; - return document.body.appendChild(webview); - }); - return it('emits when link with target is called', function(done) { - webview.addEventListener('new-window', function(e) { - assert.equal(e.url, 'http://host/'); - assert.equal(e.frameName, 'target'); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/target-name.html"; - return document.body.appendChild(webview); - }); - }); - describe('ipc-message event', function() { - return it('emits when guest sends a ipc message to browser', function(done) { - webview.addEventListener('ipc-message', function(e) { - assert.equal(e.channel, 'channel'); - assert.deepEqual(e.args, ['arg1', 'arg2']); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/ipc-message.html"; - webview.setAttribute('nodeintegration', 'on'); - return document.body.appendChild(webview); - }); - }); - describe('page-title-set event', function() { - return it('emits when title is set', function(done) { - webview.addEventListener('page-title-set', function(e) { - assert.equal(e.title, 'test'); - assert(e.explicitSet); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/a.html"; - return document.body.appendChild(webview); - }); - }); - describe('page-favicon-updated event', function() { - return it('emits when favicon urls are received', function(done) { - webview.addEventListener('page-favicon-updated', function(e) { - var pageUrl; - assert.equal(e.favicons.length, 2); - pageUrl = process.platform === 'win32' ? 'file:///C:/favicon.png' : 'file:///favicon.png'; - assert.equal(e.favicons[0], pageUrl); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/a.html"; - return document.body.appendChild(webview); - }); - }); - describe('will-navigate event', function() { - return it('emits when a url that leads to oustide of the page is clicked', function(done) { - webview.addEventListener('will-navigate', function(e) { - assert.equal(e.url, "http://host/"); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/webview-will-navigate.html"; - return document.body.appendChild(webview); - }); - }); - describe('did-navigate event', function() { - var p, pageUrl; - p = path.join(fixtures, 'pages', 'webview-will-navigate.html'); - p = p.replace(/\\/g, '/'); - pageUrl = url.format({ + }) + + 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') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('preload', fixtures + '/module/preload.js') + webview.src = 'file://' + fixtures + '/pages/e.html' + document.body.appendChild(webview) + }) + + it('preload script can still use "process" in required modules when nodeintegration is off', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'object undefined object') + done() + }) + webview.setAttribute('preload', fixtures + '/module/preload-node-off.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) { + assert.equal(e.channel, 'pong') + assert.deepEqual(e.args, [message]) + webview.removeEventListener('ipc-message', listener) + done() + } + var listener2 = function () { + webview.send('ping', message) + webview.removeEventListener('did-finish-load', listener2) + } + webview.addEventListener('ipc-message', listener) + webview.addEventListener('did-finish-load', listener2) + webview.setAttribute('preload', fixtures + '/module/preload-ipc.js') + webview.src = 'file://' + fixtures + '/pages/e.html' + document.body.appendChild(webview) + }) + }) + + describe('httpreferrer attribute', function () { + it('sets the referrer url', function (done) { + var referrer = 'http://github.com/' + var listener = function (e) { + assert.equal(e.message, referrer) + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('httpreferrer', referrer) + webview.src = 'file://' + fixtures + '/pages/referrer.html' + document.body.appendChild(webview) + }) + }) + + describe('useragent attribute', function () { + it('sets the user agent', function (done) { + var referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko' + var listener = function (e) { + assert.equal(e.message, referrer) + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('useragent', referrer) + webview.src = 'file://' + fixtures + '/pages/useragent.html' + document.body.appendChild(webview) + }) + }) + + describe('disablewebsecurity attribute', function () { + it('does not disable web security when not set', function (done) { + var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') + var src = ` ` + var encoded = btoa(unescape(encodeURIComponent(src))) + var listener = function (e) { + assert(/Not allowed to load local resource/.test(e.message)) + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.src = 'data:text/html;base64,' + encoded + document.body.appendChild(webview) + }) + + it('disables web security when set', function (done) { + var jqueryPath = path.join(__dirname, '/static/jquery-2.0.3.min.js') + var src = ` ` + var encoded = btoa(unescape(encodeURIComponent(src))) + var listener = function (e) { + assert.equal(e.message, 'ok') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('disablewebsecurity', '') + webview.src = 'data:text/html;base64,' + encoded + document.body.appendChild(webview) + }) + }) + + describe('partition attribute', function () { + it('inserts no node symbols when not set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'undefined undefined undefined undefined') + done() + }) + webview.src = 'file://' + fixtures + '/pages/c.html' + webview.partition = 'test1' + document.body.appendChild(webview) + }) + + it('inserts node symbols when set', function (done) { + webview.addEventListener('console-message', function (e) { + assert.equal(e.message, 'function object object') + done() + }) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/d.html' + webview.partition = 'test2' + document.body.appendChild(webview) + }) + + it('isolates storage for different id', function (done) { + var listener = function (e) { + assert.equal(e.message, ' 0') + webview.removeEventListener('console-message', listener) + done() + } + window.localStorage.setItem('test', 'one') + webview.addEventListener('console-message', listener) + webview.src = 'file://' + fixtures + '/pages/partition/one.html' + webview.partition = 'test3' + document.body.appendChild(webview) + }) + + it('uses current session storage when no id is provided', function (done) { + var listener = function (e) { + assert.equal(e.message, 'one 1') + webview.removeEventListener('console-message', listener) + done() + } + window.localStorage.setItem('test', 'one') + webview.addEventListener('console-message', listener) + webview.src = 'file://' + fixtures + '/pages/partition/one.html' + document.body.appendChild(webview) + }) + }) + + describe('allowpopups attribute', function () { + it('can not open new window when not set', function (done) { + var listener = function (e) { + assert.equal(e.message, 'null') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.src = 'file://' + fixtures + '/pages/window-open-hide.html' + document.body.appendChild(webview) + }) + + it('can open new window when set', function (done) { + var listener = function (e) { + assert.equal(e.message, 'window') + webview.removeEventListener('console-message', listener) + done() + } + webview.addEventListener('console-message', listener) + webview.setAttribute('allowpopups', 'on') + webview.src = 'file://' + fixtures + '/pages/window-open-hide.html' + document.body.appendChild(webview) + }) + }) + + describe('new-window event', function () { + it('emits when window.open is called', function (done) { + webview.addEventListener('new-window', function (e) { + assert.equal(e.url, 'http://host/') + assert.equal(e.frameName, 'host') + done() + }) + webview.src = 'file://' + fixtures + '/pages/window-open.html' + document.body.appendChild(webview) + }) + + it('emits when link with target is called', function (done) { + webview.addEventListener('new-window', function (e) { + assert.equal(e.url, 'http://host/') + assert.equal(e.frameName, 'target') + done() + }) + webview.src = 'file://' + fixtures + '/pages/target-name.html' + document.body.appendChild(webview) + }) + }) + + describe('ipc-message event', function () { + it('emits when guest sends a ipc message to browser', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, 'channel') + assert.deepEqual(e.args, ['arg1', 'arg2']) + done() + }) + webview.src = 'file://' + fixtures + '/pages/ipc-message.html' + webview.setAttribute('nodeintegration', 'on') + document.body.appendChild(webview) + }) + }) + + describe('page-title-set event', function () { + it('emits when title is set', function (done) { + webview.addEventListener('page-title-set', function (e) { + assert.equal(e.title, 'test') + assert(e.explicitSet) + done() + }) + webview.src = 'file://' + fixtures + '/pages/a.html' + document.body.appendChild(webview) + }) + }) + + describe('page-favicon-updated event', 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) + done() + }) + webview.src = 'file://' + fixtures + '/pages/a.html' + document.body.appendChild(webview) + }) + }) + + describe('will-navigate event', function () { + it('emits when a url that leads to oustide of the page is clicked', function (done) { + webview.addEventListener('will-navigate', function (e) { + assert.equal(e.url, 'http://host/') + done() + }) + webview.src = 'file://' + fixtures + '/pages/webview-will-navigate.html' + document.body.appendChild(webview) + }) + }) + + describe('did-navigate event', function () { + var p = path.join(fixtures, 'pages', 'webview-will-navigate.html') + p = p.replace(/\\/g, '/') + var pageUrl = url.format({ protocol: 'file', slashes: true, pathname: p - }); - return it('emits when a url that leads to outside of the page is clicked', function(done) { - webview.addEventListener('did-navigate', function(e) { - assert.equal(e.url, pageUrl); - return done(); - }); - webview.src = pageUrl; - return document.body.appendChild(webview); - }); - }); - describe('did-navigate-in-page event', function() { - it('emits when an anchor link is clicked', function(done) { - var p, pageUrl; - p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page.html'); - p = p.replace(/\\/g, '/'); - pageUrl = url.format({ + }) + + it('emits when a url that leads to outside of the page is clicked', function (done) { + webview.addEventListener('did-navigate', function (e) { + assert.equal(e.url, pageUrl) + done() + }) + webview.src = pageUrl + document.body.appendChild(webview) + }) + }) + + describe('did-navigate-in-page event', function () { + it('emits when an anchor link is clicked', function (done) { + var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page.html') + p = p.replace(/\\/g, '/') + var pageUrl = url.format({ protocol: 'file', slashes: true, pathname: p - }); - webview.addEventListener('did-navigate-in-page', function(e) { - assert.equal(e.url, pageUrl + "#test_content"); - return done(); - }); - webview.src = pageUrl; - return document.body.appendChild(webview); - }); - it('emits when window.history.replaceState is called', function(done) { - webview.addEventListener('did-navigate-in-page', function(e) { - assert.equal(e.url, "http://host/"); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/webview-did-navigate-in-page-with-history.html"; - return document.body.appendChild(webview); - }); - return it('emits when window.location.hash is changed', function(done) { - var p, pageUrl; - p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html'); - p = p.replace(/\\/g, '/'); - pageUrl = url.format({ + }) + webview.addEventListener('did-navigate-in-page', function (e) { + assert.equal(e.url, pageUrl + '#test_content') + done() + }) + webview.src = pageUrl + document.body.appendChild(webview) + }) + + it('emits when window.history.replaceState is called', function (done) { + webview.addEventListener('did-navigate-in-page', function (e) { + assert.equal(e.url, 'http://host/') + done() + }) + webview.src = 'file://' + fixtures + '/pages/webview-did-navigate-in-page-with-history.html' + document.body.appendChild(webview) + }) + + it('emits when window.location.hash is changed', function (done) { + var p = path.join(fixtures, 'pages', 'webview-did-navigate-in-page-with-hash.html') + p = p.replace(/\\/g, '/') + var pageUrl = url.format({ protocol: 'file', slashes: true, pathname: p - }); - webview.addEventListener('did-navigate-in-page', function(e) { - assert.equal(e.url, pageUrl + "#test"); - return done(); - }); - webview.src = pageUrl; - return document.body.appendChild(webview); - }); - }); - describe('close event', function() { - return it('should fire when interior page calls window.close', function(done) { - webview.addEventListener('close', function() { - return done(); - }); - webview.src = "file://" + fixtures + "/pages/close.html"; - return document.body.appendChild(webview); - }); - }); - describe('devtools-opened event', function() { - return it('should fire when webview.openDevTools() is called', function(done) { - var listener; - listener = function() { - webview.removeEventListener('devtools-opened', listener); - webview.closeDevTools(); - return done(); - }; - webview.addEventListener('devtools-opened', listener); - webview.addEventListener('dom-ready', function() { - return webview.openDevTools(); - }); - webview.src = "file://" + fixtures + "/pages/base-page.html"; - return document.body.appendChild(webview); - }); - }); - describe('devtools-closed event', function() { - return it('should fire when webview.closeDevTools() is called', function(done) { - var listener, listener2; - listener2 = function() { - webview.removeEventListener('devtools-closed', listener2); - return done(); - }; - listener = function() { - webview.removeEventListener('devtools-opened', listener); - return webview.closeDevTools(); - }; - webview.addEventListener('devtools-opened', listener); - webview.addEventListener('devtools-closed', listener2); - webview.addEventListener('dom-ready', function() { - return webview.openDevTools(); - }); - webview.src = "file://" + fixtures + "/pages/base-page.html"; - return document.body.appendChild(webview); - }); - }); - describe('devtools-focused event', function() { - return it('should fire when webview.openDevTools() is called', function(done) { - var listener; - listener = function() { - webview.removeEventListener('devtools-focused', listener); - webview.closeDevTools(); - return done(); - }; - webview.addEventListener('devtools-focused', listener); - webview.addEventListener('dom-ready', function() { - return webview.openDevTools(); - }); - webview.src = "file://" + fixtures + "/pages/base-page.html"; - return document.body.appendChild(webview); - }); - }); - describe('.reload()', function() { - return it('should emit beforeunload handler', function(done) { - var listener, listener2; - listener = function(e) { - assert.equal(e.channel, 'onbeforeunload'); - webview.removeEventListener('ipc-message', listener); - return done(); - }; - listener2 = function() { - webview.reload(); - return webview.removeEventListener('did-finish-load', listener2); - }; - webview.addEventListener('ipc-message', listener); - webview.addEventListener('did-finish-load', listener2); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/beforeunload-false.html"; - return document.body.appendChild(webview); - }); - }); - describe('.clearHistory()', function() { - return it('should clear the navigation history', function(done) { - var listener; - listener = function(e) { - assert.equal(e.channel, 'history'); - assert.equal(e.args[0], 2); - assert(webview.canGoBack()); - webview.clearHistory(); - assert(!webview.canGoBack()); - webview.removeEventListener('ipc-message', listener); - return done(); - }; - webview.addEventListener('ipc-message', listener); - webview.setAttribute('nodeintegration', 'on'); - webview.src = "file://" + fixtures + "/pages/history.html"; - return document.body.appendChild(webview); - }); - }); - describe('basic auth', function() { - var auth; - auth = require('basic-auth'); - return it('should authenticate with correct credentials', function(done) { - var message, server; - message = 'Authenticated'; - server = http.createServer(function(req, res) { - var credentials; - credentials = auth(req); + }) + webview.addEventListener('did-navigate-in-page', function (e) { + assert.equal(e.url, pageUrl + '#test') + done() + }) + webview.src = pageUrl + document.body.appendChild(webview) + }) + }) + + describe('close event', function () { + it('should fire when interior page calls window.close', function (done) { + webview.addEventListener('close', function () { + done() + }) + webview.src = 'file://' + fixtures + '/pages/close.html' + document.body.appendChild(webview) + }) + }) + + describe('devtools-opened event', function () { + it('should fire when webview.openDevTools() is called', function (done) { + var listener = function () { + webview.removeEventListener('devtools-opened', listener) + webview.closeDevTools() + done() + } + webview.addEventListener('devtools-opened', listener) + webview.addEventListener('dom-ready', function () { + webview.openDevTools() + }) + webview.src = 'file://' + fixtures + '/pages/base-page.html' + document.body.appendChild(webview) + }) + }) + + describe('devtools-closed event', function () { + it('should fire when webview.closeDevTools() is called', function (done) { + var listener2 = function () { + webview.removeEventListener('devtools-closed', listener2) + done() + } + var listener = function () { + webview.removeEventListener('devtools-opened', listener) + webview.closeDevTools() + } + webview.addEventListener('devtools-opened', listener) + webview.addEventListener('devtools-closed', listener2) + webview.addEventListener('dom-ready', function () { + webview.openDevTools() + }) + webview.src = 'file://' + fixtures + '/pages/base-page.html' + document.body.appendChild(webview) + }) + }) + + describe('devtools-focused event', function () { + it('should fire when webview.openDevTools() is called', function (done) { + var listener = function () { + webview.removeEventListener('devtools-focused', listener) + webview.closeDevTools() + done() + } + webview.addEventListener('devtools-focused', listener) + webview.addEventListener('dom-ready', function () { + webview.openDevTools() + }) + webview.src = 'file://' + fixtures + '/pages/base-page.html' + document.body.appendChild(webview) + }) + }) + + describe('.reload()', function () { + it('should emit beforeunload handler', function (done) { + var listener = function (e) { + assert.equal(e.channel, 'onbeforeunload') + webview.removeEventListener('ipc-message', listener) + done() + } + var listener2 = function () { + webview.reload() + webview.removeEventListener('did-finish-load', listener2) + } + webview.addEventListener('ipc-message', listener) + webview.addEventListener('did-finish-load', listener2) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/beforeunload-false.html' + document.body.appendChild(webview) + }) + }) + + describe('.clearHistory()', function () { + it('should clear the navigation history', function (done) { + var listener = function (e) { + assert.equal(e.channel, 'history') + assert.equal(e.args[0], 2) + assert(webview.canGoBack()) + webview.clearHistory() + assert(!webview.canGoBack()) + webview.removeEventListener('ipc-message', listener) + done() + } + webview.addEventListener('ipc-message', listener) + webview.setAttribute('nodeintegration', 'on') + webview.src = 'file://' + fixtures + '/pages/history.html' + document.body.appendChild(webview) + }) + }) + + describe('basic auth', function () { + var auth = require('basic-auth') + + it('should authenticate with correct credentials', function (done) { + var message = 'Authenticated' + var server = http.createServer(function (req, res) { + var credentials = auth(req) if (credentials.name === 'test' && credentials.pass === 'test') { - res.end(message); + res.end(message) } else { - res.end('failed'); + res.end('failed') } - return server.close(); - }); - return server.listen(0, '127.0.0.1', function() { - var port; - port = server.address().port; - webview.addEventListener('ipc-message', function(e) { - assert.equal(e.channel, message); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/basic-auth.html?port=" + port; - webview.setAttribute('nodeintegration', 'on'); - return document.body.appendChild(webview); - }); - }); - }); - describe('dom-ready event', function() { - return it('emits when document is loaded', function(done) { - var server; - server = http.createServer(function() {}); - return server.listen(0, '127.0.0.1', function() { - var port; - port = server.address().port; - webview.addEventListener('dom-ready', function() { - return done(); - }); - webview.src = "file://" + fixtures + "/pages/dom-ready.html?port=" + port; - return document.body.appendChild(webview); - }); - }); - }); - describe('executeJavaScript', function() { - if (process.env.TRAVIS !== 'true') { - return; - } - return it('should support user gesture', function(done) { - var listener, listener2; - listener = function() { - webview.removeEventListener('enter-html-full-screen', listener); - return done(); - }; - listener2 = function() { - var jsScript; - jsScript = 'document.getElementsByTagName("video")[0].webkitRequestFullScreen()'; - webview.executeJavaScript(jsScript, true); - return webview.removeEventListener('did-finish-load', listener2); - }; - webview.addEventListener('enter-html-full-screen', listener); - webview.addEventListener('did-finish-load', listener2); - webview.src = "file://" + fixtures + "/pages/fullscreen.html"; - return document.body.appendChild(webview); - }); - }); - describe('sendInputEvent', function() { - it('can send keyboard event', function(done) { - webview.addEventListener('ipc-message', function(e) { - assert.equal(e.channel, 'keyup'); - assert.deepEqual(e.args, [67, true, false]); - return done(); - }); - webview.addEventListener('dom-ready', function() { - return webview.sendInputEvent({ + server.close() + }) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, message) + done() + }) + webview.src = 'file://' + fixtures + '/pages/basic-auth.html?port=' + port + webview.setAttribute('nodeintegration', 'on') + document.body.appendChild(webview) + }) + }) + }) + + describe('dom-ready event', function () { + it('emits when document is loaded', function (done) { + var server = http.createServer(function () {}) + server.listen(0, '127.0.0.1', function () { + var port = server.address().port + webview.addEventListener('dom-ready', function () { + done() + }) + webview.src = 'file://' + fixtures + '/pages/dom-ready.html?port=' + port + document.body.appendChild(webview) + }) + }) + + it('throws a custom error when an API method is called before the event is emitted', function () { + assert.throws(function () { + webview.stop() + }, 'Cannot call stop because the webContents is unavailable. The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.') + }) + }) + + describe('executeJavaScript', function () { + it('should support user gesture', function (done) { + if (process.env.TRAVIS !== 'true' || process.platform === 'darwin') return done() + + var listener = function () { + webview.removeEventListener('enter-html-full-screen', listener) + done() + } + var listener2 = function () { + var jsScript = "document.querySelector('video').webkitRequestFullscreen()" + webview.executeJavaScript(jsScript, true) + webview.removeEventListener('did-finish-load', listener2) + } + webview.addEventListener('enter-html-full-screen', listener) + webview.addEventListener('did-finish-load', listener2) + webview.src = 'file://' + fixtures + '/pages/fullscreen.html' + document.body.appendChild(webview) + }) + + it('can return the result of the executed script', function (done) { + if (process.env.TRAVIS === 'true' && process.platform === 'darwin') return done() + + var listener = function () { + var jsScript = "'4'+2" + webview.executeJavaScript(jsScript, false, function (result) { + assert.equal(result, '42') + done() + }) + webview.removeEventListener('did-finish-load', listener) + } + webview.addEventListener('did-finish-load', listener) + webview.src = 'about:blank' + document.body.appendChild(webview) + }) + }) + + describe('sendInputEvent', function () { + it('can send keyboard event', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, 'keyup') + assert.deepEqual(e.args, [67, true, false]) + done() + }) + webview.addEventListener('dom-ready', function () { + webview.sendInputEvent({ type: 'keyup', keyCode: 'c', modifiers: ['shift'] - }); - }); - webview.src = "file://" + fixtures + "/pages/onkeyup.html"; - webview.setAttribute('nodeintegration', 'on'); - return document.body.appendChild(webview); - }); - return it('can send mouse event', function(done) { - webview.addEventListener('ipc-message', function(e) { - assert.equal(e.channel, 'mouseup'); - assert.deepEqual(e.args, [10, 20, false, true]); - return done(); - }); - webview.addEventListener('dom-ready', function() { - return webview.sendInputEvent({ + }) + }) + webview.src = 'file://' + fixtures + '/pages/onkeyup.html' + webview.setAttribute('nodeintegration', 'on') + document.body.appendChild(webview) + }) + + it('can send mouse event', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert.equal(e.channel, 'mouseup') + assert.deepEqual(e.args, [10, 20, false, true]) + done() + }) + webview.addEventListener('dom-ready', function () { + webview.sendInputEvent({ type: 'mouseup', modifiers: ['ctrl'], x: 10, y: 20 - }); - }); - webview.src = "file://" + fixtures + "/pages/onmouseup.html"; - webview.setAttribute('nodeintegration', 'on'); - return document.body.appendChild(webview); - }); - }); - describe('media-started-playing media-paused events', function() { - return it('emits when audio starts and stops playing', function(done) { - var audioPlayed; - audioPlayed = false; - webview.addEventListener('media-started-playing', function() { - return audioPlayed = true; - }); - webview.addEventListener('media-paused', function() { - assert(audioPlayed); - return done(); - }); - webview.src = "file://" + fixtures + "/pages/audio.html"; - return document.body.appendChild(webview); - }); - }); - describe('found-in-page event', function() { - return it('emits when a request is made', function(done) { - var listener, listener2, requestId; - requestId = null; - listener = function(e) { - assert.equal(e.result.requestId, requestId); + }) + }) + webview.src = 'file://' + fixtures + '/pages/onmouseup.html' + webview.setAttribute('nodeintegration', 'on') + document.body.appendChild(webview) + }) + }) + + describe('media-started-playing media-paused events', function () { + it('emits when audio starts and stops playing', function (done) { + var audioPlayed = false + webview.addEventListener('media-started-playing', function () { + audioPlayed = true + }) + webview.addEventListener('media-paused', function () { + assert(audioPlayed) + done() + }) + webview.src = 'file://' + fixtures + '/pages/audio.html' + document.body.appendChild(webview) + }) + }) + + describe('found-in-page event', function () { + it('emits when a request is made', function (done) { + var requestId = null + var totalMatches = null + var activeMatchOrdinal = [] + var listener = function (e) { + assert.equal(e.result.requestId, requestId) if (e.result.finalUpdate) { - assert.equal(e.result.matches, 3); - webview.stopFindInPage("clearSelection"); - return done(); + assert.equal(e.result.matches, 3) + totalMatches = e.result.matches + listener2() + } else { + activeMatchOrdinal.push(e.result.activeMatchOrdinal) + if (e.result.activeMatchOrdinal === totalMatches) { + assert.deepEqual(activeMatchOrdinal, [1, 2, 3]) + webview.stopFindInPage('clearSelection') + done() + } } - }; - listener2 = function() { - return requestId = webview.findInPage("virtual"); - }; - webview.addEventListener('found-in-page', listener); - webview.addEventListener('did-finish-load', listener2); - webview.src = "file://" + fixtures + "/pages/content.html"; - return document.body.appendChild(webview); - }); - }); - return xdescribe('did-change-theme-color event', function() { - return it('emits when theme color changes', function(done) { - webview.addEventListener('did-change-theme-color', function() { - return done(); - }); - webview.src = "file://" + fixtures + "/pages/theme-color.html"; - return document.body.appendChild(webview); - }); - }); -}); + } + var listener2 = function () { + requestId = webview.findInPage('virtual') + } + webview.addEventListener('found-in-page', listener) + webview.addEventListener('did-finish-load', listener2) + webview.src = 'file://' + fixtures + '/pages/content.html' + document.body.appendChild(webview) + }) + }) + + xdescribe('did-change-theme-color event', function () { + it('emits when theme color changes', function (done) { + webview.addEventListener('did-change-theme-color', function () { + done() + }) + webview.src = 'file://' + fixtures + '/pages/theme-color.html' + document.body.appendChild(webview) + }) + }) + + describe('permission-request event', function () { + function setUpRequestHandler (webview, requested_permission) { + var listener = function (webContents, permission, callback) { + if (webContents.getId() === webview.getId()) { + assert.equal(permission, requested_permission) + callback(false) + } + } + session.fromPartition(webview.partition).setPermissionRequestHandler(listener) + } + + it('emits when using navigator.getUserMedia api', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert(e.channel, 'message') + assert(e.args, ['PermissionDeniedError']) + done() + }) + webview.src = 'file://' + fixtures + '/pages/permissions/media.html' + webview.partition = 'permissionTest' + webview.setAttribute('nodeintegration', 'on') + setUpRequestHandler(webview, 'media') + document.body.appendChild(webview) + }) + + it('emits when using navigator.geolocation api', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert(e.channel, 'message') + assert(e.args, ['ERROR(1): User denied Geolocation']) + done() + }) + webview.src = 'file://' + fixtures + '/pages/permissions/geolocation.html' + webview.partition = 'permissionTest' + webview.setAttribute('nodeintegration', 'on') + setUpRequestHandler(webview, 'geolocation') + document.body.appendChild(webview) + }) + + it('emits when using navigator.requestMIDIAccess api', function (done) { + webview.addEventListener('ipc-message', function (e) { + assert(e.channel, 'message') + assert(e.args, ['SecurityError']) + done() + }) + webview.src = 'file://' + fixtures + '/pages/permissions/midi.html' + webview.partition = 'permissionTest' + webview.setAttribute('nodeintegration', 'on') + setUpRequestHandler(webview, 'midiSysex') + document.body.appendChild(webview) + }) + }) + + describe('.getWebContents', function () { + it('can return the webcontents associated', function (done) { + webview.addEventListener('did-finish-load', function () { + const webviewContents = webview.getWebContents() + assert(webviewContents) + assert.equal(webviewContents.getURL(), 'about:blank') + done() + }) + webview.src = 'about:blank' + document.body.appendChild(webview) + }) + }) +}) diff --git a/toolchain.gypi b/toolchain.gypi index 23592d0473a..7c3cd0d0583 100644 --- a/toolchain.gypi +++ b/toolchain.gypi @@ -5,9 +5,6 @@ # Set this to true when building with Clang. 'clang%': 1, - # Path to sysroot dir. - 'sysroot%': '', - 'variables': { # The minimum OS X SDK version to use. 'mac_sdk_min%': '10.10', @@ -17,12 +14,16 @@ # Set NEON compilation flags. 'arm_neon%': 1, + + # Abosulte path to source root. + 'source_root%': '&2 + exit 1 +fi + +rewrite=`dirname $0`/rewrite_dirs.py +package=${!#} + +libdir=$root/usr/$libpath/pkgconfig:$root/usr/share/pkgconfig + +set -e +# Some sysroots, like the Chromium OS ones, may generate paths that are not +# relative to the sysroot. For example, +# /path/to/chroot/build/x86-generic/usr/lib/pkgconfig/pkg.pc may have all paths +# relative to /path/to/chroot (i.e. prefix=/build/x86-generic/usr) instead of +# relative to /path/to/chroot/build/x86-generic (i.e prefix=/usr). +# To support this correctly, it's necessary to extract the prefix to strip from +# pkg-config's |prefix| variable. +prefix=`PKG_CONFIG_LIBDIR=$libdir pkg-config --variable=prefix "$package" | sed -e 's|/usr$||'` +result=`PKG_CONFIG_LIBDIR=$libdir pkg-config "$@"` +echo "$result"| $rewrite --sysroot "$root" --strip-prefix "$prefix" diff --git a/tools/linux/rewrite_dirs.py b/tools/linux/rewrite_dirs.py new file mode 100755 index 00000000000..30f22f0cd61 --- /dev/null +++ b/tools/linux/rewrite_dirs.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# 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. + +"""Rewrites paths in -I, -L and other option to be relative to a sysroot.""" + +import sys +import os +import optparse + +REWRITE_PREFIX = ['-I', + '-idirafter', + '-imacros', + '-imultilib', + '-include', + '-iprefix', + '-iquote', + '-isystem', + '-L'] + +def RewritePath(path, opts): + """Rewrites a path by stripping the prefix and prepending the sysroot.""" + sysroot = opts.sysroot + prefix = opts.strip_prefix + if os.path.isabs(path) and not path.startswith(sysroot): + if path.startswith(prefix): + path = path[len(prefix):] + path = path.lstrip('/') + return os.path.join(sysroot, path) + else: + return path + + +def RewriteLine(line, opts): + """Rewrites all the paths in recognized options.""" + args = line.split() + count = len(args) + i = 0 + while i < count: + for prefix in REWRITE_PREFIX: + # The option can be either in the form "-I /path/to/dir" or + # "-I/path/to/dir" so handle both. + if args[i] == prefix: + i += 1 + try: + args[i] = RewritePath(args[i], opts) + except IndexError: + sys.stderr.write('Missing argument following %s\n' % prefix) + break + elif args[i].startswith(prefix): + args[i] = prefix + RewritePath(args[i][len(prefix):], opts) + i += 1 + + return ' '.join(args) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('-s', '--sysroot', default='/', help='sysroot to prepend') + parser.add_option('-p', '--strip-prefix', default='', help='prefix to strip') + opts, args = parser.parse_args(argv[1:]) + + for line in sys.stdin.readlines(): + line = RewriteLine(line.strip(), opts) + print line + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tools/win/register_msdia80_dll.js b/tools/win/register_msdia80_dll.js index 5691ef9caf5..e90b9714d1f 100644 --- a/tools/win/register_msdia80_dll.js +++ b/tools/win/register_msdia80_dll.js @@ -1,12 +1,12 @@ -var fs = require('fs'); -var path = require('path'); -var runas = require('runas'); +var fs = require('fs') +var path = require('path') +var runas = require('runas') -var source = path.resolve(__dirname, '..', '..', 'vendor', 'breakpad', 'msdia80.dll'); -var target = 'C:\\Program Files\\Common Files\\Microsoft Shared\\VC\\msdia80.dll'; -if (fs.existsSync(target)) - return; +var source = path.resolve(__dirname, '..', '..', 'vendor', 'breakpad', 'msdia80.dll') +var target = 'C:\\Program Files\\Common Files\\Microsoft Shared\\VC\\msdia80.dll' -runas('cmd', - ['/K', 'copy', source, target, '&', 'regsvr32', '/s', target, '&', 'exit'], - {admin: true}); +if (!fs.existsSync(target)) { + runas('cmd', + ['/K', 'copy', source, target, '&', 'regsvr32', '/s', target, '&', 'exit'], + {admin: true}) +} diff --git a/vendor/brightray b/vendor/brightray index 3edbdfdf520..242feb1c817 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit 3edbdfdf52015309df78fb586143c833e3c01b25 +Subproject commit 242feb1c817565de6592e9e672c136635bfff453 diff --git a/vendor/crashpad b/vendor/crashpad index 5b777419c30..db713da7554 160000 --- a/vendor/crashpad +++ b/vendor/crashpad @@ -1 +1 @@ -Subproject commit 5b777419c303d8aa7930239d8ef755475f1ede57 +Subproject commit db713da7554f565e43c6dcf9a51b59ccc4f06066 diff --git a/vendor/native_mate b/vendor/native_mate index a3dcf8ced66..553326b0069 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit a3dcf8ced663e974ac94ad5e50a1d25a43995a9d +Subproject commit 553326b00696fcda106a8866872a8f2ad6caff0d diff --git a/vendor/node b/vendor/node index 3b044608ee5..d8e7d3e76cb 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit 3b044608ee51ca001dabe944cade6e64f46b0724 +Subproject commit d8e7d3e76cb3c6e709449d181ddc2af8c4859303