diff --git a/.gitignore b/.gitignore index b18292e6ab0f..0c6f4cb79dd0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,13 @@ /external_binaries/ /out/ /vendor/brightray/vendor/download/ +/vendor/debian_wheezy_arm-sysroot/ +/vendor/debian_wheezy_i386-sysroot/ /vendor/python_26/ /vendor/npm/ +/vendor/llvm/ +/vendor/llvm-build/ +/vendor/.gclient node_modules/ *.xcodeproj *.swp diff --git a/.gitmodules b/.gitmodules index 2a1f087e7446..c6e53ed39a69 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,12 @@ [submodule "vendor/native_mate"] path = vendor/native_mate url = https://github.com/zcbenz/native-mate.git +[submodule "vendor/crashpad"] + path = vendor/crashpad + url = https://github.com/atom/crashpad.git +[submodule "vendor/requests"] + path = vendor/requests + url = https://github.com/kennethreitz/requests +[submodule "vendor/boto"] + path = vendor/boto + url = https://github.com/boto/boto.git diff --git a/.travis.yml b/.travis.yml index 2d64c38e8484..4d4cd5dde9f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,28 @@ +git: + depth: 10 +notifications: + email: false + language: cpp compiler: clang os: - linux - osx +env: + - TARGET_ARCH=x64 -notifications: - email: - on_success: never - on_failure: change +matrix: + include: + - os: linux + env: TARGET_ARCH=arm + - os: linux + env: TARGET_ARCH=ia32 + allow_failures: + - env: TARGET_ARCH=arm + - env: TARGET_ARCH=ia32 script: './script/cibuild' -git: - depth: 10 +branches: + only: + - master diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000000..6ca3ea5d2f32 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,75 @@ +# Contributing to Electron + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0). +By participating, you are expected to uphold this code. Please report unacceptable behavior to atom@github.com. + +The following is a set of guidelines for contributing to Electron. +These are just guidelines, not rules, use your best judgment and feel free to +propose changes to this document in a pull request. + +## Submitting Issues + +* You can create an issue [here](https://github.com/atom/electron/issues/new), +but before doing that please read the notes below and include as many details as +possible with your report. If you can, please include: + * The version of Electron you are using + * The operating system you are using + * If applicable, what you were doing when the issue arose and what you + expected to happen +* Other things that will help resolve your issue: + * Screenshots and animated GIFs + * Error output that appears in your terminal, dev tools or as an alert + * Perform a [cursory search](https://github.com/atom/electron/issues?utf8=✓&q=is%3Aissue+) + to see if a similar issue has already been submitted + +## 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). +* 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). + +## Styleguides + +### General Code + +* End files with a newline. +* Place requires in the following order: + * Built in Node Modules (such as `path`) + * Built in Electron Modules (such as `ipc`, `app`) + * Local Modules (using relative paths) +* Place class properties in the following order: + * Class methods and properties (methods starting with a `@`) + * Instance methods and properties +* Avoid platform-dependent code: + * Use `path.join()` to concatenate filenames. + * Use `os.tmpdir()` rather than `/tmp` when you need to reference the + temporary directory. +* Using a plain `return` when returning explicitly at the end of a function. + * Not `return null`, `return undefined`, `null`, or `undefined` + +### Git Commit Messages + +* Use the present tense ("Add feature" not "Added feature") +* Use the imperative mood ("Move cursor to..." not "Moves cursor to...") +* Limit the first line to 72 characters or less +* Reference issues and pull requests liberally +* Consider starting the commit message with an applicable emoji: + * :art: `:art:` when improving the format/structure of the code + * :racehorse: `:racehorse:` when improving performance + * :non-potable_water: `:non-potable_water:` when plugging memory leaks + * :memo: `:memo:` when writing docs + * :penguin: `:penguin:` when fixing something on Linux + * :apple: `:apple:` when fixing something on Mac OS + * :checkered_flag: `:checkered_flag:` when fixing something on Windows + * :bug: `:bug:` when fixing a bug + * :fire: `:fire:` when removing code or files + * :green_heart: `:green_heart:` when fixing the CI build + * :white_check_mark: `:white_check_mark:` when adding tests + * :lock: `:lock:` when dealing with security + * :arrow_up: `:arrow_up:` when upgrading dependencies + * :arrow_down: `:arrow_down:` when downgrading dependencies + * :shirt: `:shirt:` when removing linter warnings diff --git a/README-ko.md b/README-ko.md new file mode 100644 index 000000000000..83634370eba9 --- /dev/null +++ b/README-ko.md @@ -0,0 +1,58 @@ +[![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) +[![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/) + +### [Electron](https://github.com/atom/electron/) 한국어 참조문서 + +:zap: *프레임워크 이름이 Atom Shell에서 Electron으로 변경되었습니다* :zap: + +Electron 프레임워크는 JavaScript, HTML 그리고 CSS를 사용하여 Cross-Platform 데스크톱 어플리케이션을 개발할 수 있도록 해주는 프레임워크입니다. 이 프레임워크는 [io.js](http://iojs.org) 와 +[Chromium](http://www.chromium.org)을 기반으로 만들어 졌으며 [Atom Editor](https://github.com/atom/atom)에 사용되고 있습니다. + +Electron에 대한 중요한 알림을 받고 싶다면 Twitter에서 [@ElectronJS](https://twitter.com/electronjs)를 팔로우 하세요. + +이 프로젝트는 기여자 규약 1.2를 준수합니다. 이 프로젝트에 참여할 때 코드를 유지해야 합니다. 받아들일 수 없는 행동은 atom@github.com로 보고 하십시오. + +## 다운로드 + +Linux, Windows, Mac용으로 미리 빌드된 Electron 바이너리와 디버그 심볼이 준비되어 있습니다. [releases](https://github.com/atom/electron/releases) 페이지에서 받아 볼 수 있습니다. + +또한 [`npm`](https://docs.npmjs.com/)을 통해 미리 빌드된 Electron 바이너리를 받을 수도 있습니다: + +```sh +# $PATH에 `electron` 커맨드를 등록하고 전역에 설치합니다. +npm install electron-prebuilt -g + +# 개발 의존성 모듈 형태로 설치합니다. +npm install electron-prebuilt --save-dev +``` + +### 미러 + +- [China](https://npm.taobao.org/mirrors/electron) + +## 참조 문서 + +[Docs](https://github.com/atom/electron/tree/master/docs/README.md)에 개발 가이드와 API 레퍼런스가 있습니다. +Electron을 빌드 하는 방법과 프로젝트에 기여하는 방법도 문서에 포함되어 있으니 참고하시기 바랍니다. + +## 참조 문서 (번역) + +- [브라질 포르투칼어](https://github.com/atom/electron/tree/master/docs-translations/pt-BR) +- [한국어](https://github.com/atom/electron/tree/master/docs-translations/ko) +- [일본어](https://github.com/atom/electron/tree/master/docs-translations/jp) +- [스페인어](https://github.com/atom/electron/tree/master/docs-translations/es) +- [중국어 간체](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) +- [중국어 번체](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) + +## 커뮤니티 + +다음 링크를 통해 커뮤니티에 질문을 올리거나 토론을 나누실 수 있습니다: + +- Atom 포럼의 [`electron`](http://discuss.atom.io/category/electron) 카테고리 +- Freenode 채팅의 `#atom-shell` 채널 +- Slack의 [`Atom`](http://atom-slack.herokuapp.com/) 채널 + +[awesome-electron](https://github.com/sindresorhus/awesome-electron) 프로젝트엔 커뮤니티가 운영중인 유용한 예제 어플리케이션과 도구, 리소스가 있으니 한번 참고해 보시기 바랍니다. diff --git a/README.md b/README.md index c3f7461c5e7e..54b6af5fa3fc 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ [![Build Status](https://travis-ci.org/atom/electron.svg?branch=master)](https://travis-ci.org/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/) -:zap: *formerly known as Atom Shell* :zap: +:zap: *Formerly known as Atom Shell* :zap: The Electron framework lets you write cross-platform desktop applications using JavaScript, HTML and CSS. It is based on [io.js](http://iojs.org) and @@ -13,6 +14,10 @@ editor](https://github.com/atom/atom). Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important announcements. +This project adheres to the [Contributor Covenant 1.2](http://contributor-covenant.org/version/1/2/0). +By participating, you are expected to uphold this code. Please report +unacceptable behavior to atom@github.com. + ## Downloads Prebuilt binaries and debug symbols of Electron for Linux, Windows and Mac can @@ -39,7 +44,23 @@ Guides and the API reference are located in the [docs](https://github.com/atom/electron/tree/master/docs) directory. It also contains documents describing how to build and contribute to Electron. +## Documentation Translations + +- [Brazilian Portuguese](https://github.com/atom/electron/tree/master/docs-translations/pt-BR) +- [Korean](https://github.com/atom/electron/tree/master/docs-translations/ko) +- [Japanese](https://github.com/atom/electron/tree/master/docs-translations/jp) +- [Spanish](https://github.com/atom/electron/tree/master/docs-translations/es) +- [Simplified Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-CN) +- [Traditional Chinese](https://github.com/atom/electron/tree/master/docs-translations/zh-TW) + ## Community -There is an [`electron` category on the Atom forums](http://discuss.atom.io/category/electron) -as well as an `#atom-shell` channel on Freenode. +You can ask questions and interact with the community in the following +locations: +- [`electron`](http://discuss.atom.io/category/electron) category on the Atom +forums +- `#atom-shell` channel on Freenode +- [`Atom`](http://atom-slack.herokuapp.com/) channel on Slack + +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.gyp b/atom.gyp index 9ae9a90a122b..c9d5f924789f 100644 --- a/atom.gyp +++ b/atom.gyp @@ -4,9 +4,7 @@ 'product_name%': 'Electron', 'company_name%': 'GitHub, Inc', 'company_abbr%': 'github', - 'version%': '0.26.0', - - 'atom_source_root': ' #include +#include "atom/common/atom_version.h" #include "atom/common/chrome_version.h" #include "atom/common/options_switches.h" #include "base/command_line.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" #include "content/public/common/content_constants.h" #include "content/public/common/pepper_plugin_info.h" +#include "content/public/common/user_agent.h" #include "ppapi/shared_impl/ppapi_permissions.h" namespace atom { @@ -26,8 +30,24 @@ content::PepperPluginInfo CreatePepperFlashInfo(const base::FilePath& path, plugin.name = content::kFlashPluginName; plugin.path = path; plugin.permissions = ppapi::PERMISSION_ALL_BITS; - plugin.version = version; + std::vector flash_version_numbers; + base::SplitString(version, '.', &flash_version_numbers); + if (flash_version_numbers.size() < 1) + flash_version_numbers.push_back("11"); + // |SplitString()| puts in an empty string given an empty string. :( + else if (flash_version_numbers[0].empty()) + flash_version_numbers[0] = "11"; + if (flash_version_numbers.size() < 2) + flash_version_numbers.push_back("2"); + if (flash_version_numbers.size() < 3) + flash_version_numbers.push_back("999"); + if (flash_version_numbers.size() < 4) + flash_version_numbers.push_back("999"); + // E.g., "Shockwave Flash 10.2 r154": + plugin.description = plugin.name + " " + flash_version_numbers[0] + "." + + flash_version_numbers[1] + " r" + flash_version_numbers[2]; + plugin.version = JoinString(flash_version_numbers, '.'); content::WebPluginMimeType swf_mime_type( content::kFlashPluginSwfMimeType, content::kFlashPluginSwfExtension, @@ -54,9 +74,25 @@ std::string AtomContentClient::GetProduct() const { return "Chrome/" CHROME_VERSION_STRING; } +std::string AtomContentClient::GetUserAgent() const { + return content::BuildUserAgentFromProduct( + "Chrome/" CHROME_VERSION_STRING " " + ATOM_PRODUCT_NAME "/" ATOM_VERSION_STRING); +} + void AtomContentClient::AddAdditionalSchemes( std::vector* standard_schemes, std::vector* savable_schemes) { + auto command_line = base::CommandLine::ForCurrentProcess(); + auto custom_schemes = command_line->GetSwitchValueASCII( + switches::kRegisterStandardSchemes); + if (!custom_schemes.empty()) { + std::vector schemes; + base::SplitString(custom_schemes, ',', &schemes); + standard_schemes->insert(standard_schemes->end(), + schemes.begin(), + schemes.end()); + } standard_schemes->push_back("chrome-extension"); } diff --git a/atom/app/atom_content_client.h b/atom/app/atom_content_client.h index 279ec7179f49..a6b2f73e7faa 100644 --- a/atom/app/atom_content_client.h +++ b/atom/app/atom_content_client.h @@ -20,6 +20,7 @@ class AtomContentClient : public brightray::ContentClient { protected: // content::ContentClient: std::string GetProduct() const override; + std::string GetUserAgent() const override; void AddAdditionalSchemes( std::vector* standard_schemes, std::vector* savable_schemes) override; diff --git a/atom/app/atom_library_main.mm b/atom/app/atom_library_main.mm index 5cf910b2369a..885500beb5d3 100644 --- a/atom/app/atom_library_main.mm +++ b/atom/app/atom_library_main.mm @@ -4,9 +4,9 @@ #include "atom/app/atom_library_main.h" -#include "atom/app/atom_main_args.h" #include "atom/app/atom_main_delegate.h" #include "atom/app/node_main.h" +#include "atom/common/atom_command_line.h" #include "base/at_exit.h" #include "base/i18n/icu_util.h" #include "base/mac/bundle_locations.h" diff --git a/atom/app/atom_main.cc b/atom/app/atom_main.cc index 02dc985e30c4..26dcb9421266 100644 --- a/atom/app/atom_main.cc +++ b/atom/app/atom_main.cc @@ -3,7 +3,6 @@ // found in the LICENSE file. #include "atom/app/atom_main.h" -#include "atom/app/atom_main_args.h" #include #include @@ -33,6 +32,8 @@ #endif // defined(OS_MACOSX) #include "atom/app/node_main.h" +#include "atom/common/atom_command_line.h" +#include "base/at_exit.h" #include "base/i18n/icu_util.h" #if defined(OS_WIN) @@ -134,6 +135,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) { if (env->GetVar("ATOM_SHELL_INTERNAL_RUN_AS_NODE", &node_indicator) && node_indicator == "1") { // Now that argv conversion is done, we can finally start. + base::AtExitManager atexit_manager; base::i18n::InitializeICU(); return atom::NodeMain(argc, argv); } else if (env->GetVar("ATOM_SHELL_INTERNAL_CRASH_SERVICE", @@ -165,6 +167,7 @@ int main(int argc, const char* argv[]) { char* node_indicator = getenv("ATOM_SHELL_INTERNAL_RUN_AS_NODE"); if (node_indicator != NULL && strcmp(node_indicator, "1") == 0) { base::i18n::InitializeICU(); + base::AtExitManager atexit_manager; return atom::NodeMain(argc, const_cast(argv)); } diff --git a/atom/app/atom_main_delegate.cc b/atom/app/atom_main_delegate.cc index c9cdc77e1173..3c7d6b2e7034 100644 --- a/atom/app/atom_main_delegate.cc +++ b/atom/app/atom_main_delegate.cc @@ -38,7 +38,9 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) { #else settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; #endif // defined(DEBUG) -#endif // defined(OS_WIN) +#else // defined(OS_WIN) + settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; +#endif // !defined(OS_WIN) logging::InitLogging(settings); // Logging with pid and timestamp. @@ -64,6 +66,10 @@ void AtomMainDelegate::PreSandboxStartup() { std::string process_type = command_line->GetSwitchValueASCII( switches::kProcessType); + if (process_type == switches::kUtilityProcess) { + AtomContentUtilityClient::PreSandboxStartup(); + } + // Only append arguments for browser process. if (!process_type.empty()) return; @@ -78,6 +84,9 @@ void AtomMainDelegate::PreSandboxStartup() { // Disable renderer sandbox for most of node's functions. command_line->AppendSwitch(switches::kNoSandbox); + // Allow file:// URIs to read other file:// URIs by default. + command_line->AppendSwitch(switches::kAllowFileAccessFromFiles); + #if defined(OS_MACOSX) // Enable AVFoundation. command_line->AppendSwitch("enable-avfoundation"); diff --git a/atom/app/node_main.cc b/atom/app/node_main.cc index 7446a0a71106..7efb1b40eece 100644 --- a/atom/app/node_main.cc +++ b/atom/app/node_main.cc @@ -4,14 +4,21 @@ #include "atom/app/node_main.h" +#include "atom/app/uv_task_runner.h" #include "atom/browser/javascript_environment.h" +#include "atom/browser/node_debugger.h" #include "atom/common/node_includes.h" +#include "base/command_line.h" +#include "base/thread_task_runner_handle.h" #include "gin/array_buffer.h" #include "gin/public/isolate_holder.h" +#include "gin/v8_initializer.h" namespace atom { int NodeMain(int argc, char *argv[]) { + base::CommandLine::Init(argc, argv); + argv = uv_setup_args(argc, argv); int exec_argc; const char** exec_argv; @@ -19,27 +26,29 @@ int NodeMain(int argc, char *argv[]) { int exit_code = 1; { - gin::IsolateHolder::LoadV8Snapshot(); + // Feed gin::PerIsolateData with a task runner. + uv_loop_t* loop = uv_default_loop(); + scoped_refptr uv_task_runner(new UvTaskRunner(loop)); + base::ThreadTaskRunnerHandle handle(uv_task_runner); + + gin::V8Initializer::LoadV8Snapshot(); + gin::V8Initializer::LoadV8Natives(); gin::IsolateHolder::Initialize( gin::IsolateHolder::kNonStrictMode, gin::ArrayBufferAllocator::SharedInstance()); JavascriptEnvironment gin_env; node::Environment* env = node::CreateEnvironment( - gin_env.isolate(), uv_default_loop(), gin_env.context(), argc, argv, + gin_env.isolate(), loop, gin_env.context(), argc, argv, exec_argc, exec_argv); - // Start debugger. - node::node_isolate = gin_env.isolate(); - if (node::use_debug_agent) - node::StartDebug(env, node::debug_wait_connect); + // Start our custom debugger implementation. + NodeDebugger node_debugger(gin_env.isolate()); + if (node_debugger.IsRunning()) + env->AssignToContext(v8::Debug::GetDebugContext()); node::LoadEnvironment(env); - // Enable debugger. - if (node::use_debug_agent) - node::EnableDebug(env); - bool more; do { more = uv_run(env->event_loop(), UV_RUN_ONCE); diff --git a/atom/app/uv_task_runner.cc b/atom/app/uv_task_runner.cc new file mode 100644 index 000000000000..23463e44e2ef --- /dev/null +++ b/atom/app/uv_task_runner.cc @@ -0,0 +1,55 @@ +// 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 "atom/app/uv_task_runner.h" + +#include "base/stl_util.h" + +namespace atom { + +UvTaskRunner::UvTaskRunner(uv_loop_t* loop) : loop_(loop) { +} + +UvTaskRunner::~UvTaskRunner() { + for (auto& iter : tasks_) { + uv_unref(reinterpret_cast(iter.first)); + delete iter.first; + } +} + +bool UvTaskRunner::PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + uv_timer_t* timer = new uv_timer_t; + timer->data = this; + uv_timer_init(loop_, timer); + uv_timer_start(timer, UvTaskRunner::OnTimeout, delay.InMilliseconds(), 0); + tasks_[timer] = task; + return true; +} + +bool UvTaskRunner::RunsTasksOnCurrentThread() const { + return true; +} + +bool UvTaskRunner::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + return PostDelayedTask(from_here, task, delay);; +} + +// static +void UvTaskRunner::OnTimeout(uv_timer_t* timer) { + UvTaskRunner* self = static_cast(timer->data); + if (!ContainsKey(self->tasks_, timer)) + return; + + self->tasks_[timer].Run(); + self->tasks_.erase(timer); + uv_unref(reinterpret_cast(timer)); + delete timer; +} + +} // namespace atom diff --git a/atom/app/uv_task_runner.h b/atom/app/uv_task_runner.h new file mode 100644 index 000000000000..e4eac0155354 --- /dev/null +++ b/atom/app/uv_task_runner.h @@ -0,0 +1,44 @@ +// 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_APP_UV_TASK_RUNNER_H_ +#define ATOM_APP_UV_TASK_RUNNER_H_ + +#include + +#include "base/callback.h" +#include "base/single_thread_task_runner.h" +#include "vendor/node/deps/uv/include/uv.h" + +namespace atom { + +// TaskRunner implementation that posts tasks into libuv's default loop. +class UvTaskRunner : public base::SingleThreadTaskRunner { + public: + explicit UvTaskRunner(uv_loop_t* loop); + ~UvTaskRunner() override; + + // base::SingleThreadTaskRunner: + bool PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) override; + bool RunsTasksOnCurrentThread() const override; + bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) override; + + private: + static void OnTimeout(uv_timer_t* timer); + + uv_loop_t* loop_; + + std::map tasks_; + + DISALLOW_COPY_AND_ASSIGN(UvTaskRunner); +}; + +} // namespace atom + +#endif // ATOM_APP_UV_TASK_RUNNER_H_ diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index d3b04f043622..2e7596971f48 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -12,29 +12,30 @@ #endif #include "atom/browser/api/atom_api_menu.h" +#include "atom/browser/api/atom_api_session.h" #include "atom/browser/atom_browser_context.h" +#include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/browser.h" +#include "atom/browser/api/atom_api_web_contents.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" -#include "atom/common/native_mate_converters/gurl_converter.h" +#include "atom/common/node_includes.h" #include "base/command_line.h" #include "base/environment.h" #include "base/files/file_path.h" #include "base/path_service.h" #include "brightray/browser/brightray_paths.h" -#include "native_mate/callback.h" +#include "content/public/browser/client_certificate_delegate.h" +#include "content/public/browser/gpu_data_manager.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" -#include "net/base/load_flags.h" -#include "net/proxy/proxy_service.h" -#include "net/url_request/url_request_context.h" -#include "net/url_request/url_request_context_getter.h" +#include "net/ssl/ssl_cert_request_info.h" +#include "ui/base/l10n/l10n_util.h" #if defined(OS_WIN) #include "base/strings/utf_string_conversions.h" #endif -#include "atom/common/node_includes.h" - using atom::Browser; namespace mate { @@ -42,7 +43,7 @@ namespace mate { #if defined(OS_WIN) template<> struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Handle val, + static bool FromV8(v8::Isolate* isolate, v8::Local val, Browser::UserTask* out) { mate::Dictionary dict; if (!ConvertFromV8(isolate, val, &dict)) @@ -60,6 +61,21 @@ struct Converter { }; #endif +template<> +struct Converter> { + static v8::Local ToV8( + v8::Isolate* isolate, + const scoped_refptr& val) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + std::string encoded_data; + net::X509Certificate::GetPEMEncoded( + val->os_cert_handle(), &encoded_data); + dict.Set("data", encoded_data); + dict.Set("issuerName", val->issuer().GetDisplayName()); + return dict.GetHandle(); + } +}; + } // namespace mate @@ -93,50 +109,39 @@ int GetPathConstant(const std::string& name) { return -1; } -class ResolveProxyHelper { - public: - ResolveProxyHelper(const GURL& url, App::ResolveProxyCallback callback) - : callback_(callback) { - net::ProxyService* proxy_service = AtomBrowserContext::Get()-> - url_request_context_getter()->GetURLRequestContext()->proxy_service(); - - // Start the request. - int result = proxy_service->ResolveProxy( - url, net::LOAD_NORMAL, &proxy_info_, - base::Bind(&ResolveProxyHelper::OnResolveProxyCompleted, - base::Unretained(this)), - &pac_req_, nullptr, net::BoundNetLog()); - - // Completed synchronously. - if (result != net::ERR_IO_PENDING) - OnResolveProxyCompleted(result); +void OnClientCertificateSelected( + v8::Isolate* isolate, + std::shared_ptr delegate, + mate::Arguments* args) { + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + mate::Dictionary cert_data; + if (!(args->Length() == 1 && args->GetNext(&cert_data))) { + args->ThrowError(); + return; } - void OnResolveProxyCompleted(int result) { - std::string proxy; - if (result == net::OK) - proxy = proxy_info_.ToPacString(); - callback_.Run(proxy); + std::string encoded_data; + cert_data.Get("data", &encoded_data); - delete this; - } + auto certs = + net::X509Certificate::CreateCertificateListFromBytes( + encoded_data.data(), encoded_data.size(), + net::X509Certificate::FORMAT_AUTO); - private: - App::ResolveProxyCallback callback_; - net::ProxyInfo proxy_info_; - net::ProxyService::PacRequest* pac_req_; - - DISALLOW_COPY_AND_ASSIGN(ResolveProxyHelper); -}; + delegate->ContinueWithCertificate(certs[0].get()); +} } // namespace App::App() { Browser::Get()->AddObserver(this); + content::GpuDataManager::GetInstance()->AddObserver(this); } App::~App() { Browser::Get()->RemoveObserver(this); + content::GpuDataManager::GetInstance()->RemoveObserver(this); } void App::OnBeforeQuit(bool* prevent_default) { @@ -163,8 +168,8 @@ void App::OnOpenURL(const std::string& url) { Emit("open-url", url); } -void App::OnActivateWithNoOpenWindows() { - Emit("activate-with-no-open-windows"); +void App::OnActivate(bool has_visible_windows) { + Emit("activate", has_visible_windows); } void App::OnWillFinishLaunching() { @@ -172,9 +177,42 @@ void App::OnWillFinishLaunching() { } void App::OnFinishLaunching() { + // Create the defaultSession. + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + auto browser_context = static_cast( + AtomBrowserMainParts::Get()->browser_context()); + auto handle = Session::CreateFrom(isolate(), browser_context); + default_session_.Reset(isolate(), handle.ToV8()); + Emit("ready"); } +void App::OnSelectCertificate( + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + scoped_ptr delegate) { + std::shared_ptr + shared_delegate(delegate.release()); + bool prevent_default = + Emit("select-certificate", + api::WebContents::CreateFrom(isolate(), web_contents), + cert_request_info->host_and_port.ToString(), + cert_request_info->client_certs, + base::Bind(&OnClientCertificateSelected, + isolate(), + shared_delegate)); + + // Default to first certificate from the platform store. + if (!prevent_default) + shared_delegate->ContinueWithCertificate( + cert_request_info->client_certs[0].get()); +} + +void App::OnGpuProcessCrashed(base::TerminationStatus exit_code) { + Emit("gpu-process-crashed"); +} + base::FilePath App::GetPath(mate::Arguments* args, const std::string& name) { bool succeed = false; base::FilePath path; @@ -197,10 +235,6 @@ void App::SetPath(mate::Arguments* args, args->ThrowError("Failed to set path"); } -void App::ResolveProxy(const GURL& url, ResolveProxyCallback callback) { - new ResolveProxyHelper(url, callback); -} - void App::SetDesktopName(const std::string& desktop_name) { #if defined(OS_LINUX) scoped_ptr env(base::Environment::Create()); @@ -215,6 +249,17 @@ void App::SetAppUserModelId(const std::string& app_id) { #endif } +std::string App::GetLocale() { + return l10n_util::GetApplicationLocale(""); +} + +v8::Local App::DefaultSession(v8::Isolate* isolate) { + if (default_session_.IsEmpty()) + return v8::Null(isolate); + else + return v8::Local::New(isolate, default_session_); +} + mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( v8::Isolate* isolate) { auto browser = base::Unretained(Browser::Get()); @@ -236,9 +281,10 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( #endif .SetMethod("setPath", &App::SetPath) .SetMethod("getPath", &App::GetPath) - .SetMethod("resolveProxy", &App::ResolveProxy) .SetMethod("setDesktopName", &App::SetDesktopName) - .SetMethod("setAppUserModelId", &App::SetAppUserModelId); + .SetMethod("setAppUserModelId", &App::SetAppUserModelId) + .SetMethod("getLocale", &App::GetLocale) + .SetProperty("defaultSession", &App::DefaultSession); } // static @@ -277,8 +323,8 @@ void DockSetMenu(atom::api::Menu* menu) { } #endif -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); auto command_line = base::CommandLine::ForCurrentProcess(); diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index c2f9212b7ca7..4896a5f066a1 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -9,11 +9,9 @@ #include "atom/browser/api/event_emitter.h" #include "atom/browser/browser_observer.h" -#include "base/callback.h" +#include "content/public/browser/gpu_data_manager_observer.h" #include "native_mate/handle.h" -class GURL; - namespace base { class FilePath; } @@ -27,10 +25,9 @@ namespace atom { namespace api { class App : public mate::EventEmitter, - public BrowserObserver { + public BrowserObserver, + public content::GpuDataManagerObserver { public: - typedef base::Callback ResolveProxyCallback; - static mate::Handle Create(v8::Isolate* isolate); protected: @@ -44,9 +41,16 @@ class App : public mate::EventEmitter, void OnQuit() override; void OnOpenFile(bool* prevent_default, const std::string& file_path) override; void OnOpenURL(const std::string& url) override; - void OnActivateWithNoOpenWindows() override; + void OnActivate(bool has_visible_windows) override; void OnWillFinishLaunching() override; void OnFinishLaunching() override; + void OnSelectCertificate( + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + scoped_ptr delegate) override; + + // content::GpuDataManagerObserver: + void OnGpuProcessCrashed(base::TerminationStatus exit_code) override; // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( @@ -59,9 +63,12 @@ class App : public mate::EventEmitter, const std::string& name, const base::FilePath& path); - void ResolveProxy(const GURL& url, ResolveProxyCallback callback); void SetDesktopName(const std::string& desktop_name); void SetAppUserModelId(const std::string& app_id); + std::string GetLocale(); + v8::Local DefaultSession(v8::Isolate* isolate); + + v8::Global default_session_; DISALLOW_COPY_AND_ASSIGN(App); }; diff --git a/atom/browser/api/atom_api_auto_updater.cc b/atom/browser/api/atom_api_auto_updater.cc index bf52c310b72a..250aa3e45f64 100644 --- a/atom/browser/api/atom_api_auto_updater.cc +++ b/atom/browser/api/atom_api_auto_updater.cc @@ -7,11 +7,10 @@ #include "base/time/time.h" #include "atom/browser/auto_updater.h" #include "atom/browser/browser.h" +#include "atom/common/node_includes.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" -#include "atom/common/node_includes.h" - namespace atom { namespace api { @@ -77,8 +76,8 @@ mate::Handle AutoUpdater::Create(v8::Isolate* isolate) { namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.Set("autoUpdater", atom::api::AutoUpdater::Create(isolate)); diff --git a/atom/browser/api/atom_api_content_tracing.cc b/atom/browser/api/atom_api_content_tracing.cc index a3299e447a8f..f29946d1f462 100644 --- a/atom/browser/api/atom_api_content_tracing.cc +++ b/atom/browser/api/atom_api_content_tracing.cc @@ -5,40 +5,32 @@ #include #include +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" -#include "base/bind.h" -#include "content/public/browser/tracing_controller.h" -#include "native_mate/callback.h" -#include "native_mate/dictionary.h" - #include "atom/common/node_includes.h" +#include "base/bind.h" +#include "base/files/file_util.h" +#include "content/public/browser/tracing_controller.h" +#include "native_mate/dictionary.h" using content::TracingController; namespace mate { template<> -struct Converter { +struct Converter { static bool FromV8(v8::Isolate* isolate, - v8::Handle val, - base::trace_event::CategoryFilter* out) { - std::string filter; - if (!ConvertFromV8(isolate, val, &filter)) - return false; - *out = base::trace_event::CategoryFilter(filter); - return true; - } -}; - -template<> -struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Handle val, - base::trace_event::TraceOptions* out) { - std::string options; + v8::Local val, + base::trace_event::TraceConfig* out) { + Dictionary options; if (!ConvertFromV8(isolate, val, &options)) return false; - return out->SetFromString(options); + std::string category_filter, trace_options; + if (!options.Get("categoryFilter", &category_filter) || + !options.Get("traceOptions", &trace_options)) + return false; + *out = base::trace_event::TraceConfig(category_filter, trace_options); + return true; } }; @@ -46,22 +38,45 @@ struct Converter { namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +using CompletionCallback = base::Callback; + +scoped_refptr GetTraceDataSink( + const base::FilePath& path, const CompletionCallback& callback) { + base::FilePath result_file_path = path; + if (result_file_path.empty() && !base::CreateTemporaryFile(&result_file_path)) + LOG(ERROR) << "Creating temporary file failed"; + + return TracingController::CreateFileSink(result_file_path, + base::Bind(callback, + result_file_path)); +} + +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( + GetTraceDataSink(path, callback)); +} + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { auto controller = base::Unretained(TracingController::GetInstance()); mate::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("getCategories", base::Bind( &TracingController::GetCategories, controller)); dict.SetMethod("startRecording", base::Bind( &TracingController::EnableRecording, controller)); - dict.SetMethod("stopRecording", base::Bind( - &TracingController::DisableRecording, controller, nullptr)); + dict.SetMethod("stopRecording", &StopRecording); dict.SetMethod("startMonitoring", base::Bind( &TracingController::EnableMonitoring, controller)); dict.SetMethod("stopMonitoring", base::Bind( &TracingController::DisableMonitoring, controller)); - dict.SetMethod("captureMonitoringSnapshot", base::Bind( - &TracingController::CaptureMonitoringSnapshot, controller, nullptr)); + 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 new file mode 100644 index 000000000000..4f989eff7275 --- /dev/null +++ b/atom/browser/api/atom_api_cookies.cc @@ -0,0 +1,346 @@ +// 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 "atom/browser/api/atom_api_cookies.h" + +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/gurl_converter.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "base/bind.h" +#include "base/time/time.h" +#include "base/values.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "native_mate/dictionary.h" +#include "native_mate/object_template_builder.h" +#include "net/cookies/cookie_monster.h" +#include "net/cookies/cookie_store.h" +#include "net/cookies/cookie_util.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" + +using atom::api::Cookies; +using content::BrowserThread; + +namespace { + +bool GetCookieListFromStore( + net::CookieStore* cookie_store, + const std::string& url, + const net::CookieMonster::GetCookieListCallback& callback) { + DCHECK(cookie_store); + GURL gurl(url); + net::CookieMonster* monster = cookie_store->GetCookieMonster(); + // Empty url will match all url cookies. + if (url.empty()) { + monster->GetAllCookiesAsync(callback); + return true; + } + + if (!gurl.is_valid()) + return false; + + monster->GetAllCookiesForURLAsync(gurl, callback); + return true; +} + +void RunGetCookiesCallbackOnUIThread(v8::Isolate* isolate, + const std::string& error_message, + const net::CookieList& cookie_list, + const Cookies::CookiesCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + + if (!error_message.empty()) { + v8::Local error = mate::ConvertToV8(isolate, error_message); + callback.Run(error, v8::Null(isolate)); + return; + } + callback.Run(v8::Null(isolate), mate::ConvertToV8(isolate, cookie_list)); +} + +void RunRemoveCookiesCallbackOnUIThread( + v8::Isolate* isolate, + const std::string& error_message, + const Cookies::CookiesCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + + if (!error_message.empty()) { + v8::Local error = mate::ConvertToV8(isolate, error_message); + callback.Run(error, v8::Null(isolate)); + return; + } + + callback.Run(v8::Null(isolate), v8::Null(isolate)); +} + +void RunSetCookiesCallbackOnUIThread(v8::Isolate* isolate, + const std::string& error_message, + bool set_success, + const Cookies::CookiesCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + + if (!error_message.empty()) { + v8::Local error = mate::ConvertToV8(isolate, error_message); + callback.Run(error, v8::Null(isolate)); + return; + } + if (!set_success) { + v8::Local error = mate::ConvertToV8( + isolate, "Failed to set cookies"); + callback.Run(error, v8::Null(isolate)); + } + + callback.Run(v8::Null(isolate), v8::Null(isolate)); +} + +bool MatchesDomain(const base::DictionaryValue* filter, + const std::string& cookie_domain) { + std::string filter_domain; + if (!filter->GetString("domain", &filter_domain)) + return true; + + // Add a leading '.' character to the filter domain if it doesn't exist. + if (net::cookie_util::DomainIsHostOnly(filter_domain)) + filter_domain.insert(0, "."); + + std::string sub_domain(cookie_domain); + // Strip any leading '.' character from the input cookie domain. + if (!net::cookie_util::DomainIsHostOnly(sub_domain)) + sub_domain = sub_domain.substr(1); + + // Now check whether the domain argument is a subdomain of the filter domain. + for (sub_domain.insert(0, "."); + sub_domain.length() >= filter_domain.length();) { + if (sub_domain == filter_domain) { + return true; + } + const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot. + sub_domain.erase(0, next_dot); + } + return false; +} + +bool MatchesCookie(const base::DictionaryValue* filter, + const net::CanonicalCookie& cookie) { + std::string name, domain, path; + bool is_secure, session; + if (filter->GetString("name", &name) && name != cookie.Name()) + return false; + if (filter->GetString("path", &path) && path != cookie.Path()) + return false; + if (!MatchesDomain(filter, cookie.Domain())) + return false; + if (filter->GetBoolean("secure", &is_secure) && + is_secure != cookie.IsSecure()) + return false; + if (filter->GetBoolean("session", &session) && + session != cookie.IsPersistent()) + return false; + return true; +} + +} // namespace + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const net::CanonicalCookie& val) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + dict.Set("name", val.Name()); + dict.Set("value", val.Value()); + dict.Set("domain", val.Domain()); + dict.Set("host_only", net::cookie_util::DomainIsHostOnly(val.Domain())); + dict.Set("path", val.Path()); + dict.Set("secure", val.IsSecure()); + dict.Set("http_only", val.IsHttpOnly()); + dict.Set("session", val.IsPersistent()); + if (!val.IsPersistent()) + dict.Set("expirationDate", val.ExpiryDate().ToDoubleT()); + return dict.GetHandle(); + } +}; + +} // namespace mate + +namespace atom { + +namespace api { + +Cookies::Cookies(content::BrowserContext* browser_context) + : request_context_getter_(browser_context->GetRequestContext()) { +} + +Cookies::~Cookies() { +} + +void Cookies::Get(const base::DictionaryValue& options, + const CookiesCallback& callback) { + scoped_ptr filter( + options.DeepCopyWithoutEmptyChildren()); + + content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&Cookies::GetCookiesOnIOThread, base::Unretained(this), + Passed(&filter), callback)); +} + +void Cookies::GetCookiesOnIOThread(scoped_ptr filter, + const CookiesCallback& callback) { + std::string url; + filter->GetString("url", &url); + if (!GetCookieListFromStore(GetCookieStore(), url, + base::Bind(&Cookies::OnGetCookies, base::Unretained(this), + Passed(&filter), callback))) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&RunGetCookiesCallbackOnUIThread, isolate(), + "Url is not valid", net::CookieList(), callback)); + } +} + +void Cookies::OnGetCookies(scoped_ptr filter, + const CookiesCallback& callback, + const net::CookieList& cookie_list) { + net::CookieList result; + for (const auto& cookie : cookie_list) { + if (MatchesCookie(filter.get(), cookie)) + result.push_back(cookie); + } + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( + &RunGetCookiesCallbackOnUIThread, isolate(), "", result, callback)); +} + +void Cookies::Remove(const mate::Dictionary& details, + const CookiesCallback& callback) { + GURL url; + std::string name; + std::string error_message; + if (!details.Get("url", &url) || !details.Get("name", &name)) { + error_message = "Details(url, name) of removing cookie are required."; + } + if (error_message.empty() && !url.is_valid()) { + error_message = "Url is not valid."; + } + if (!error_message.empty()) { + RunRemoveCookiesCallbackOnUIThread(isolate(), error_message, callback); + return; + } + content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&Cookies::RemoveCookiesOnIOThread, base::Unretained(this), + url, name, callback)); +} + +void Cookies::RemoveCookiesOnIOThread(const GURL& url, const std::string& name, + const CookiesCallback& callback) { + GetCookieStore()->DeleteCookieAsync(url, name, + base::Bind(&Cookies::OnRemoveCookies, base::Unretained(this), callback)); +} + +void Cookies::OnRemoveCookies(const CookiesCallback& callback) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&RunRemoveCookiesCallbackOnUIThread, isolate(), "", callback)); +} + +void Cookies::Set(const base::DictionaryValue& options, + const CookiesCallback& callback) { + std::string url; + std::string error_message; + if (!options.GetString("url", &url)) { + error_message = "The url field is required."; + } + + GURL gurl(url); + if (error_message.empty() && !gurl.is_valid()) { + error_message = "Url is not valid."; + } + + if (!error_message.empty()) { + RunSetCookiesCallbackOnUIThread(isolate(), error_message, false, callback); + return; + } + + scoped_ptr details( + options.DeepCopyWithoutEmptyChildren()); + + content::BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&Cookies::SetCookiesOnIOThread, base::Unretained(this), + Passed(&details), gurl, callback)); +} + +void Cookies::SetCookiesOnIOThread(scoped_ptr details, + const GURL& url, + const CookiesCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + std::string name, value, domain, path; + bool secure = false; + bool http_only = false; + double expiration_date; + + details->GetString("name", &name); + details->GetString("value", &value); + details->GetString("domain", &domain); + details->GetString("path", &path); + details->GetBoolean("secure", &secure); + details->GetBoolean("http_only", &http_only); + + base::Time expiration_time; + if (details->GetDouble("expirationDate", &expiration_date)) { + expiration_time = (expiration_date == 0) ? + base::Time::UnixEpoch() : + base::Time::FromDoubleT(expiration_date); + } + + GetCookieStore()->GetCookieMonster()->SetCookieWithDetailsAsync( + url, + name, + value, + domain, + path, + expiration_time, + secure, + http_only, + false, + net::COOKIE_PRIORITY_DEFAULT, + base::Bind(&Cookies::OnSetCookies, base::Unretained(this), callback)); +} + +void Cookies::OnSetCookies(const CookiesCallback& callback, + bool set_success) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&RunSetCookiesCallbackOnUIThread, isolate(), "", set_success, + callback)); +} + +mate::ObjectTemplateBuilder Cookies::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return mate::ObjectTemplateBuilder(isolate) + .SetMethod("get", &Cookies::Get) + .SetMethod("remove", &Cookies::Remove) + .SetMethod("set", &Cookies::Set); +} + +net::CookieStore* Cookies::GetCookieStore() { + return request_context_getter_->GetURLRequestContext()->cookie_store(); +} + +// static +mate::Handle Cookies::Create( + v8::Isolate* isolate, + content::BrowserContext* browser_context) { + return mate::CreateHandle(isolate, new Cookies(browser_context)); +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/atom_api_cookies.h b/atom/browser/api/atom_api_cookies.h new file mode 100644 index 000000000000..0c309b3f18ee --- /dev/null +++ b/atom/browser/api/atom_api_cookies.h @@ -0,0 +1,90 @@ +// 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_BROWSER_API_ATOM_API_COOKIES_H_ +#define ATOM_BROWSER_API_ATOM_API_COOKIES_H_ + +#include + +#include "base/callback.h" +#include "native_mate/wrappable.h" +#include "native_mate/handle.h" +#include "net/cookies/canonical_cookie.h" + +namespace base { +class DictionaryValue; +} + +namespace content { +class BrowserContext; +} + +namespace mate { +class Dictionary; +} + +namespace net { +class CookieStore; +class URLRequestContextGetter; +} + +namespace atom { + +namespace api { + +class Cookies : public mate::Wrappable { + public: + // node.js style callback function(error, result) + typedef base::Callback, v8::Local)> + CookiesCallback; + + static mate::Handle Create(v8::Isolate* isolate, + content::BrowserContext* browser_context); + + protected: + explicit Cookies(content::BrowserContext* browser_context); + ~Cookies(); + + void Get(const base::DictionaryValue& options, + const CookiesCallback& callback); + void Remove(const mate::Dictionary& details, + const CookiesCallback& callback); + void Set(const base::DictionaryValue& details, + const CookiesCallback& callback); + + void GetCookiesOnIOThread(scoped_ptr filter, + const CookiesCallback& callback); + void OnGetCookies(scoped_ptr filter, + const CookiesCallback& callback, + const net::CookieList& cookie_list); + + void RemoveCookiesOnIOThread(const GURL& url, + const std::string& name, + const CookiesCallback& callback); + void OnRemoveCookies(const CookiesCallback& callback); + + void SetCookiesOnIOThread(scoped_ptr details, + const GURL& url, + const CookiesCallback& callback); + void OnSetCookies(const CookiesCallback& callback, + bool set_success); + + // mate::Wrappable: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; + + private: + // Must be called on IO thread. + net::CookieStore* GetCookieStore(); + + net::URLRequestContextGetter* request_context_getter_; + + DISALLOW_COPY_AND_ASSIGN(Cookies); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_COOKIES_H_ diff --git a/atom/browser/api/atom_api_dialog.cc b/atom/browser/api/atom_api_dialog.cc index 5834b73e6b6d..40ee4d0d9b51 100644 --- a/atom/browser/api/atom_api_dialog.cc +++ b/atom/browser/api/atom_api_dialog.cc @@ -10,9 +10,9 @@ #include "atom/browser/native_window.h" #include "atom/browser/ui/file_dialog.h" #include "atom/browser/ui/message_box.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/image_converter.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "atom/common/node_includes.h" @@ -22,7 +22,7 @@ namespace mate { template<> struct Converter { static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, file_dialog::Filter* out) { mate::Dictionary dict; if (!ConvertFromV8(isolate, val, &dict)) @@ -41,27 +41,25 @@ namespace { void ShowMessageBox(int type, const std::vector& buttons, - const std::vector& texts, + int cancel_id, + int options, + const std::string& title, + const std::string& message, + const std::string& detail, const gfx::ImageSkia& icon, atom::NativeWindow* window, mate::Arguments* args) { - // FIXME We are exceeding the parameters limit of base::Bind here, so we have - // to pass some parameters in an array. We should remove this once we have - // variadic template support in base::Bind. - const std::string& title = texts[0]; - const std::string& message = texts[1]; - const std::string& detail = texts[2]; - - v8::Handle peek = args->PeekNext(); + v8::Local peek = args->PeekNext(); atom::MessageBoxCallback callback; if (mate::Converter::FromV8(args->isolate(), peek, &callback)) { - atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons, title, - message, detail, icon, callback); + atom::ShowMessageBox(window, (atom::MessageBoxType)type, buttons, cancel_id, + options, title, message, detail, icon, callback); } else { int chosen = atom::ShowMessageBox(window, (atom::MessageBoxType)type, - buttons, title, message, detail, icon); + buttons, cancel_id, options, title, + message, detail, icon); args->Return(chosen); } } @@ -72,7 +70,7 @@ void ShowOpenDialog(const std::string& title, int properties, atom::NativeWindow* window, mate::Arguments* args) { - v8::Handle peek = args->PeekNext(); + v8::Local peek = args->PeekNext(); file_dialog::OpenDialogCallback callback; if (mate::Converter::FromV8(args->isolate(), peek, @@ -92,7 +90,7 @@ void ShowSaveDialog(const std::string& title, const file_dialog::Filters& filters, atom::NativeWindow* window, mate::Arguments* args) { - v8::Handle peek = args->PeekNext(); + v8::Local peek = args->PeekNext(); file_dialog::SaveDialogCallback callback; if (mate::Converter::FromV8(args->isolate(), peek, @@ -106,8 +104,8 @@ void ShowSaveDialog(const std::string& title, } } -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("showMessageBox", &ShowMessageBox); dict.SetMethod("showErrorBox", &atom::ShowErrorBox); diff --git a/atom/browser/api/atom_api_global_shortcut.cc b/atom/browser/api/atom_api_global_shortcut.cc index c30873379746..f5a03e4abf90 100644 --- a/atom/browser/api/atom_api_global_shortcut.cc +++ b/atom/browser/api/atom_api_global_shortcut.cc @@ -7,8 +7,8 @@ #include #include "atom/common/native_mate_converters/accelerator_converter.h" +#include "atom/common/native_mate_converters/callback.h" #include "base/stl_util.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "atom/common/node_includes.h" @@ -86,8 +86,8 @@ mate::Handle GlobalShortcut::Create(v8::Isolate* isolate) { namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.Set("globalShortcut", atom::api::GlobalShortcut::Create(isolate)); diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 010d0414d9a9..9bd724a9612e 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -6,9 +6,9 @@ #include "atom/browser/native_window.h" #include "atom/common/native_mate_converters/accelerator_converter.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" -#include "native_mate/callback.h" #include "native_mate/constructor.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" @@ -20,7 +20,7 @@ namespace atom { namespace api { Menu::Menu() - : model_(new ui::SimpleMenuModel(this)), + : model_(new AtomMenuModel(this)), parent_(NULL) { } @@ -55,11 +55,10 @@ bool Menu::IsCommandIdVisible(int command_id) const { bool Menu::GetAcceleratorForCommandId(int command_id, ui::Accelerator* accelerator) { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - v8::Locker locker(isolate); - v8::HandleScope handle_scope(isolate); - v8::Handle val = get_accelerator_.Run(command_id); - return mate::ConvertFromV8(isolate, val, accelerator); + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + v8::Local val = get_accelerator_.Run(command_id); + return mate::ConvertFromV8(isolate(), val, accelerator); } void Menu::ExecuteCommand(int command_id, int event_flags) { @@ -70,10 +69,6 @@ void Menu::MenuWillShow(ui::SimpleMenuModel* source) { menu_will_show_.Run(); } -void Menu::AttachToWindow(Window* window) { - window->window()->SetMenu(model_.get()); -} - void Menu::InsertItemAt( int index, int command_id, const base::string16& label) { model_->InsertItemAt(index, command_id, label); @@ -112,6 +107,10 @@ void Menu::SetSublabel(int index, const base::string16& sublabel) { model_->SetSublabel(index, sublabel); } +void Menu::SetRole(int index, const base::string16& role) { + model_->SetRole(index, role); +} + void Menu::Clear() { model_->Clear(); } @@ -150,7 +149,7 @@ bool Menu::IsVisibleAt(int index) const { // static void Menu::BuildPrototype(v8::Isolate* isolate, - v8::Handle prototype) { + v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) .SetMethod("insertItem", &Menu::InsertItemAt) .SetMethod("insertCheckItem", &Menu::InsertCheckItemAt) @@ -159,6 +158,7 @@ void Menu::BuildPrototype(v8::Isolate* isolate, .SetMethod("insertSubMenu", &Menu::InsertSubMenuAt) .SetMethod("setIcon", &Menu::SetIcon) .SetMethod("setSublabel", &Menu::SetSublabel) + .SetMethod("setRole", &Menu::SetRole) .SetMethod("clear", &Menu::Clear) .SetMethod("getIndexOfCommandId", &Menu::GetIndexOfCommandId) .SetMethod("getItemCount", &Menu::GetItemCount) @@ -168,7 +168,6 @@ void Menu::BuildPrototype(v8::Isolate* isolate, .SetMethod("isItemCheckedAt", &Menu::IsItemCheckedAt) .SetMethod("isEnabledAt", &Menu::IsEnabledAt) .SetMethod("isVisibleAt", &Menu::IsVisibleAt) - .SetMethod("attachToWindow", &Menu::AttachToWindow) .SetMethod("_popup", &Menu::Popup) .SetMethod("_popupAt", &Menu::PopupAt); } @@ -180,14 +179,14 @@ void Menu::BuildPrototype(v8::Isolate* isolate, namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { using atom::api::Menu; v8::Isolate* isolate = context->GetIsolate(); v8::Local constructor = mate::CreateConstructor( isolate, "Menu", base::Bind(&Menu::Create)); mate::Dictionary dict(isolate, exports); - dict.Set("Menu", static_cast>(constructor)); + dict.Set("Menu", static_cast>(constructor)); #if defined(OS_MACOSX) dict.SetMethod("setApplicationMenu", &Menu::SetApplicationMenu); dict.SetMethod("sendActionToFirstResponder", diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 830c5d0c6465..0d93c0d46be6 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -8,9 +8,9 @@ #include #include "atom/browser/api/atom_api_window.h" +#include "atom/browser/ui/atom_menu_model.h" #include "base/callback.h" #include "base/memory/scoped_ptr.h" -#include "ui/base/models/simple_menu_model.h" #include "native_mate/wrappable.h" namespace atom { @@ -18,12 +18,12 @@ namespace atom { namespace api { class Menu : public mate::Wrappable, - public ui::SimpleMenuModel::Delegate { + public AtomMenuModel::Delegate { public: static mate::Wrappable* Create(); static void BuildPrototype(v8::Isolate* isolate, - v8::Handle prototype); + v8::Local prototype); #if defined(OS_MACOSX) // Set the global menubar. @@ -33,7 +33,7 @@ class Menu : public mate::Wrappable, static void SendActionToFirstResponder(const std::string& action); #endif - ui::SimpleMenuModel* model() const { return model_.get(); } + AtomMenuModel* model() const { return model_.get(); } protected: Menu(); @@ -42,7 +42,7 @@ class Menu : public mate::Wrappable, // mate::Wrappable: void AfterInit(v8::Isolate* isolate) override; - // ui::SimpleMenuModel::Delegate implementations: + // ui::SimpleMenuModel::Delegate: bool IsCommandIdChecked(int command_id) const override; bool IsCommandIdEnabled(int command_id) const override; bool IsCommandIdVisible(int command_id) const override; @@ -51,11 +51,10 @@ class Menu : public mate::Wrappable, void ExecuteCommand(int command_id, int event_flags) override; void MenuWillShow(ui::SimpleMenuModel* source) override; - virtual void AttachToWindow(Window* window); virtual void Popup(Window* window) = 0; virtual void PopupAt(Window* window, int x, int y) = 0; - scoped_ptr model_; + scoped_ptr model_; Menu* parent_; private: @@ -74,6 +73,7 @@ class Menu : public mate::Wrappable, Menu* menu); void SetIcon(int index, const gfx::Image& image); void SetSublabel(int index, const base::string16& sublabel); + void SetRole(int index, const base::string16& role); void Clear(); int GetIndexOfCommandId(int command_id); int GetItemCount() const; @@ -88,7 +88,7 @@ class Menu : public mate::Wrappable, base::Callback is_checked_; base::Callback is_enabled_; base::Callback is_visible_; - base::Callback(int)> get_accelerator_; + base::Callback(int)> get_accelerator_; base::Callback execute_command_; base::Callback menu_will_show_; @@ -99,4 +99,27 @@ class Menu : public mate::Wrappable, } // namespace atom + +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + atom::AtomMenuModel** out) { + // null would be tranfered to NULL. + if (val->IsNull()) { + *out = nullptr; + return true; + } + + atom::api::Menu* menu; + if (!Converter::FromV8(isolate, val, &menu)) + return false; + *out = menu->model(); + return true; + } +}; + +} // namespace mate + #endif // ATOM_BROWSER_API_ATOM_API_MENU_H_ diff --git a/atom/browser/api/atom_api_menu_mac.mm b/atom/browser/api/atom_api_menu_mac.mm index b61d1c69a87c..071753218d6a 100644 --- a/atom/browser/api/atom_api_menu_mac.mm +++ b/atom/browser/api/atom_api_menu_mac.mm @@ -22,7 +22,7 @@ void MenuMac::Popup(Window* window) { NativeWindow* native_window = window->window(); if (!native_window) return; - content::WebContents* web_contents = native_window->GetWebContents(); + content::WebContents* web_contents = native_window->web_contents(); if (!web_contents) return; @@ -54,7 +54,7 @@ void MenuMac::PopupAt(Window* window, int x, int y) { NativeWindow* native_window = window->window(); if (!native_window) return; - content::WebContents* web_contents = native_window->GetWebContents(); + content::WebContents* web_contents = native_window->web_contents(); if (!web_contents) return; diff --git a/atom/browser/api/atom_api_menu_views.cc b/atom/browser/api/atom_api_menu_views.cc index b06a1e79cbe0..45904dd8c033 100644 --- a/atom/browser/api/atom_api_menu_views.cc +++ b/atom/browser/api/atom_api_menu_views.cc @@ -24,7 +24,7 @@ void MenuViews::PopupAt(Window* window, int x, int y) { NativeWindow* native_window = static_cast(window->window()); if (!native_window) return; - content::WebContents* web_contents = native_window->GetWebContents(); + content::WebContents* web_contents = native_window->web_contents(); if (!web_contents) return; content::RenderWidgetHostView* view = web_contents->GetRenderWidgetHostView(); diff --git a/atom/browser/api/atom_api_power_monitor.cc b/atom/browser/api/atom_api_power_monitor.cc index 8f87eec07280..31b35e10cea8 100644 --- a/atom/browser/api/atom_api_power_monitor.cc +++ b/atom/browser/api/atom_api_power_monitor.cc @@ -5,12 +5,11 @@ #include "atom/browser/api/atom_api_power_monitor.h" #include "atom/browser/browser.h" +#include "atom/common/node_includes.h" #include "base/power_monitor/power_monitor.h" #include "base/power_monitor/power_monitor_device_source.h" #include "native_mate/dictionary.h" -#include "atom/common/node_includes.h" - namespace atom { namespace api { @@ -39,10 +38,11 @@ void PowerMonitor::OnResume() { } // static -v8::Handle PowerMonitor::Create(v8::Isolate* isolate) { +v8::Local PowerMonitor::Create(v8::Isolate* isolate) { if (!Browser::Get()->is_ready()) { - node::ThrowError("Cannot initialize \"power-monitor\" module" - "before app is ready"); + isolate->ThrowException(v8::Exception::Error(mate::StringToV8( + isolate, + "Cannot initialize \"power-monitor\" module before app is ready"))); return v8::Null(isolate); } @@ -56,8 +56,8 @@ v8::Handle PowerMonitor::Create(v8::Isolate* isolate) { namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { #if defined(OS_MACOSX) base::PowerMonitorDeviceSource::AllocateSystemIOPorts(); #endif diff --git a/atom/browser/api/atom_api_power_monitor.h b/atom/browser/api/atom_api_power_monitor.h index cdd14ff6e859..dcf0906b437f 100644 --- a/atom/browser/api/atom_api_power_monitor.h +++ b/atom/browser/api/atom_api_power_monitor.h @@ -17,7 +17,7 @@ namespace api { class PowerMonitor : public mate::EventEmitter, public base::PowerObserver { public: - static v8::Handle Create(v8::Isolate* isolate); + static v8::Local Create(v8::Isolate* isolate); protected: PowerMonitor(); diff --git a/atom/browser/api/atom_api_power_save_blocker.cc b/atom/browser/api/atom_api_power_save_blocker.cc new file mode 100644 index 000000000000..58983e6c846a --- /dev/null +++ b/atom/browser/api/atom_api_power_save_blocker.cc @@ -0,0 +1,128 @@ +// 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 "atom/browser/api/atom_api_power_save_blocker.h" + +#include + +#include "atom/common/node_includes.h" +#include "content/public/browser/power_save_blocker.h" +#include "native_mate/dictionary.h" + +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + content::PowerSaveBlocker::PowerSaveBlockerType* out) { + using content::PowerSaveBlocker; + std::string type; + if (!ConvertFromV8(isolate, val, &type)) + return false; + if (type == "prevent-app-suspension") + *out = PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension; + else if (type == "prevent-display-sleep") + *out = PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep; + else + return false; + return true; + } +}; + +} // namespace mate + +namespace atom { + +namespace api { + +PowerSaveBlocker::PowerSaveBlocker() + : current_blocker_type_( + content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension) { +} + +PowerSaveBlocker::~PowerSaveBlocker() { +} + +void PowerSaveBlocker::UpdatePowerSaveBlocker() { + if (power_save_blocker_types_.empty()) { + power_save_blocker_.reset(); + return; + } + + // |kPowerSaveBlockPreventAppSuspension| keeps system active, but allows + // screen to be turned off. + // |kPowerSaveBlockPreventDisplaySleep| keeps system and screen active, has a + // higher precedence level than |kPowerSaveBlockPreventAppSuspension|. + // + // Only the highest-precedence blocker type takes effect. + content::PowerSaveBlocker::PowerSaveBlockerType new_blocker_type = + content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension; + for (const auto& element : power_save_blocker_types_) { + if (element.second == + content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep) { + new_blocker_type = + content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep; + break; + } + } + + if (!power_save_blocker_ || new_blocker_type != current_blocker_type_) { + scoped_ptr new_blocker = + content::PowerSaveBlocker::Create( + new_blocker_type, + content::PowerSaveBlocker::kReasonOther, + ATOM_PRODUCT_NAME); + power_save_blocker_.swap(new_blocker); + current_blocker_type_ = new_blocker_type; + } +} + +int PowerSaveBlocker::Start( + content::PowerSaveBlocker::PowerSaveBlockerType type) { + static int count = 0; + power_save_blocker_types_[count] = type; + UpdatePowerSaveBlocker(); + return count++; +} + +bool PowerSaveBlocker::Stop(int id) { + bool success = power_save_blocker_types_.erase(id) > 0; + UpdatePowerSaveBlocker(); + return success; +} + +bool PowerSaveBlocker::IsStarted(int id) { + return power_save_blocker_types_.find(id) != power_save_blocker_types_.end(); +} + +mate::ObjectTemplateBuilder PowerSaveBlocker::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return mate::ObjectTemplateBuilder(isolate) + .SetMethod("start", &PowerSaveBlocker::Start) + .SetMethod("stop", &PowerSaveBlocker::Stop) + .SetMethod("isStarted", &PowerSaveBlocker::IsStarted); +} + +// static +mate::Handle PowerSaveBlocker::Create(v8::Isolate* isolate) { + return CreateHandle(isolate, new PowerSaveBlocker); +} + +} // 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.Set("powerSaveBlocker", atom::api::PowerSaveBlocker::Create(isolate)); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_power_save_blocker, Initialize); diff --git a/atom/browser/api/atom_api_power_save_blocker.h b/atom/browser/api/atom_api_power_save_blocker.h new file mode 100644 index 000000000000..9861f2b0f7cd --- /dev/null +++ b/atom/browser/api/atom_api_power_save_blocker.h @@ -0,0 +1,59 @@ +// 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_BROWSER_API_ATOM_API_POWER_SAVE_BLOCKER_H_ +#define ATOM_BROWSER_API_ATOM_API_POWER_SAVE_BLOCKER_H_ + +#include + +#include "base/memory/scoped_ptr.h" +#include "content/public/browser/power_save_blocker.h" +#include "native_mate/handle.h" +#include "native_mate/wrappable.h" + +namespace mate { +class Dictionary; +} + +namespace atom { + +namespace api { + +class PowerSaveBlocker : public mate::Wrappable { + public: + static mate::Handle Create(v8::Isolate* isolate); + + protected: + PowerSaveBlocker(); + virtual ~PowerSaveBlocker(); + + // mate::Wrappable implementations: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; + + private: + void UpdatePowerSaveBlocker(); + int Start(content::PowerSaveBlocker::PowerSaveBlockerType type); + bool Stop(int id); + bool IsStarted(int id); + + scoped_ptr power_save_blocker_; + + // Currnet blocker type used by |power_save_blocker_| + content::PowerSaveBlocker::PowerSaveBlockerType current_blocker_type_; + + // Map from id to the corresponding blocker type for each request. + using PowerSaveBlockerTypeMap = + std::map; + PowerSaveBlockerTypeMap power_save_blocker_types_; + + + DISALLOW_COPY_AND_ASSIGN(PowerSaveBlocker); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_POWER_SAVE_BLOCKER_H_ diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index 8a7e0ae6840a..661ab1b5cbdd 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -4,16 +4,16 @@ #include "atom/browser/api/atom_api_protocol.h" +#include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_context.h" -#include "atom/browser/net/adapter_request_job.h" -#include "atom/browser/net/atom_url_request_job_factory.h" -#include "atom/common/native_mate_converters/file_path_converter.h" -#include "content/public/browser/browser_thread.h" -#include "native_mate/callback.h" -#include "native_mate/dictionary.h" -#include "net/url_request/url_request_context.h" - +#include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/net/url_request_async_asar_job.h" +#include "atom/browser/net/url_request_buffer_job.h" +#include "atom/browser/net/url_request_fetch_job.h" +#include "atom/browser/net/url_request_string_job.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/node_includes.h" +#include "native_mate/dictionary.h" using content::BrowserThread; @@ -21,8 +21,8 @@ namespace mate { template<> struct Converter { - static v8::Handle ToV8(v8::Isolate* isolate, - const net::URLRequest* val) { + static v8::Local ToV8(v8::Isolate* isolate, + const net::URLRequest* val) { return mate::ObjectTemplateBuilder(isolate) .SetValue("method", val->method()) .SetValue("url", val->url().spec()) @@ -33,304 +33,132 @@ struct Converter { } // namespace mate - namespace atom { namespace api { -namespace { - -typedef net::URLRequestJobFactory::ProtocolHandler ProtocolHandler; - -class CustomProtocolRequestJob : public AdapterRequestJob { - public: - CustomProtocolRequestJob(Protocol* registry, - ProtocolHandler* protocol_handler, - net::URLRequest* request, - net::NetworkDelegate* network_delegate) - : AdapterRequestJob(protocol_handler, request, network_delegate), - registry_(registry) { - } - - // AdapterRequestJob: - void GetJobTypeInUI() override { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - v8::Locker locker(isolate); - v8::HandleScope handle_scope(isolate); - - // Call the JS handler. - Protocol::JsProtocolHandler callback = - registry_->GetProtocolHandler(request()->url().scheme()); - v8::Handle result = callback.Run(request()); - - // Determine the type of the job we are going to create. - if (result->IsString()) { - std::string data = mate::V8ToString(result); - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&AdapterRequestJob::CreateStringJobAndStart, - GetWeakPtr(), "text/plain", "UTF-8", data)); - return; - } else if (result->IsObject()) { - v8::Handle obj = result->ToObject(); - mate::Dictionary dict(isolate, obj); - std::string name = mate::V8ToString(obj->GetConstructorName()); - if (name == "RequestStringJob") { - std::string mime_type, charset, data; - dict.Get("mimeType", &mime_type); - dict.Get("charset", &charset); - dict.Get("data", &data); - - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&AdapterRequestJob::CreateStringJobAndStart, - GetWeakPtr(), mime_type, charset, data)); - return; - } else if (name == "RequestBufferJob") { - std::string mime_type, encoding; - v8::Handle buffer; - dict.Get("mimeType", &mime_type); - dict.Get("encoding", &encoding); - dict.Get("data", &buffer); - - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&AdapterRequestJob::CreateBufferJobAndStart, - GetWeakPtr(), mime_type, encoding, buffer->ToObject())); - return; - } else if (name == "RequestFileJob") { - base::FilePath path; - dict.Get("path", &path); - - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&AdapterRequestJob::CreateFileJobAndStart, - GetWeakPtr(), path)); - return; - } else if (name == "RequestErrorJob") { - // Default value net::ERR_NOT_IMPLEMENTED - int error = -11; - dict.Get("error", &error); - - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&AdapterRequestJob::CreateErrorJobAndStart, - GetWeakPtr(), error)); - return; - } - } - - // Try the default protocol handler if we have. - if (default_protocol_handler()) { - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&AdapterRequestJob::CreateJobFromProtocolHandlerAndStart, - GetWeakPtr())); - return; - } - - // Fallback to the not implemented error. - BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, - base::Bind(&AdapterRequestJob::CreateErrorJobAndStart, - GetWeakPtr(), net::ERR_NOT_IMPLEMENTED)); - } - - private: - Protocol* registry_; // Weak, the Protocol class is expected to live forever. -}; - -// Always return the same CustomProtocolRequestJob for all requests, because -// the content API needs the ProtocolHandler to return a job immediately, and -// getting the real job from the JS requires asynchronous calls, so we have -// to create an adapter job first. -// Users can also pass an extra ProtocolHandler as the fallback one when -// registered handler doesn't want to deal with the request. -class CustomProtocolHandler : public ProtocolHandler { - public: - CustomProtocolHandler(api::Protocol* registry, - ProtocolHandler* protocol_handler = NULL) - : registry_(registry), protocol_handler_(protocol_handler) { - } - - net::URLRequestJob* MaybeCreateJob( - net::URLRequest* request, - net::NetworkDelegate* network_delegate) const override { - return new CustomProtocolRequestJob(registry_, protocol_handler_.get(), - request, network_delegate); - } - - ProtocolHandler* ReleaseDefaultProtocolHandler() { - return protocol_handler_.release(); - } - - ProtocolHandler* original_handler() { return protocol_handler_.get(); } - - private: - Protocol* registry_; // Weak, the Protocol class is expected to live forever. - scoped_ptr protocol_handler_; - - DISALLOW_COPY_AND_ASSIGN(CustomProtocolHandler); -}; - -} // namespace - -Protocol::Protocol() - : job_factory_(AtomBrowserContext::Get()->job_factory()) { +Protocol::Protocol(AtomBrowserContext* browser_context) + : request_context_getter_(browser_context->GetRequestContext()), + job_factory_(browser_context->job_factory()) { CHECK(job_factory_); } -Protocol::JsProtocolHandler Protocol::GetProtocolHandler( - const std::string& scheme) { - return protocol_handlers_[scheme]; -} - mate::ObjectTemplateBuilder Protocol::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) - .SetMethod("registerProtocol", - base::Bind(&Protocol::RegisterProtocol, - base::Unretained(this))) - .SetMethod("unregisterProtocol", - base::Bind(&Protocol::UnregisterProtocol, - base::Unretained(this))) - .SetMethod("isHandledProtocol", - base::Bind(&Protocol::IsHandledProtocol, - base::Unretained(this))) - .SetMethod("interceptProtocol", - base::Bind(&Protocol::InterceptProtocol, - base::Unretained(this))) - .SetMethod("uninterceptProtocol", - base::Bind(&Protocol::UninterceptProtocol, - base::Unretained(this))); + .SetMethod("registerStandardSchemes", &Protocol::RegisterStandardSchemes) + .SetMethod("registerStringProtocol", + &Protocol::RegisterProtocol) + .SetMethod("registerBufferProtocol", + &Protocol::RegisterProtocol) + .SetMethod("registerFileProtocol", + &Protocol::RegisterProtocol) + .SetMethod("registerHttpProtocol", + &Protocol::RegisterProtocol) + .SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol) + .SetMethod("isProtocolHandled", &Protocol::IsProtocolHandled) + .SetMethod("interceptStringProtocol", + &Protocol::InterceptProtocol) + .SetMethod("interceptBufferProtocol", + &Protocol::InterceptProtocol) + .SetMethod("interceptFileProtocol", + &Protocol::InterceptProtocol) + .SetMethod("interceptHttpProtocol", + &Protocol::InterceptProtocol) + .SetMethod("uninterceptProtocol", &Protocol::UninterceptProtocol); } -void Protocol::RegisterProtocol(const std::string& scheme, - const JsProtocolHandler& callback) { - if (ContainsKey(protocol_handlers_, scheme) || - job_factory_->IsHandledProtocol(scheme)) - return node::ThrowError("The scheme is already registered"); - - protocol_handlers_[scheme] = callback; - BrowserThread::PostTask(BrowserThread::IO, - FROM_HERE, - base::Bind(&Protocol::RegisterProtocolInIO, - base::Unretained(this), scheme)); +void Protocol::RegisterStandardSchemes( + const std::vector& schemes) { + atom::AtomBrowserClient::SetCustomSchemes(schemes); } -void Protocol::UnregisterProtocol(const std::string& scheme) { - ProtocolHandlersMap::iterator it(protocol_handlers_.find(scheme)); - if (it == protocol_handlers_.end()) - return node::ThrowError("The scheme has not been registered"); - - protocol_handlers_.erase(it); - BrowserThread::PostTask(BrowserThread::IO, - FROM_HERE, - base::Bind(&Protocol::UnregisterProtocolInIO, - base::Unretained(this), scheme)); +void Protocol::UnregisterProtocol( + const std::string& scheme, mate::Arguments* args) { + CompletionCallback callback; + args->GetNext(&callback); + content::BrowserThread::PostTaskAndReplyWithResult( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&Protocol::UnregisterProtocolInIO, + base::Unretained(this), scheme), + base::Bind(&Protocol::OnIOCompleted, + base::Unretained(this), callback)); } -bool Protocol::IsHandledProtocol(const std::string& scheme) { +Protocol::ProtocolError Protocol::UnregisterProtocolInIO( + const std::string& scheme) { + if (!job_factory_->HasProtocolHandler(scheme)) + return PROTOCOL_NOT_REGISTERED; + job_factory_->SetProtocolHandler(scheme, nullptr); + return PROTOCOL_OK; +} + +void Protocol::IsProtocolHandled(const std::string& scheme, + const BooleanCallback& callback) { + content::BrowserThread::PostTaskAndReplyWithResult( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&Protocol::IsProtocolHandledInIO, + base::Unretained(this), scheme), + callback); +} + +bool Protocol::IsProtocolHandledInIO(const std::string& scheme) { return job_factory_->IsHandledProtocol(scheme); } -void Protocol::InterceptProtocol(const std::string& scheme, - const JsProtocolHandler& callback) { - if (!job_factory_->HasProtocolHandler(scheme)) - return node::ThrowError("Scheme does not exist."); - - if (ContainsKey(protocol_handlers_, scheme)) - return node::ThrowError("Cannot intercept custom procotols"); - - protocol_handlers_[scheme] = callback; - BrowserThread::PostTask(BrowserThread::IO, - FROM_HERE, - base::Bind(&Protocol::InterceptProtocolInIO, - base::Unretained(this), scheme)); +void Protocol::UninterceptProtocol( + const std::string& scheme, mate::Arguments* args) { + CompletionCallback callback; + args->GetNext(&callback); + content::BrowserThread::PostTaskAndReplyWithResult( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&Protocol::UninterceptProtocolInIO, + base::Unretained(this), scheme), + base::Bind(&Protocol::OnIOCompleted, + base::Unretained(this), callback)); } -void Protocol::UninterceptProtocol(const std::string& scheme) { - ProtocolHandlersMap::iterator it(protocol_handlers_.find(scheme)); - if (it == protocol_handlers_.end()) - return node::ThrowError("The scheme has not been registered"); - - protocol_handlers_.erase(it); - BrowserThread::PostTask(BrowserThread::IO, - FROM_HERE, - base::Bind(&Protocol::UninterceptProtocolInIO, - base::Unretained(this), scheme)); +Protocol::ProtocolError Protocol::UninterceptProtocolInIO( + const std::string& scheme) { + if (!original_protocols_.contains(scheme)) + return PROTOCOL_NOT_INTERCEPTED; + job_factory_->ReplaceProtocol(scheme, + original_protocols_.take_and_erase(scheme)); + return PROTOCOL_OK; } -void Protocol::RegisterProtocolInIO(const std::string& scheme) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - - job_factory_->SetProtocolHandler(scheme, new CustomProtocolHandler(this)); - BrowserThread::PostTask(BrowserThread::UI, - FROM_HERE, - base::Bind(&Protocol::EmitEventInUI, - base::Unretained(this), - "registered", scheme)); -} - -void Protocol::UnregisterProtocolInIO(const std::string& scheme) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - - job_factory_->SetProtocolHandler(scheme, NULL); - BrowserThread::PostTask(BrowserThread::UI, - FROM_HERE, - base::Bind(&Protocol::EmitEventInUI, - base::Unretained(this), - "unregistered", scheme)); -} - -void Protocol::InterceptProtocolInIO(const std::string& scheme) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - - ProtocolHandler* original_handler = job_factory_->GetProtocolHandler(scheme); - if (original_handler == NULL) { - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( - &Protocol::EmitEventInUI, - base::Unretained(this), - "error", "There is no protocol handler to intercpet")); +void Protocol::OnIOCompleted( + const CompletionCallback& callback, ProtocolError error) { + // The completion callback is optional. + if (callback.is_null()) return; - } - job_factory_->ReplaceProtocol( - scheme, new CustomProtocolHandler(this, original_handler)); - BrowserThread::PostTask(BrowserThread::UI, - FROM_HERE, - base::Bind(&Protocol::EmitEventInUI, - base::Unretained(this), - "intercepted", scheme)); + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + + if (error == PROTOCOL_OK) { + callback.Run(v8::Null(isolate())); + } else { + std::string str = ErrorCodeToString(error); + callback.Run(v8::Exception::Error(mate::StringToV8(isolate(), str))); + } } -void Protocol::UninterceptProtocolInIO(const std::string& scheme) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - - CustomProtocolHandler* handler = static_cast( - job_factory_->GetProtocolHandler(scheme)); - if (handler->original_handler() == NULL) { - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( - &Protocol::EmitEventInUI, - base::Unretained(this), - "error", "The protocol is not intercpeted")); - return; +std::string Protocol::ErrorCodeToString(ProtocolError error) { + switch (error) { + case PROTOCOL_FAIL: return "Failed to manipulate protocol factory"; + case PROTOCOL_REGISTERED: return "The scheme has been registred"; + case PROTOCOL_NOT_REGISTERED: return "The scheme has not been registred"; + case PROTOCOL_INTERCEPTED: return "The scheme has been intercepted"; + case PROTOCOL_NOT_INTERCEPTED: return "The scheme has not been intercepted"; + default: return "Unexpected error"; } - - // Reset the protocol handler to the orignal one and delete current protocol - // handler. - ProtocolHandler* original_handler = handler->ReleaseDefaultProtocolHandler(); - delete job_factory_->ReplaceProtocol(scheme, original_handler); - BrowserThread::PostTask(BrowserThread::UI, - FROM_HERE, - base::Bind(&Protocol::EmitEventInUI, - base::Unretained(this), - "unintercepted", scheme)); -} - -void Protocol::EmitEventInUI(const std::string& event, - const std::string& parameter) { - Emit(event, parameter); } // static -mate::Handle Protocol::Create(v8::Isolate* isolate) { - return CreateHandle(isolate, new Protocol); +mate::Handle Protocol::Create( + v8::Isolate* isolate, AtomBrowserContext* browser_context) { + return mate::CreateHandle(isolate, new Protocol(browser_context)); } } // namespace api @@ -339,11 +167,13 @@ mate::Handle Protocol::Create(v8::Isolate* isolate) { namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); - dict.Set("protocol", atom::api::Protocol::Create(isolate)); + auto browser_context = static_cast( + atom::AtomBrowserMainParts::Get()->browser_context()); + dict.Set("protocol", atom::api::Protocol::Create(isolate, browser_context)); } } // namespace diff --git a/atom/browser/api/atom_api_protocol.h b/atom/browser/api/atom_api_protocol.h index 03a6deb847a1..9f98eb767309 100644 --- a/atom/browser/api/atom_api_protocol.h +++ b/atom/browser/api/atom_api_protocol.h @@ -7,67 +7,180 @@ #include #include +#include -#include "atom/browser/api/event_emitter.h" +#include "atom/browser/net/atom_url_request_job_factory.h" #include "base/callback.h" +#include "base/containers/scoped_ptr_hash_map.h" +#include "content/public/browser/browser_thread.h" +#include "native_mate/arguments.h" +#include "native_mate/dictionary.h" #include "native_mate/handle.h" +#include "native_mate/wrappable.h" namespace net { class URLRequest; +class URLRequestContextGetter; } namespace atom { +class AtomBrowserContext; class AtomURLRequestJobFactory; namespace api { -class Protocol : public mate::EventEmitter { +class Protocol : public mate::Wrappable { public: - typedef base::Callback(const net::URLRequest*)> - JsProtocolHandler; + using Handler = + base::Callback)>; + using CompletionCallback = base::Callback)>; + using BooleanCallback = base::Callback; - static mate::Handle Create(v8::Isolate* isolate); - - JsProtocolHandler GetProtocolHandler(const std::string& scheme); + static mate::Handle Create( + v8::Isolate* isolate, AtomBrowserContext* browser_context); protected: - Protocol(); + explicit Protocol(AtomBrowserContext* browser_context); // mate::Wrappable implementations: virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate); private: - typedef std::map ProtocolHandlersMap; + // Possible errors. + enum ProtocolError { + PROTOCOL_OK, // no error + PROTOCOL_FAIL, // operation failed, should never occur + PROTOCOL_REGISTERED, + PROTOCOL_NOT_REGISTERED, + PROTOCOL_INTERCEPTED, + PROTOCOL_NOT_INTERCEPTED, + }; - // Register/unregister an networking |scheme| which would be handled by - // |callback|. + // The protocol handler that will create a protocol handler for certain + // request job. + template + class CustomProtocolHandler + : public net::URLRequestJobFactory::ProtocolHandler { + public: + CustomProtocolHandler( + v8::Isolate* isolate, + net::URLRequestContextGetter* request_context, + const Handler& handler) + : isolate_(isolate), + request_context_(request_context), + handler_(handler) {} + ~CustomProtocolHandler() override {} + + net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const override { + RequestJob* request_job = new RequestJob(request, network_delegate); + request_job->SetHandlerInfo(isolate_, request_context_, handler_); + return request_job; + } + + private: + v8::Isolate* isolate_; + net::URLRequestContextGetter* request_context_; + Protocol::Handler handler_; + + DISALLOW_COPY_AND_ASSIGN(CustomProtocolHandler); + }; + + // Register schemes to standard scheme list. + void RegisterStandardSchemes(const std::vector& schemes); + + // Register the protocol with certain request job. + template void RegisterProtocol(const std::string& scheme, - const JsProtocolHandler& callback); - void UnregisterProtocol(const std::string& scheme); + const Handler& handler, + mate::Arguments* args) { + CompletionCallback callback; + args->GetNext(&callback); + content::BrowserThread::PostTaskAndReplyWithResult( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&Protocol::RegisterProtocolInIO, + base::Unretained(this), scheme, handler), + base::Bind(&Protocol::OnIOCompleted, + base::Unretained(this), callback)); + } + template + ProtocolError RegisterProtocolInIO(const std::string& scheme, + const Handler& handler) { + if (job_factory_->IsHandledProtocol(scheme)) + return PROTOCOL_REGISTERED; + scoped_ptr> protocol_handler( + new CustomProtocolHandler( + isolate(), request_context_getter_, handler)); + if (job_factory_->SetProtocolHandler(scheme, protocol_handler.Pass())) + return PROTOCOL_OK; + else + return PROTOCOL_FAIL; + } - // Returns whether a scheme has been registered. - // FIXME Should accept a callback and be asynchronous so we do not have to use - // locks. - bool IsHandledProtocol(const std::string& scheme); + // Unregister the protocol handler that handles |scheme|. + void UnregisterProtocol(const std::string& scheme, mate::Arguments* args); + ProtocolError UnregisterProtocolInIO(const std::string& scheme); - // Intercept/unintercept an existing protocol handler. + // Whether the protocol has handler registered. + void IsProtocolHandled(const std::string& scheme, + const BooleanCallback& callback); + bool IsProtocolHandledInIO(const std::string& scheme); + + // Replace the protocol handler with a new one. + template void InterceptProtocol(const std::string& scheme, - const JsProtocolHandler& callback); - void UninterceptProtocol(const std::string& scheme); + const Handler& handler, + mate::Arguments* args) { + CompletionCallback callback; + args->GetNext(&callback); + content::BrowserThread::PostTaskAndReplyWithResult( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&Protocol::InterceptProtocolInIO, + base::Unretained(this), scheme, handler), + base::Bind(&Protocol::OnIOCompleted, + base::Unretained(this), callback)); + } + template + ProtocolError InterceptProtocolInIO(const std::string& scheme, + const Handler& handler) { + if (!job_factory_->IsHandledProtocol(scheme)) + return PROTOCOL_NOT_REGISTERED; + // It is possible a protocol is handled but can not be intercepted. + if (!job_factory_->HasProtocolHandler(scheme)) + return PROTOCOL_FAIL; + if (ContainsKey(original_protocols_, scheme)) + return PROTOCOL_INTERCEPTED; + scoped_ptr> protocol_handler( + new CustomProtocolHandler( + isolate(), request_context_getter_, handler)); + original_protocols_.set( + scheme, + job_factory_->ReplaceProtocol(scheme, protocol_handler.Pass())); + return PROTOCOL_OK; + } - // The networking related operations have to be done in IO thread. - void RegisterProtocolInIO(const std::string& scheme); - void UnregisterProtocolInIO(const std::string& scheme); - void InterceptProtocolInIO(const std::string& scheme); - void UninterceptProtocolInIO(const std::string& scheme); + // Restore the |scheme| to its original protocol handler. + void UninterceptProtocol(const std::string& scheme, mate::Arguments* args); + ProtocolError UninterceptProtocolInIO(const std::string& scheme); - // Do protocol.emit(event, parameter) under UI thread. - void EmitEventInUI(const std::string& event, const std::string& parameter); + // Convert error code to JS exception and call the callback. + void OnIOCompleted(const CompletionCallback& callback, ProtocolError error); - AtomURLRequestJobFactory* job_factory_; - ProtocolHandlersMap protocol_handlers_; + // Convert error code to string. + std::string ErrorCodeToString(ProtocolError error); + + net::URLRequestContextGetter* request_context_getter_; + + // Map that stores the original protocols of schemes. + using OriginalProtocolsMap = base::ScopedPtrHashMap< + std::string, + scoped_ptr>; + OriginalProtocolsMap original_protocols_; + + AtomURLRequestJobFactory* job_factory_; // weak ref DISALLOW_COPY_AND_ASSIGN(Protocol); }; diff --git a/atom/browser/api/atom_api_screen.cc b/atom/browser/api/atom_api_screen.cc index c595f350d920..b73bda9ced84 100644 --- a/atom/browser/api/atom_api_screen.cc +++ b/atom/browser/api/atom_api_screen.cc @@ -48,6 +48,7 @@ std::vector MetricsToArray(uint32_t metrics) { } // namespace Screen::Screen(gfx::Screen* screen) : screen_(screen) { + displays_ = screen_->GetAllDisplays(); screen_->AddObserver(this); } @@ -64,11 +65,6 @@ gfx::Display Screen::GetPrimaryDisplay() { } std::vector Screen::GetAllDisplays() { - // The Screen::GetAllDisplays doesn't update when there is display added or - // removed, so we have to manually maintain the displays_ to make it up to - // date. - if (displays_.size() == 0) - displays_ = screen_->GetAllDisplays(); return displays_; } @@ -115,15 +111,18 @@ mate::ObjectTemplateBuilder Screen::GetObjectTemplateBuilder( } // static -v8::Handle Screen::Create(v8::Isolate* isolate) { +v8::Local Screen::Create(v8::Isolate* isolate) { if (!Browser::Get()->is_ready()) { - node::ThrowError("Cannot initialize \"screen\" module before app is ready"); + isolate->ThrowException(v8::Exception::Error(mate::StringToV8( + isolate, + "Cannot initialize \"screen\" module before app is ready"))); return v8::Null(isolate); } gfx::Screen* screen = gfx::Screen::GetNativeScreen(); if (!screen) { - node::ThrowError("Failed to get screen information"); + isolate->ThrowException(v8::Exception::Error(mate::StringToV8( + isolate, "Failed to get screen information"))); return v8::Null(isolate); } @@ -136,8 +135,8 @@ v8::Handle Screen::Create(v8::Isolate* isolate) { namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); dict.Set("screen", atom::api::Screen::Create(context->GetIsolate())); } diff --git a/atom/browser/api/atom_api_screen.h b/atom/browser/api/atom_api_screen.h index 5a81f3d67057..f724130fa7fc 100644 --- a/atom/browser/api/atom_api_screen.h +++ b/atom/browser/api/atom_api_screen.h @@ -24,7 +24,7 @@ namespace api { class Screen : public mate::EventEmitter, public gfx::DisplayObserver { public: - static v8::Handle Create(v8::Isolate* isolate); + static v8::Local Create(v8::Isolate* isolate); protected: explicit Screen(gfx::Screen* screen); diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc new file mode 100644 index 000000000000..f07ab8b78045 --- /dev/null +++ b/atom/browser/api/atom_api_session.cc @@ -0,0 +1,372 @@ +// 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 "atom/browser/api/atom_api_session.h" + +#include +#include + +#include "atom/browser/api/atom_api_cookies.h" +#include "atom/browser/atom_browser_context.h" +#include "atom/browser/api/atom_api_web_contents.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/gurl_converter.h" +#include "atom/common/native_mate_converters/file_path_converter.h" +#include "atom/common/node_includes.h" +#include "base/files/file_path.h" +#include "base/prefs/pref_service.h" +#include "base/strings/string_util.h" +#include "base/thread_task_runner_handle.h" +#include "chrome/common/pref_names.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/storage_partition.h" +#include "native_mate/dictionary.h" +#include "native_mate/object_template_builder.h" +#include "net/base/load_flags.h" +#include "net/disk_cache/disk_cache.h" +#include "net/proxy/proxy_service.h" +#include "net/proxy/proxy_config_service_fixed.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" + +using content::BrowserThread; +using content::StoragePartition; + +namespace { + +struct ClearStorageDataOptions { + GURL origin; + uint32 storage_types = StoragePartition::REMOVE_DATA_MASK_ALL; + uint32 quota_types = StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL; +}; + +uint32 GetStorageMask(const std::vector& storage_types) { + uint32 storage_mask = 0; + for (const auto& it : storage_types) { + auto type = base::StringToLowerASCII(it); + if (type == "appcache") + storage_mask |= StoragePartition::REMOVE_DATA_MASK_APPCACHE; + else if (type == "cookies") + storage_mask |= StoragePartition::REMOVE_DATA_MASK_COOKIES; + else if (type == "filesystem") + storage_mask |= StoragePartition::REMOVE_DATA_MASK_FILE_SYSTEMS; + else if (type == "indexdb") + storage_mask |= StoragePartition::REMOVE_DATA_MASK_INDEXEDDB; + else if (type == "localstorage") + storage_mask |= StoragePartition::REMOVE_DATA_MASK_LOCAL_STORAGE; + else if (type == "shadercache") + storage_mask |= StoragePartition::REMOVE_DATA_MASK_SHADER_CACHE; + else if (type == "websql") + storage_mask |= StoragePartition::REMOVE_DATA_MASK_WEBSQL; + else if (type == "serviceworkers") + storage_mask |= StoragePartition::REMOVE_DATA_MASK_SERVICE_WORKERS; + } + return storage_mask; +} + +uint32 GetQuotaMask(const std::vector& quota_types) { + uint32 quota_mask = 0; + for (const auto& it : quota_types) { + auto type = base::StringToLowerASCII(it); + if (type == "temporary") + quota_mask |= StoragePartition::QUOTA_MANAGED_STORAGE_MASK_TEMPORARY; + else if (type == "persistent") + quota_mask |= StoragePartition::QUOTA_MANAGED_STORAGE_MASK_PERSISTENT; + else if (type == "syncable") + quota_mask |= StoragePartition::QUOTA_MANAGED_STORAGE_MASK_SYNCABLE; + } + return quota_mask; +} + +} // namespace + +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + ClearStorageDataOptions* out) { + mate::Dictionary options; + if (!ConvertFromV8(isolate, val, &options)) + return false; + options.Get("origin", &out->origin); + std::vector types; + if (options.Get("storages", &types)) + out->storage_types = GetStorageMask(types); + if (options.Get("quotas", &types)) + out->quota_types = GetQuotaMask(types); + return true; + } +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + content::DownloadItem* val) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + dict.Set("url", val->GetURL()); + dict.Set("filename", val->GetSuggestedFilename()); + dict.Set("mimeType", val->GetMimeType()); + dict.Set("hasUserGesture", val->HasUserGesture()); + return dict.GetHandle(); + } +}; + +} // namespace mate + +namespace atom { + +namespace api { + +namespace { + +// The wrapSession funtion which is implemented in JavaScript +using WrapSessionCallback = base::Callback)>; +WrapSessionCallback g_wrap_session; + +class ResolveProxyHelper { + public: + ResolveProxyHelper(AtomBrowserContext* browser_context, + const GURL& url, + Session::ResolveProxyCallback callback) + : callback_(callback), + original_thread_(base::ThreadTaskRunnerHandle::Get()) { + scoped_refptr context_getter = + browser_context->GetRequestContext(); + context_getter->GetNetworkTaskRunner()->PostTask( + FROM_HERE, + base::Bind(&ResolveProxyHelper::ResolveProxy, + base::Unretained(this), context_getter, url)); + } + + void OnResolveProxyCompleted(int result) { + std::string proxy; + if (result == net::OK) + proxy = proxy_info_.ToPacString(); + original_thread_->PostTask(FROM_HERE, + base::Bind(callback_, proxy)); + delete this; + } + + private: + void ResolveProxy(scoped_refptr context_getter, + const GURL& url) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + net::ProxyService* proxy_service = + context_getter->GetURLRequestContext()->proxy_service(); + net::CompletionCallback completion_callback = + base::Bind(&ResolveProxyHelper::OnResolveProxyCompleted, + base::Unretained(this)); + + // Start the request. + int result = proxy_service->ResolveProxy( + url, net::LOAD_NORMAL, &proxy_info_, completion_callback, + &pac_req_, nullptr, net::BoundNetLog()); + + // Completed synchronously. + if (result != net::ERR_IO_PENDING) + completion_callback.Run(result); + } + + Session::ResolveProxyCallback callback_; + net::ProxyInfo proxy_info_; + net::ProxyService::PacRequest* pac_req_; + scoped_refptr original_thread_; + + DISALLOW_COPY_AND_ASSIGN(ResolveProxyHelper); +}; + +// Runs the callback in UI thread. +template +void RunCallbackInUI(const base::Callback& callback, T... result) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, base::Bind(callback, result...)); +} + +// Callback of HttpCache::GetBackend. +void OnGetBackend(disk_cache::Backend** backend_ptr, + const net::CompletionCallback& callback, + int result) { + if (result != net::OK) { + RunCallbackInUI(callback, result); + } else if (backend_ptr && *backend_ptr) { + (*backend_ptr)->DoomAllEntries(base::Bind(&RunCallbackInUI, callback)); + } else { + RunCallbackInUI(callback, net::ERR_FAILED); + } +} + +void ClearHttpCacheInIO( + const scoped_refptr& context_getter, + const net::CompletionCallback& callback) { + auto request_context = context_getter->GetURLRequestContext(); + auto http_cache = request_context->http_transaction_factory()->GetCache(); + if (!http_cache) + RunCallbackInUI(callback, net::ERR_FAILED); + + // Call GetBackend and make the backend's ptr accessable in OnGetBackend. + using BackendPtr = disk_cache::Backend*; + BackendPtr* backend_ptr = new BackendPtr(nullptr); + net::CompletionCallback on_get_backend = + base::Bind(&OnGetBackend, base::Owned(backend_ptr), callback); + int rv = http_cache->GetBackend(backend_ptr, on_get_backend); + if (rv != net::ERR_IO_PENDING) + on_get_backend.Run(net::OK); +} + +void SetProxyInIO(net::URLRequestContextGetter* getter, + const std::string& proxy, + const base::Closure& callback) { + net::ProxyConfig config; + config.proxy_rules().ParseFromString(proxy); + auto proxy_service = getter->GetURLRequestContext()->proxy_service(); + proxy_service->ResetConfigService(new net::ProxyConfigServiceFixed(config)); + RunCallbackInUI(callback); +} + +} // namespace + +Session::Session(AtomBrowserContext* browser_context) + : browser_context_(browser_context) { + AttachAsUserData(browser_context); + + // Observe DownloadManger to get download notifications. + content::BrowserContext::GetDownloadManager(browser_context)-> + AddObserver(this); +} + +Session::~Session() { + content::BrowserContext::GetDownloadManager(browser_context())-> + RemoveObserver(this); + Destroy(); +} + +void Session::OnDownloadCreated(content::DownloadManager* manager, + content::DownloadItem* item) { + auto web_contents = item->GetWebContents(); + bool prevent_default = Emit("will-download", item, + api::WebContents::CreateFrom(isolate(), + web_contents)); + if (prevent_default) { + item->Cancel(true); + item->Remove(); + } +} + +bool Session::IsDestroyed() const { + return !browser_context_; +} + +void Session::Destroy() { + browser_context_ = nullptr; +} + +void Session::ResolveProxy(const GURL& url, ResolveProxyCallback callback) { + new ResolveProxyHelper(browser_context(), url, callback); +} + +void Session::ClearCache(const net::CompletionCallback& callback) { + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&ClearHttpCacheInIO, + make_scoped_refptr(browser_context_->GetRequestContext()), + callback)); +} + +void Session::ClearStorageData(mate::Arguments* args) { + // clearStorageData([options, ]callback) + ClearStorageDataOptions options; + args->GetNext(&options); + base::Closure callback; + if (!args->GetNext(&callback)) { + args->ThrowError(); + return; + } + + auto storage_partition = + content::BrowserContext::GetStoragePartition(browser_context(), nullptr); + storage_partition->ClearData( + options.storage_types, options.quota_types, options.origin, + content::StoragePartition::OriginMatcherFunction(), + base::Time(), base::Time::Max(), callback); +} + +void Session::SetProxy(const std::string& proxy, + const base::Closure& callback) { + auto getter = browser_context_->GetRequestContext(); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&SetProxyInIO, base::Unretained(getter), proxy, callback)); +} + +void Session::SetDownloadPath(const base::FilePath& path) { + browser_context_->prefs()->SetFilePath( + prefs::kDownloadDefaultDirectory, path); +} + +v8::Local Session::Cookies(v8::Isolate* isolate) { + if (cookies_.IsEmpty()) { + auto handle = atom::api::Cookies::Create(isolate, browser_context()); + cookies_.Reset(isolate, handle.ToV8()); + } + return v8::Local::New(isolate, cookies_); +} + +mate::ObjectTemplateBuilder Session::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return mate::ObjectTemplateBuilder(isolate) + .SetMethod("resolveProxy", &Session::ResolveProxy) + .SetMethod("clearCache", &Session::ClearCache) + .SetMethod("clearStorageData", &Session::ClearStorageData) + .SetMethod("setProxy", &Session::SetProxy) + .SetMethod("setDownloadPath", &Session::SetDownloadPath) + .SetProperty("cookies", &Session::Cookies); +} + +// static +mate::Handle Session::CreateFrom( + v8::Isolate* isolate, AtomBrowserContext* browser_context) { + auto existing = TrackableObject::FromWrappedClass(isolate, browser_context); + if (existing) + return mate::CreateHandle(isolate, static_cast(existing)); + + auto handle = mate::CreateHandle(isolate, new Session(browser_context)); + g_wrap_session.Run(handle.ToV8()); + return handle; +} + +// static +mate::Handle Session::FromPartition( + v8::Isolate* isolate, const std::string& partition, bool in_memory) { + auto browser_context = brightray::BrowserContext::From(partition, in_memory); + return CreateFrom(isolate, + static_cast(browser_context.get())); +} + +void SetWrapSession(const WrapSessionCallback& callback) { + g_wrap_session = callback; +} + +void ClearWrapSession() { + g_wrap_session.Reset(); +} + +} // 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("fromPartition", &atom::api::Session::FromPartition); + dict.SetMethod("_setWrapSession", &atom::api::SetWrapSession); + dict.SetMethod("_clearWrapSession", &atom::api::ClearWrapSession); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_session, Initialize) diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h new file mode 100644 index 000000000000..14406e57af5b --- /dev/null +++ b/atom/browser/api/atom_api_session.h @@ -0,0 +1,82 @@ +// 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_BROWSER_API_ATOM_API_SESSION_H_ +#define ATOM_BROWSER_API_ATOM_API_SESSION_H_ + +#include + +#include "atom/browser/api/trackable_object.h" +#include "content/public/browser/download_manager.h" +#include "native_mate/handle.h" +#include "net/base/completion_callback.h" + +class GURL; + +namespace base { +class FilePath; +} + +namespace mate { +class Arguments; +} + +namespace atom { + +class AtomBrowserContext; + +namespace api { + +class Session: public mate::TrackableObject, + public content::DownloadManager::Observer { + public: + using ResolveProxyCallback = base::Callback; + + // Gets or creates Session from the |browser_context|. + static mate::Handle CreateFrom( + v8::Isolate* isolate, AtomBrowserContext* browser_context); + + // Gets the Session of |partition| and |in_memory|. + static mate::Handle FromPartition( + v8::Isolate* isolate, const std::string& partition, bool in_memory); + + AtomBrowserContext* browser_context() const { return browser_context_.get(); } + + protected: + explicit Session(AtomBrowserContext* browser_context); + ~Session(); + + // content::DownloadManager::Observer: + void OnDownloadCreated(content::DownloadManager* manager, + content::DownloadItem* item) override; + + // mate::Wrappable: + mate::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) override; + bool IsDestroyed() const override; + + private: + // mate::TrackableObject: + void Destroy() override; + + void ResolveProxy(const GURL& url, ResolveProxyCallback callback); + void ClearCache(const net::CompletionCallback& callback); + void ClearStorageData(mate::Arguments* args); + void SetProxy(const std::string& proxy, const base::Closure& callback); + void SetDownloadPath(const base::FilePath& path); + v8::Local Cookies(v8::Isolate* isolate); + + // Cached object for cookies API. + v8::Global cookies_; + + scoped_refptr browser_context_; + + DISALLOW_COPY_AND_ASSIGN(Session); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_SESSION_H_ diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index b66d95cb0732..0f5829e19c14 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -12,12 +12,12 @@ #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" +#include "atom/common/node_includes.h" #include "native_mate/constructor.h" #include "native_mate/dictionary.h" +#include "ui/events/event_constants.h" #include "ui/gfx/image/image.h" -#include "atom/common/node_includes.h" - namespace atom { namespace api { @@ -32,20 +32,34 @@ Tray::~Tray() { } // static -mate::Wrappable* Tray::New(const gfx::Image& image) { +mate::Wrappable* Tray::New(v8::Isolate* isolate, const gfx::Image& image) { if (!Browser::Get()->is_ready()) { - node::ThrowError("Cannot create Tray before app is ready"); + isolate->ThrowException(v8::Exception::Error(mate::StringToV8( + isolate, "Cannot create Tray before app is ready"))); return nullptr; } return new Tray(image); } -void Tray::OnClicked(const gfx::Rect& bounds) { - Emit("clicked", bounds); +void Tray::OnClicked(const gfx::Rect& bounds, int modifiers) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + EmitCustomEvent("clicked", + ModifiersToObject(isolate(), modifiers), bounds); } -void Tray::OnDoubleClicked() { - Emit("double-clicked"); +void Tray::OnDoubleClicked(const gfx::Rect& bounds, int modifiers) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + EmitCustomEvent("double-clicked", + ModifiersToObject(isolate(), modifiers), bounds); +} + +void Tray::OnRightClicked(const gfx::Rect& bounds, int modifiers) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + EmitCustomEvent("right-clicked", + ModifiersToObject(isolate(), modifiers), bounds); } void Tray::OnBalloonShow() { @@ -60,45 +74,40 @@ void Tray::OnBalloonClosed() { Emit("balloon-closed"); } +void Tray::OnDropFiles(const std::vector& files) { + Emit("drop-files", files); +} + +bool Tray::IsDestroyed() const { + return !tray_icon_; +} + void Tray::Destroy() { tray_icon_.reset(); } void Tray::SetImage(mate::Arguments* args, const gfx::Image& image) { - if (!CheckTrayLife(args)) - return; tray_icon_->SetImage(image); } void Tray::SetPressedImage(mate::Arguments* args, const gfx::Image& image) { - if (!CheckTrayLife(args)) - return; tray_icon_->SetPressedImage(image); } void Tray::SetToolTip(mate::Arguments* args, const std::string& tool_tip) { - if (!CheckTrayLife(args)) - return; tray_icon_->SetToolTip(tool_tip); } void Tray::SetTitle(mate::Arguments* args, const std::string& title) { - if (!CheckTrayLife(args)) - return; tray_icon_->SetTitle(title); } void Tray::SetHighlightMode(mate::Arguments* args, bool highlight) { - if (!CheckTrayLife(args)) - return; tray_icon_->SetHighlightMode(highlight); } void Tray::DisplayBalloon(mate::Arguments* args, const mate::Dictionary& options) { - if (!CheckTrayLife(args)) - return; - gfx::Image icon; options.Get("icon", &icon); base::string16 title, content; @@ -111,32 +120,38 @@ void Tray::DisplayBalloon(mate::Arguments* args, tray_icon_->DisplayBalloon(icon, title, content); } +void Tray::PopUpContextMenu(mate::Arguments* args) { + gfx::Point pos; + args->GetNext(&pos); + tray_icon_->PopUpContextMenu(pos); +} + void Tray::SetContextMenu(mate::Arguments* args, Menu* menu) { - if (!CheckTrayLife(args)) - return; tray_icon_->SetContextMenu(menu->model()); } -bool Tray::CheckTrayLife(mate::Arguments* args) { - if (!tray_icon_) { - args->ThrowError("Tray is already destroyed"); - return false; - } else { - return true; - } +v8::Local Tray::ModifiersToObject(v8::Isolate* isolate, + int modifiers) { + mate::Dictionary obj(isolate, v8::Object::New(isolate)); + obj.Set("shiftKey", static_cast(modifiers & ui::EF_SHIFT_DOWN)); + obj.Set("ctrlKey", static_cast(modifiers & ui::EF_CONTROL_DOWN)); + obj.Set("altKey", static_cast(modifiers & ui::EF_ALT_DOWN)); + obj.Set("metaKey", static_cast(modifiers & ui::EF_COMMAND_DOWN)); + return obj.GetHandle(); } // static void Tray::BuildPrototype(v8::Isolate* isolate, - v8::Handle prototype) { + v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) - .SetMethod("destroy", &Tray::Destroy) + .SetMethod("destroy", &Tray::Destroy, true) .SetMethod("setImage", &Tray::SetImage) .SetMethod("setPressedImage", &Tray::SetPressedImage) .SetMethod("setToolTip", &Tray::SetToolTip) .SetMethod("setTitle", &Tray::SetTitle) .SetMethod("setHighlightMode", &Tray::SetHighlightMode) .SetMethod("displayBalloon", &Tray::DisplayBalloon) + .SetMethod("popUpContextMenu", &Tray::PopUpContextMenu) .SetMethod("_setContextMenu", &Tray::SetContextMenu); } @@ -147,14 +162,14 @@ void Tray::BuildPrototype(v8::Isolate* isolate, namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { using atom::api::Tray; v8::Isolate* isolate = context->GetIsolate(); - v8::Handle constructor = mate::CreateConstructor( + v8::Local constructor = mate::CreateConstructor( isolate, "Tray", base::Bind(&Tray::New)); mate::Dictionary dict(isolate, exports); - dict.Set("Tray", static_cast>(constructor)); + dict.Set("Tray", static_cast>(constructor)); } } // namespace diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 5ed29ecd74f1..dc9302597cf3 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_API_ATOM_API_TRAY_H_ #include +#include #include "atom/browser/api/event_emitter.h" #include "atom/browser/ui/tray_icon_observer.h" @@ -31,21 +32,26 @@ class Menu; class Tray : public mate::EventEmitter, public TrayIconObserver { public: - static mate::Wrappable* New(const gfx::Image& image); + static mate::Wrappable* New(v8::Isolate* isolate, const gfx::Image& image); static void BuildPrototype(v8::Isolate* isolate, - v8::Handle prototype); + v8::Local prototype); protected: explicit Tray(const gfx::Image& image); virtual ~Tray(); // TrayIconObserver: - void OnClicked(const gfx::Rect&) override; - void OnDoubleClicked() override; + void OnClicked(const gfx::Rect& bounds, int modifiers) override; + void OnDoubleClicked(const gfx::Rect& bounds, int modifiers) override; + void OnRightClicked(const gfx::Rect& bounds, int modifiers) override; void OnBalloonShow() override; void OnBalloonClicked() override; void OnBalloonClosed() override; + void OnDropFiles(const std::vector& files) override; + + // mate::Wrappable: + bool IsDestroyed() const override; void Destroy(); void SetImage(mate::Arguments* args, const gfx::Image& image); @@ -54,10 +60,11 @@ class Tray : public mate::EventEmitter, void SetTitle(mate::Arguments* args, const std::string& title); void SetHighlightMode(mate::Arguments* args, bool highlight); void DisplayBalloon(mate::Arguments* args, const mate::Dictionary& options); + void PopUpContextMenu(mate::Arguments* args); void SetContextMenu(mate::Arguments* args, Menu* menu); private: - bool CheckTrayLife(mate::Arguments* args); + v8::Local ModifiersToObject(v8::Isolate* isolate, int modifiers); scoped_ptr tray_icon_; diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index e228a064f413..9791a94bb77b 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -6,24 +6,34 @@ #include +#include "atom/browser/api/atom_api_session.h" #include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_context.h" -#include "atom/browser/atom_javascript_dialog_manager.h" +#include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/native_window.h" -#include "atom/browser/web_dialog_helper.h" -#include "atom/browser/web_view_manager.h" +#include "atom/browser/web_contents_preferences.h" +#include "atom/browser/web_view_guest_delegate.h" #include "atom/common/api/api_messages.h" +#include "atom/common/api/event_emitter_caller.h" +#include "atom/common/native_mate_converters/blink_converter.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #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 "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "brightray/browser/inspectable_web_contents.h" -#include "brightray/browser/media/media_stream_devices_controller.h" +#include "chrome/browser/printing/print_view_manager_basic.h" +#include "chrome/browser/printing/print_preview_message_handler.h" +#include "content/common/view_messages.h" #include "content/public/browser/favicon_status.h" +#include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" +#include "content/public/browser/plugin_service.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" @@ -33,12 +43,86 @@ #include "content/public/browser/storage_partition.h" #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/static_http_user_agent_settings.h" +#include "net/url_request/url_request_context.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" #include "atom/common/node_includes.h" +namespace { + +struct PrintSettings { + bool silent; + bool print_background; +}; + +void SetUserAgentInIO(scoped_refptr getter, + std::string user_agent) { + getter->GetURLRequestContext()->set_http_user_agent_settings( + new net::StaticHttpUserAgentSettings("en-us,en", user_agent)); +} + +} // namespace + +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + atom::SetSizeParams* out) { + mate::Dictionary params; + if (!ConvertFromV8(isolate, val, ¶ms)) + return false; + bool autosize; + if (params.Get("enableAutoSize", &autosize)) + out->enable_auto_size.reset(new bool(true)); + gfx::Size size; + if (params.Get("min", &size)) + out->min_size.reset(new gfx::Size(size)); + if (params.Get("max", &size)) + out->max_size.reset(new gfx::Size(size)); + if (params.Get("normal", &size)) + out->normal_size.reset(new gfx::Size(size)); + return true; + } +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + PrintSettings* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + dict.Get("silent", &(out->silent)); + dict.Get("printBackground", &(out->print_background)); + return true; + } +}; + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + WindowOpenDisposition val) { + std::string disposition = "other"; + switch (val) { + case CURRENT_TAB: disposition = "default"; break; + case NEW_FOREGROUND_TAB: disposition = "foreground-tab"; break; + case NEW_BACKGROUND_TAB: disposition = "background-tab"; break; + case NEW_POPUP: case NEW_WINDOW: disposition = "new-window"; break; + default: break; + } + return mate::ConvertToV8(isolate, disposition); + } +}; + +} // namespace mate + + namespace atom { namespace api { @@ -47,16 +131,9 @@ namespace { v8::Persistent template_; -// Get the window that has the |guest| embedded. -NativeWindow* GetWindowFromGuest(const content::WebContents* guest) { - WebViewManager::WebViewInfo info; - if (WebViewManager::GetInfoForProcess(guest->GetRenderProcessHost(), &info)) - return NativeWindow::FromRenderView( - info.embedder->GetRenderProcessHost()->GetID(), - info.embedder->GetRoutingID()); - else - return nullptr; -} +// The wrapWebContents funtion which is implemented in JavaScript +using WrapWebContentsCallback = base::Callback)>; +WrapWebContentsCallback g_wrap_web_contents; content::ServiceWorkerContext* GetServiceWorkerContext( const content::WebContents* web_contents) { @@ -65,11 +142,10 @@ content::ServiceWorkerContext* GetServiceWorkerContext( if (!context || !site_instance) return nullptr; - content::StoragePartition* storage_partition = - content::BrowserContext::GetStoragePartition( - context, site_instance); - - DCHECK(storage_partition); + auto storage_partition = + content::BrowserContext::GetStoragePartition(context, site_instance); + if (!storage_partition) + return nullptr; return storage_partition->GetServiceWorkerContext(); } @@ -78,33 +154,74 @@ content::ServiceWorkerContext* GetServiceWorkerContext( WebContents::WebContents(content::WebContents* web_contents) : content::WebContentsObserver(web_contents), - guest_instance_id_(-1), - element_instance_id_(-1), - guest_opaque_(true), - guest_sizer_(nullptr), - auto_size_enabled_(false) { + type_(REMOTE) { + AttachAsUserData(web_contents); + web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent()); } -WebContents::WebContents(const mate::Dictionary& options) - : guest_instance_id_(-1), - element_instance_id_(-1), - guest_opaque_(true), - guest_sizer_(nullptr), - auto_size_enabled_(false) { - options.Get("guestInstanceId", &guest_instance_id_); +WebContents::WebContents(v8::Isolate* isolate, + const mate::Dictionary& options) { + // Whether it is a guest WebContents. + bool is_guest = false; + options.Get("isGuest", &is_guest); + type_ = is_guest ? WEB_VIEW : BROWSER_WINDOW; - auto browser_context = AtomBrowserContext::Get(); - content::SiteInstance* site_instance = content::SiteInstance::CreateForURL( - browser_context, GURL("chrome-guest://fake-host")); + // Obtain the session. + std::string partition; + mate::Handle session; + if (options.Get("session", &session)) { + } else if (options.Get("partition", &partition) && !partition.empty()) { + bool in_memory = true; + if (base::StartsWith(partition, "persist:", base::CompareCase::SENSITIVE)) { + in_memory = false; + partition = partition.substr(8); + } + session = Session::FromPartition(isolate, partition, in_memory); + } else { + // Use the default session if not specified. + session = Session::FromPartition(isolate, "", false); + } + session_.Reset(isolate, session.ToV8()); - content::WebContents::CreateParams params(browser_context, site_instance); - bool is_guest; - if (options.Get("isGuest", &is_guest) && is_guest) - params.guest_delegate = this; + content::WebContents* web_contents; + if (is_guest) { + content::SiteInstance* site_instance = content::SiteInstance::CreateForURL( + session->browser_context(), GURL("chrome-guest://fake-host")); + content::WebContents::CreateParams params( + session->browser_context(), site_instance); + guest_delegate_.reset(new WebViewGuestDelegate); + params.guest_delegate = guest_delegate_.get(); + web_contents = content::WebContents::Create(params); + } else { + content::WebContents::CreateParams params(session->browser_context()); + web_contents = content::WebContents::Create(params); + } - storage_.reset(brightray::InspectableWebContents::Create(params)); - Observe(storage_->GetWebContents()); - web_contents()->SetDelegate(this); + Observe(web_contents); + AttachAsUserData(web_contents); + InitWithWebContents(web_contents); + + // Save the preferences. + base::DictionaryValue web_preferences; + mate::ConvertFromV8(isolate, options.GetHandle(), &web_preferences); + new WebContentsPreferences(web_contents, &web_preferences); + + 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) { + // New WebContents's owner_window is the embedder's owner_window. + auto relay = NativeWindowRelay::FromWebContents(embedder->web_contents()); + if (relay) + owner_window = relay->window.get(); + } + if (owner_window) + SetOwnerWindow(owner_window); + } } WebContents::~WebContents() { @@ -116,8 +233,12 @@ bool WebContents::AddMessageToConsole(content::WebContents* source, const base::string16& message, int32 line_no, const base::string16& source_id) { - Emit("console-message", level, message, line_no, source_id); - return true; + if (type_ == BROWSER_WINDOW) { + return false; + } else { + Emit("console-message", level, message, line_no, source_id); + return true; + } } bool WebContents::ShouldCreateWebContents( @@ -125,26 +246,25 @@ bool WebContents::ShouldCreateWebContents( int route_id, int main_frame_route_id, WindowContainerType window_container_type, - const base::string16& frame_name, + const std::string& frame_name, const GURL& target_url, const std::string& partition_id, content::SessionStorageNamespace* session_storage_namespace) { - Emit("-new-window", - target_url, - frame_name, - static_cast(NEW_FOREGROUND_TAB)); + if (type_ == BROWSER_WINDOW) + Emit("-new-window", target_url, frame_name, NEW_FOREGROUND_TAB); + else + Emit("new-window", target_url, frame_name, NEW_FOREGROUND_TAB); return false; } -void WebContents::CloseContents(content::WebContents* source) { - Emit("close"); -} - content::WebContents* WebContents::OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) { if (params.disposition != CURRENT_TAB) { - Emit("-new-window", params.url, "", static_cast(params.disposition)); + if (type_ == BROWSER_WINDOW) + Emit("-new-window", params.url, "", params.disposition); + else + Emit("new-window", params.url, "", params.disposition); return nullptr; } @@ -152,79 +272,105 @@ content::WebContents* WebContents::OpenURLFromTab( if (Emit("will-navigate", params.url)) return nullptr; - content::NavigationController::LoadURLParams load_url_params(params.url); - load_url_params.referrer = params.referrer; - load_url_params.transition_type = params.transition; - load_url_params.extra_headers = params.extra_headers; - 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; - - web_contents()->GetController().LoadURLWithParams(load_url_params); - return web_contents(); + return CommonWebContentsDelegate::OpenURLFromTab(source, params); } -content::JavaScriptDialogManager* WebContents::GetJavaScriptDialogManager( - content::WebContents* source) { - if (!dialog_manager_) - dialog_manager_.reset(new AtomJavaScriptDialogManager); - - return dialog_manager_.get(); +void WebContents::BeforeUnloadFired(content::WebContents* tab, + bool proceed, + bool* proceed_to_fire_unload) { + if (type_ == BROWSER_WINDOW) + *proceed_to_fire_unload = proceed; + else + *proceed_to_fire_unload = true; } -void WebContents::RunFileChooser(content::WebContents* guest, - const content::FileChooserParams& params) { - if (!web_dialog_helper_) - web_dialog_helper_.reset(new WebDialogHelper(GetWindowFromGuest(guest))); - web_dialog_helper_->RunFileChooser(guest, params); +void WebContents::MoveContents(content::WebContents* source, + const gfx::Rect& pos) { + Emit("move", pos); } -void WebContents::EnumerateDirectory(content::WebContents* guest, - int request_id, - const base::FilePath& path) { - if (!web_dialog_helper_) - web_dialog_helper_.reset(new WebDialogHelper(GetWindowFromGuest(guest))); - web_dialog_helper_->EnumerateDirectory(guest, request_id, path); +void WebContents::CloseContents(content::WebContents* source) { + Emit("close"); + if (type_ == BROWSER_WINDOW) + owner_window()->CloseContents(source); } -bool WebContents::CheckMediaAccessPermission(content::WebContents* web_contents, - const GURL& security_origin, - content::MediaStreamType type) { - return true; +void WebContents::ActivateContents(content::WebContents* source) { + Emit("activate"); } -void WebContents::RequestMediaAccessPermission( - content::WebContents*, - const content::MediaStreamRequest& request, - const content::MediaResponseCallback& callback) { - brightray::MediaStreamDevicesController controller(request, callback); - controller.TakeAction(); +bool WebContents::IsPopupOrPanel(const content::WebContents* source) const { + return type_ == BROWSER_WINDOW; } void WebContents::HandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) { - if (!attached()) - return; + if (event.windowsKeyCode == ui::VKEY_ESCAPE && is_html_fullscreen()) { + // Escape exits tabbed fullscreen mode. + ExitFullscreenModeForTab(source); + } else if (type_ == BROWSER_WINDOW) { + owner_window()->HandleKeyboardEvent(source, event); + } else if (type_ == WEB_VIEW && guest_delegate_) { + // Send the unhandled keyboard events back to the embedder. + guest_delegate_->HandleKeyboardEvent(source, event); + } +} - // Send the unhandled keyboard events back to the embedder to reprocess them. - embedder_web_contents_->GetDelegate()->HandleKeyboardEvent( - web_contents(), event); +void WebContents::EnterFullscreenModeForTab(content::WebContents* source, + const GURL& origin) { + CommonWebContentsDelegate::EnterFullscreenModeForTab(source, origin); + Emit("enter-html-full-screen"); +} + +void WebContents::ExitFullscreenModeForTab(content::WebContents* source) { + CommonWebContentsDelegate::ExitFullscreenModeForTab(source); + Emit("leave-html-full-screen"); +} + +void WebContents::RendererUnresponsive(content::WebContents* source) { + Emit("unresponsive"); + if (type_ == BROWSER_WINDOW) + owner_window()->RendererUnresponsive(source); +} + +void WebContents::RendererResponsive(content::WebContents* source) { + Emit("responsive"); + if (type_ == BROWSER_WINDOW) + owner_window()->RendererResponsive(source); +} + +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) { - Emit("render-view-deleted", - render_view_host->GetProcess()->GetID(), - render_view_host->GetRoutingID()); + 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); } void WebContents::RenderProcessGone(base::TerminationStatus status) { Emit("crashed"); } +void WebContents::PluginCrashed(const base::FilePath& plugin_path, + base::ProcessId plugin_pid) { + content::WebPluginInfo info; + auto plugin_service = content::PluginService::GetInstance(); + plugin_service->GetPluginInfoByPath(plugin_path, &info); + Emit("plugin-crashed", info.name, info.version); +} + void WebContents::DocumentLoadedInFrame( content::RenderFrameHost* render_frame_host) { if (!render_frame_host->GetParent()) @@ -245,34 +391,61 @@ void WebContents::DidFailProvisionalLoad( content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, - const base::string16& error_description) { - Emit("did-fail-load", error_code, error_description); + const base::string16& error_description, + bool was_ignored_by_handler) { + Emit("did-fail-load", error_code, error_description, validated_url); } void WebContents::DidFailLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, - const base::string16& error_description) { - Emit("did-fail-load", error_code, error_description); + const base::string16& error_description, + bool was_ignored_by_handler) { + Emit("did-fail-load", error_code, error_description, validated_url); } -void WebContents::DidStartLoading(content::RenderViewHost* render_view_host) { +void WebContents::DidStartLoading() { Emit("did-start-loading"); } -void WebContents::DidStopLoading(content::RenderViewHost* render_view_host) { +void WebContents::DidStopLoading() { Emit("did-stop-loading"); } void WebContents::DidGetResourceResponseStart( const content::ResourceRequestDetails& details) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + base::DictionaryValue response_headers; + + net::HttpResponseHeaders* headers = details.headers.get(); + if (!headers) + return; + void* iter = nullptr; + std::string key; + std::string value; + while (headers->EnumerateHeaderLines(&iter, &key, &value)) { + key = base::StringToLowerASCII(key); + value = base::StringToLowerASCII(value); + if (response_headers.HasKey(key)) { + base::ListValue* values = nullptr; + if (response_headers.GetList(key, &values)) + values->AppendString(value); + } else { + scoped_ptr values(new base::ListValue()); + values->AppendString(value); + response_headers.Set(key, values.Pass()); + } + } + Emit("did-get-response-details", details.socket_address.IsEmpty(), details.url, details.original_url, details.http_response_code, details.method, - details.referrer); + details.referrer, + response_headers); } void WebContents::DidGetRedirectForResourceRequest( @@ -323,29 +496,11 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { return handled; } -void WebContents::RenderViewReady() { - if (!is_guest()) - return; - - // 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. - if (guest_opaque_) { - web_contents() - ->GetRenderViewHost() - ->GetView() - ->SetBackgroundColorToDefault(); - } else { - web_contents()->GetRenderViewHost()->GetView()->SetBackgroundColor( - SK_ColorTRANSPARENT); - } -} - void WebContents::WebContentsDestroyed() { // The RenderViewDeleted was not called when the WebContents is destroyed. RenderViewDeleted(web_contents()->GetRenderViewHost()); Emit("destroyed"); + RemoveFromWeakMap(); } void WebContents::NavigationEntryCommitted( @@ -354,55 +509,16 @@ void WebContents::NavigationEntryCommitted( details.is_in_page, details.did_replace_entry); } -void WebContents::DidAttach(int guest_proxy_routing_id) { - Emit("did-attach"); -} - -void WebContents::ElementSizeChanged(const gfx::Size& size) { - element_size_ = size; - - // Only resize if needed. - if (!size.IsEmpty()) - guest_sizer_->SizeContents(size); -} - -content::WebContents* WebContents::GetOwnerWebContents() const { - return embedder_web_contents_; -} - -void WebContents::GuestSizeChanged(const gfx::Size& new_size) { - if (!auto_size_enabled_) - return; - GuestSizeChangedDueToAutoSize(guest_size_, new_size); - guest_size_ = new_size; -} - -void WebContents::RegisterDestructionCallback( - const DestructionCallback& callback) { - destruction_callback_ = callback; -} - -void WebContents::SetGuestSizer(content::GuestSizer* guest_sizer) { - guest_sizer_ = guest_sizer; -} - -void WebContents::WillAttach(content::WebContents* embedder_web_contents, - int element_instance_id, - bool is_full_page_plugin) { - embedder_web_contents_ = embedder_web_contents; - element_instance_id_ = element_instance_id; -} - void WebContents::Destroy() { - if (storage_) { - if (!destruction_callback_.is_null()) - destruction_callback_.Run(); - + session_.Reset(); + if (type_ == WEB_VIEW && managed_web_contents()) { // When force destroying the "destroyed" event is not emitted. WebContentsDestroyed(); + guest_delegate_->Destroy(); + Observe(nullptr); - storage_.reset(); + DestroyWebContents(); } } @@ -410,20 +526,36 @@ bool WebContents::IsAlive() const { return web_contents() != NULL; } +int WebContents::GetID() const { + return web_contents()->GetRenderProcessHost()->GetID(); +} + +bool WebContents::Equal(const WebContents* web_contents) const { + return GetID() == web_contents->GetID(); +} + void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { content::NavigationController::LoadURLParams params(url); GURL http_referrer; - if (options.Get("httpreferrer", &http_referrer)) + if (options.Get("httpReferrer", &http_referrer)) params.referrer = content::Referrer(http_referrer.GetAsReferrer(), blink::WebReferrerPolicyDefault); + std::string user_agent; + if (options.Get("userAgent", &user_agent)) + SetUserAgent(user_agent); + params.transition_type = ui::PAGE_TRANSITION_TYPED; params.should_clear_history_list = true; params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE; web_contents()->GetController().LoadURLWithParams(params); } +GURL WebContents::GetURL() const { + return web_contents()->GetURL(); +} + base::string16 WebContents::GetTitle() const { return web_contents()->GetTitle(); } @@ -459,139 +591,108 @@ void WebContents::GoToOffset(int offset) { web_contents()->GetController().GoToOffset(offset); } -int WebContents::GetRoutingID() const { - return web_contents()->GetRoutingID(); -} - -int WebContents::GetProcessID() const { - return web_contents()->GetRenderProcessHost()->GetID(); -} - bool WebContents::IsCrashed() const { return web_contents()->IsCrashed(); } void WebContents::SetUserAgent(const std::string& user_agent) { web_contents()->SetUserAgentOverride(user_agent); + scoped_refptr getter = + web_contents()->GetBrowserContext()->GetRequestContext(); + getter->GetNetworkTaskRunner()->PostTask(FROM_HERE, + base::Bind(&SetUserAgentInIO, getter, user_agent)); +} + +std::string WebContents::GetUserAgent() { + return web_contents()->GetUserAgentOverride(); } void WebContents::InsertCSS(const std::string& css) { web_contents()->InsertCSS(css); } -void WebContents::ExecuteJavaScript(const base::string16& code) { - web_contents()->GetMainFrame()->ExecuteJavaScript(code); +void WebContents::ExecuteJavaScript(const base::string16& code, + bool has_user_gesture) { + Send(new AtomViewMsg_ExecuteJavaScript(routing_id(), code, has_user_gesture)); } -void WebContents::OpenDevTools() { - storage_->SetCanDock(false); - storage_->ShowDevTools(); +void WebContents::OpenDevTools(mate::Arguments* args) { + if (type_ == REMOTE) + return; + + bool detach = false; + if (type_ == WEB_VIEW) { + detach = true; + } else if (args && args->Length() == 1) { + mate::Dictionary options; + args->GetNext(&options) && options.Get("detach", &detach); + } + managed_web_contents()->SetCanDock(!detach); + managed_web_contents()->ShowDevTools(); } void WebContents::CloseDevTools() { - storage_->CloseDevTools(); + if (type_ == REMOTE) + return; + + managed_web_contents()->CloseDevTools(); } bool WebContents::IsDevToolsOpened() { - return storage_->IsDevToolsViewShowing(); + if (type_ == REMOTE) + return false; + + return managed_web_contents()->IsDevToolsViewShowing(); +} + +void WebContents::EnableDeviceEmulation( + const blink::WebDeviceEmulationParams& params) { + if (type_ == REMOTE) + return; + + Send(new ViewMsg_EnableDeviceEmulation(routing_id(), params)); +} + +void WebContents::DisableDeviceEmulation() { + if (type_ == REMOTE) + return; + + Send(new ViewMsg_DisableDeviceEmulation(routing_id())); +} + +void WebContents::ToggleDevTools() { + if (IsDevToolsOpened()) + CloseDevTools(); + else + OpenDevTools(nullptr); } void WebContents::InspectElement(int x, int y) { - OpenDevTools(); + if (type_ == REMOTE) + return; + + OpenDevTools(nullptr); scoped_refptr agent( - content::DevToolsAgentHost::GetOrCreateFor(storage_->GetWebContents())); + content::DevToolsAgentHost::GetOrCreateFor(web_contents())); agent->InspectElement(x, y); } -void WebContents::Undo() { - web_contents()->Undo(); -} - -void WebContents::Redo() { - web_contents()->Redo(); -} - -void WebContents::Cut() { - web_contents()->Cut(); -} - -void WebContents::Copy() { - web_contents()->Copy(); -} - -void WebContents::Paste() { - web_contents()->Paste(); -} - -void WebContents::Delete() { - web_contents()->Delete(); -} - -void WebContents::SelectAll() { - web_contents()->SelectAll(); -} - -void WebContents::Unselect() { - web_contents()->Unselect(); -} - -void WebContents::Replace(const base::string16& word) { - web_contents()->Replace(word); -} - -void WebContents::ReplaceMisspelling(const base::string16& word) { - web_contents()->ReplaceMisspelling(word); -} - -bool WebContents::SendIPCMessage(const base::string16& channel, - const base::ListValue& args) { - return Send(new AtomViewMsg_Message(routing_id(), channel, args)); -} - -void WebContents::SetAutoSize(bool enabled, - const gfx::Size& min_size, - const gfx::Size& max_size) { - min_auto_size_ = min_size; - min_auto_size_.SetToMin(max_size); - max_auto_size_ = max_size; - max_auto_size_.SetToMax(min_size); - - enabled &= !min_auto_size_.IsEmpty() && !max_auto_size_.IsEmpty(); - if (!enabled && !auto_size_enabled_) +void WebContents::InspectServiceWorker() { + if (type_ == REMOTE) return; - auto_size_enabled_ = enabled; - - if (!attached()) - return; - - content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); - if (auto_size_enabled_) { - rvh->EnableAutoResize(min_auto_size_, max_auto_size_); - } else { - rvh->DisableAutoResize(element_size_); - guest_size_ = element_size_; - GuestSizeChangedDueToAutoSize(guest_size_, element_size_); + for (const auto& agent_host : content::DevToolsAgentHost::GetOrCreateAll()) { + if (agent_host->GetType() == + content::DevToolsAgentHost::TYPE_SERVICE_WORKER) { + OpenDevTools(nullptr); + managed_web_contents()->AttachTo(agent_host); + break; + } } } -void WebContents::SetAllowTransparency(bool allow) { - if (guest_opaque_ != allow) - return; - - guest_opaque_ = !allow; - if (!web_contents()->GetRenderViewHost()->GetView()) - return; - - if (guest_opaque_) { - web_contents() - ->GetRenderViewHost() - ->GetView() - ->SetBackgroundColorToDefault(); - } else { - web_contents()->GetRenderViewHost()->GetView()->SetBackgroundColor( - SK_ColorTRANSPARENT); - } +v8::Local WebContents::Session(v8::Isolate* isolate) { + return v8::Local::New(isolate, session_); } void WebContents::HasServiceWorker( @@ -615,13 +716,180 @@ void WebContents::UnregisterServiceWorker( callback); } +void WebContents::SetAudioMuted(bool muted) { + web_contents()->SetAudioMuted(muted); +} + +bool WebContents::IsAudioMuted() { + return web_contents()->IsAudioMuted(); +} + +void WebContents::Print(mate::Arguments* args) { + PrintSettings settings = { false, false }; + if (args->Length() == 1 && !args->GetNext(&settings)) { + args->ThrowError(); + return; + } + + printing::PrintViewManagerBasic::FromWebContents(web_contents())-> + PrintNow(settings.silent, settings.print_background); +} + +void WebContents::PrintToPDF(const base::DictionaryValue& setting, + const PrintToPDFCallback& callback) { + printing::PrintPreviewMessageHandler::FromWebContents(web_contents())-> + PrintToPDF(setting, callback); +} + +void WebContents::AddWorkSpace(mate::Arguments* args, + const base::FilePath& path) { + if (path.empty()) { + args->ThrowError("path cannot be empty"); + return; + } + DevToolsAddFileSystem(path); +} + +void WebContents::RemoveWorkSpace(mate::Arguments* args, + const base::FilePath& path) { + if (path.empty()) { + args->ThrowError("path cannot be empty"); + return; + } + DevToolsRemoveFileSystem(path); +} + +void WebContents::Undo() { + web_contents()->Undo(); +} + +void WebContents::Redo() { + web_contents()->Redo(); +} + +void WebContents::Cut() { + web_contents()->Cut(); +} + +void WebContents::Copy() { + web_contents()->Copy(); +} + +void WebContents::Paste() { + web_contents()->Paste(); +} + +void WebContents::PasteAndMatchStyle() { + web_contents()->PasteAndMatchStyle(); +} + +void WebContents::Delete() { + web_contents()->Delete(); +} + +void WebContents::SelectAll() { + web_contents()->SelectAll(); +} + +void WebContents::Unselect() { + web_contents()->Unselect(); +} + +void WebContents::Replace(const base::string16& word) { + web_contents()->Replace(word); +} + +void WebContents::ReplaceMisspelling(const base::string16& word) { + web_contents()->ReplaceMisspelling(word); +} + +void WebContents::Focus() { + web_contents()->Focus(); +} + +void WebContents::TabTraverse(bool reverse) { + web_contents()->FocusThroughTabTraversal(reverse); +} + +bool WebContents::SendIPCMessage(const base::string16& channel, + const base::ListValue& args) { + return Send(new AtomViewMsg_Message(routing_id(), channel, args)); +} + +void WebContents::SendInputEvent(v8::Isolate* isolate, + v8::Local input_event) { + const auto view = web_contents()->GetRenderWidgetHostView(); + if (!view) + return; + const auto host = view->GetRenderWidgetHost(); + if (!host) + return; + + int type = mate::GetWebInputEventType(isolate, input_event); + if (blink::WebInputEvent::isMouseEventType(type)) { + blink::WebMouseEvent mouse_event; + if (mate::ConvertFromV8(isolate, input_event, &mouse_event)) { + host->ForwardMouseEvent(mouse_event); + return; + } + } else if (blink::WebInputEvent::isKeyboardEventType(type)) { + content::NativeWebKeyboardEvent keyboard_event;; + if (mate::ConvertFromV8(isolate, input_event, &keyboard_event)) { + host->ForwardKeyboardEvent(keyboard_event); + return; + } + } else if (type == blink::WebInputEvent::MouseWheel) { + blink::WebMouseWheelEvent mouse_wheel_event; + if (mate::ConvertFromV8(isolate, input_event, &mouse_wheel_event)) { + host->ForwardWheelEvent(mouse_wheel_event); + return; + } + } + + isolate->ThrowException(v8::Exception::Error(mate::StringToV8( + isolate, "Invalid event object"))); +} + +void WebContents::BeginFrameSubscription( + const FrameSubscriber::FrameCaptureCallback& callback) { + const auto view = web_contents()->GetRenderWidgetHostView(); + if (view) { + scoped_ptr frame_subscriber(new FrameSubscriber( + isolate(), view->GetVisibleViewportSize(), callback)); + view->BeginFrameSubscription(frame_subscriber.Pass()); + } +} + +void WebContents::EndFrameSubscription() { + const auto view = web_contents()->GetRenderWidgetHostView(); + if (view) + view->EndFrameSubscription(); +} + +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; +} + mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( v8::Isolate* isolate) { if (template_.IsEmpty()) template_.Reset(isolate, mate::ObjectTemplateBuilder(isolate) - .SetMethod("destroy", &WebContents::Destroy) - .SetMethod("isAlive", &WebContents::IsAlive) + .SetMethod("destroy", &WebContents::Destroy, true) + .SetMethod("isAlive", &WebContents::IsAlive, true) + .SetMethod("getId", &WebContents::GetID) + .SetMethod("equal", &WebContents::Equal) .SetMethod("_loadUrl", &WebContents::LoadURL) + .SetMethod("_getUrl", &WebContents::GetURL) .SetMethod("getTitle", &WebContents::GetTitle) .SetMethod("isLoading", &WebContents::IsLoading) .SetMethod("isWaitingForResponse", &WebContents::IsWaitingForResponse) @@ -630,39 +898,66 @@ mate::ObjectTemplateBuilder WebContents::GetObjectTemplateBuilder( .SetMethod("_goBack", &WebContents::GoBack) .SetMethod("_goForward", &WebContents::GoForward) .SetMethod("_goToOffset", &WebContents::GoToOffset) - .SetMethod("getRoutingId", &WebContents::GetRoutingID) - .SetMethod("getProcessId", &WebContents::GetProcessID) .SetMethod("isCrashed", &WebContents::IsCrashed) .SetMethod("setUserAgent", &WebContents::SetUserAgent) + .SetMethod("getUserAgent", &WebContents::GetUserAgent) .SetMethod("insertCSS", &WebContents::InsertCSS) .SetMethod("_executeJavaScript", &WebContents::ExecuteJavaScript) .SetMethod("openDevTools", &WebContents::OpenDevTools) .SetMethod("closeDevTools", &WebContents::CloseDevTools) .SetMethod("isDevToolsOpened", &WebContents::IsDevToolsOpened) + .SetMethod("enableDeviceEmulation", + &WebContents::EnableDeviceEmulation) + .SetMethod("disableDeviceEmulation", + &WebContents::DisableDeviceEmulation) + .SetMethod("toggleDevTools", &WebContents::ToggleDevTools) .SetMethod("inspectElement", &WebContents::InspectElement) + .SetMethod("setAudioMuted", &WebContents::SetAudioMuted) + .SetMethod("isAudioMuted", &WebContents::IsAudioMuted) .SetMethod("undo", &WebContents::Undo) .SetMethod("redo", &WebContents::Redo) .SetMethod("cut", &WebContents::Cut) .SetMethod("copy", &WebContents::Copy) .SetMethod("paste", &WebContents::Paste) + .SetMethod("pasteAndMatchStyle", &WebContents::PasteAndMatchStyle) .SetMethod("delete", &WebContents::Delete) .SetMethod("selectAll", &WebContents::SelectAll) .SetMethod("unselect", &WebContents::Unselect) .SetMethod("replace", &WebContents::Replace) .SetMethod("replaceMisspelling", &WebContents::ReplaceMisspelling) - .SetMethod("_send", &WebContents::SendIPCMessage) - .SetMethod("setAutoSize", &WebContents::SetAutoSize) + .SetMethod("focus", &WebContents::Focus) + .SetMethod("tabTraverse", &WebContents::TabTraverse) + .SetMethod("_send", &WebContents::SendIPCMessage, true) + .SetMethod("sendInputEvent", &WebContents::SendInputEvent) + .SetMethod("beginFrameSubscription", + &WebContents::BeginFrameSubscription) + .SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription) + .SetMethod("setSize", &WebContents::SetSize) .SetMethod("setAllowTransparency", &WebContents::SetAllowTransparency) - .SetMethod("isGuest", &WebContents::is_guest) + .SetMethod("isGuest", &WebContents::IsGuest) .SetMethod("hasServiceWorker", &WebContents::HasServiceWorker) .SetMethod("unregisterServiceWorker", &WebContents::UnregisterServiceWorker) + .SetMethod("inspectServiceWorker", &WebContents::InspectServiceWorker) + .SetMethod("print", &WebContents::Print) + .SetMethod("_printToPDF", &WebContents::PrintToPDF) + .SetMethod("addWorkSpace", &WebContents::AddWorkSpace) + .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) + .SetProperty("session", &WebContents::Session) .Build()); return mate::ObjectTemplateBuilder( isolate, v8::Local::New(isolate, template_)); } +bool WebContents::IsDestroyed() const { + return !IsAlive(); +} + +AtomBrowserContext* WebContents::GetBrowserContext() const { + return static_cast(web_contents()->GetBrowserContext()); +} + void WebContents::OnRendererMessage(const base::string16& channel, const base::ListValue& args) { // webContents.emit(channel, new Event(), args...); @@ -676,23 +971,34 @@ void WebContents::OnRendererMessageSync(const base::string16& channel, EmitWithSender(base::UTF16ToUTF8(channel), web_contents(), message, args); } -void WebContents::GuestSizeChangedDueToAutoSize(const gfx::Size& old_size, - const gfx::Size& new_size) { - Emit("size-changed", - old_size.width(), old_size.height(), - new_size.width(), new_size.height()); -} - // static mate::Handle WebContents::CreateFrom( v8::Isolate* isolate, content::WebContents* web_contents) { - return mate::CreateHandle(isolate, new WebContents(web_contents)); + // We have an existing WebContents object in JS. + auto existing = TrackableObject::FromWrappedClass(isolate, web_contents); + if (existing) + return mate::CreateHandle(isolate, static_cast(existing)); + + // Otherwise create a new WebContents wrapper object. + auto handle = mate::CreateHandle(isolate, new WebContents(web_contents)); + g_wrap_web_contents.Run(handle.ToV8()); + return handle; } // static mate::Handle WebContents::Create( v8::Isolate* isolate, const mate::Dictionary& options) { - return mate::CreateHandle(isolate, new WebContents(options)); + auto handle = mate::CreateHandle(isolate, new WebContents(isolate, options)); + g_wrap_web_contents.Run(handle.ToV8()); + return handle; +} + +void SetWrapWebContents(const WrapWebContentsCallback& callback) { + g_wrap_web_contents = callback; +} + +void ClearWrapWebContents() { + g_wrap_web_contents.Reset(); } } // namespace api @@ -702,11 +1008,13 @@ mate::Handle WebContents::Create( namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +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("create", &atom::api::WebContents::Create); + dict.SetMethod("_setWrapWebContents", &atom::api::SetWrapWebContents); + dict.SetMethod("_clearWrapWebContents", &atom::api::ClearWrapWebContents); } } // namespace diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index e75cfb0267f0..c8ea6908bc5b 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -8,35 +8,43 @@ #include #include -#include "atom/browser/api/event_emitter.h" -#include "brightray/browser/default_web_contents_delegate.h" -#include "content/public/browser/browser_plugin_guest_delegate.h" -#include "content/public/common/favicon_url.h" -#include "content/public/browser/web_contents_delegate.h" +#include "atom/browser/api/frame_subscriber.h" +#include "atom/browser/api/trackable_object.h" +#include "atom/browser/common_web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" +#include "content/public/common/favicon_url.h" #include "native_mate/handle.h" #include "ui/gfx/image/image.h" +namespace blink { +struct WebDeviceEmulationParams; +} + namespace brightray { class InspectableWebContents; } namespace mate { +class Arguments; class Dictionary; } namespace atom { -class AtomJavaScriptDialogManager; -class WebDialogHelper; +struct SetSizeParams; +class AtomBrowserContext; +class WebViewGuestDelegate; namespace api { -class WebContents : public mate::EventEmitter, - public content::BrowserPluginGuestDelegate, - public content::WebContentsDelegate, +class WebContents : public mate::TrackableObject, + public CommonWebContentsDelegate, public content::WebContentsObserver { public: + // For node.js callback function type: function(error, buffer) + using PrintToPDFCallback = + base::Callback, v8::Local)>; + // Create from an existing WebContents. static mate::Handle CreateFrom( v8::Isolate* isolate, content::WebContents* web_contents); @@ -45,9 +53,14 @@ class WebContents : public mate::EventEmitter, static mate::Handle Create( v8::Isolate* isolate, const mate::Dictionary& options); - void Destroy(); + // mate::TrackableObject: + void Destroy() override; + bool IsAlive() const; + int GetID() const; + bool Equal(const WebContents* web_contents) const; void LoadURL(const GURL& url, const mate::Dictionary& options); + GURL GetURL() const; base::string16 GetTitle() const; bool IsLoading() const; bool IsWaitingForResponse() const; @@ -56,18 +69,34 @@ class WebContents : public mate::EventEmitter, void GoBack(); void GoForward(); void GoToOffset(int offset); - int GetRoutingID() const; - int GetProcessID() const; bool IsCrashed() const; void SetUserAgent(const std::string& user_agent); + std::string GetUserAgent(); void InsertCSS(const std::string& css); - void ExecuteJavaScript(const base::string16& code); - void OpenDevTools(); + void ExecuteJavaScript(const base::string16& code, + bool has_user_gesture); + void OpenDevTools(mate::Arguments* args); void CloseDevTools(); bool IsDevToolsOpened(); + void ToggleDevTools(); + void EnableDeviceEmulation(const blink::WebDeviceEmulationParams& params); + void DisableDeviceEmulation(); void InspectElement(int x, int y); + void InspectServiceWorker(); + v8::Local Session(v8::Isolate* isolate); void HasServiceWorker(const base::Callback&); void UnregisterServiceWorker(const base::Callback&); + void SetAudioMuted(bool muted); + bool IsAudioMuted(); + void Print(mate::Arguments* args); + + // Print current page as PDF. + void PrintToPDF(const base::DictionaryValue& setting, + const PrintToPDFCallback& callback); + + // DevTools workspace api. + void AddWorkSpace(mate::Arguments* args, const base::FilePath& path); + void RemoveWorkSpace(mate::Arguments* args, const base::FilePath& path); // Editing commands. void Undo(); @@ -75,42 +104,43 @@ class WebContents : public mate::EventEmitter, void Cut(); void Copy(); void Paste(); + void PasteAndMatchStyle(); void Delete(); void SelectAll(); void Unselect(); void Replace(const base::string16& word); void ReplaceMisspelling(const base::string16& word); - // Sending messages to browser. + // Focus. + void Focus(); + void TabTraverse(bool reverse); + + // Send messages to browser. bool SendIPCMessage(const base::string16& channel, const base::ListValue& args); - // Toggles autosize mode for corresponding . - void SetAutoSize(bool enabled, - const gfx::Size& min_size, - const gfx::Size& max_size); + // Send WebInputEvent to the page. + void SendInputEvent(v8::Isolate* isolate, v8::Local input_event); - // Sets the transparency of the guest. + // Subscribe to the frame updates. + void BeginFrameSubscription( + const FrameSubscriber::FrameCaptureCallback& callback); + void EndFrameSubscription(); + + // Methods for creating . + void SetSize(const SetSizeParams& params); void SetAllowTransparency(bool allow); - - // Returns whether this is a guest view. - bool is_guest() const { return guest_instance_id_ != -1; } - - // Returns whether this guest has an associated embedder. - bool attached() const { return !!embedder_web_contents_; } - - content::WebContents* web_contents() const { - return content::WebContentsObserver::web_contents(); - } + bool IsGuest() const; protected: explicit WebContents(content::WebContents* web_contents); - explicit WebContents(const mate::Dictionary& options); + WebContents(v8::Isolate* isolate, const mate::Dictionary& options); ~WebContents(); // mate::Wrappable: mate::ObjectTemplateBuilder GetObjectTemplateBuilder( v8::Isolate* isolate) override; + bool IsDestroyed() const override; // content::WebContentsDelegate: bool AddMessageToConsole(content::WebContents* source, @@ -123,33 +153,32 @@ class WebContents : public mate::EventEmitter, int route_id, int main_frame_route_id, WindowContainerType window_container_type, - const base::string16& frame_name, + const std::string& frame_name, const GURL& target_url, const std::string& partition_id, content::SessionStorageNamespace* session_storage_namespace) override; - void CloseContents(content::WebContents* source) override; content::WebContents* OpenURLFromTab( content::WebContents* source, const content::OpenURLParams& params) override; - content::JavaScriptDialogManager* GetJavaScriptDialogManager( - content::WebContents* source) override; - void RunFileChooser(content::WebContents* web_contents, - const content::FileChooserParams& params) override; - void EnumerateDirectory(content::WebContents* web_contents, - int request_id, - const base::FilePath& path) override; - bool CheckMediaAccessPermission(content::WebContents* web_contents, - const GURL& security_origin, - content::MediaStreamType type) override; - void RequestMediaAccessPermission( - content::WebContents*, - const content::MediaStreamRequest&, - const content::MediaResponseCallback&) override; + void BeforeUnloadFired(content::WebContents* tab, + bool proceed, + bool* proceed_to_fire_unload) override; + void MoveContents(content::WebContents* source, + const gfx::Rect& pos) override; + void CloseContents(content::WebContents* source) override; + void ActivateContents(content::WebContents* contents) override; + bool IsPopupOrPanel(const content::WebContents* source) const override; void HandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) override; + void EnterFullscreenModeForTab(content::WebContents* source, + const GURL& origin) override; + void ExitFullscreenModeForTab(content::WebContents* source) override; + void RendererUnresponsive(content::WebContents* source) override; + void RendererResponsive(content::WebContents* source) override; // content::WebContentsObserver: + void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; void RenderViewDeleted(content::RenderViewHost*) override; void RenderProcessGone(base::TerminationStatus status) override; void DocumentLoadedInFrame( @@ -159,13 +188,15 @@ class WebContents : public mate::EventEmitter, void DidFailLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, - const base::string16& error_description) override; + const base::string16& error_description, + bool was_ignored_by_handler) override; void DidFailProvisionalLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, - const base::string16& error_description) override; - void DidStartLoading(content::RenderViewHost* render_view_host) override; - void DidStopLoading(content::RenderViewHost* render_view_host) override; + const base::string16& error_description, + bool was_ignored_by_handler) override; + void DidStartLoading() override; + void DidStopLoading() override; void DidGetResourceResponseStart( const content::ResourceRequestDetails& details) override; void DidGetRedirectForResourceRequest( @@ -175,26 +206,24 @@ class WebContents : public mate::EventEmitter, const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) override; bool OnMessageReceived(const IPC::Message& message) override; - void RenderViewReady() override; void WebContentsDestroyed() override; void NavigationEntryCommitted( const content::LoadCommittedDetails& load_details) override; void TitleWasSet(content::NavigationEntry* entry, bool explicit_set) override; void DidUpdateFaviconURL( const std::vector& urls) override; - - // content::BrowserPluginGuestDelegate: - void DidAttach(int guest_proxy_routing_id) final; - void ElementSizeChanged(const gfx::Size& size) final; - content::WebContents* GetOwnerWebContents() const final; - void GuestSizeChanged(const gfx::Size& new_size) final; - void RegisterDestructionCallback(const DestructionCallback& callback) final; - void SetGuestSizer(content::GuestSizer* guest_sizer) final; - void WillAttach(content::WebContents* embedder_web_contents, - int element_instance_id, - bool is_full_page_plugin) final; + void PluginCrashed(const base::FilePath& plugin_path, + base::ProcessId plugin_pid) override; private: + enum Type { + BROWSER_WINDOW, // Used by BrowserWindow. + WEB_VIEW, // Used by . + REMOTE, // Thin wrap around an existing WebContents. + }; + + AtomBrowserContext* GetBrowserContext() const; + // Called when received a message from renderer. void OnRendererMessage(const base::string16& channel, const base::ListValue& args); @@ -204,48 +233,12 @@ class WebContents : public mate::EventEmitter, const base::ListValue& args, IPC::Message* message); - void GuestSizeChangedDueToAutoSize(const gfx::Size& old_size, - const gfx::Size& new_size); + v8::Global session_; - scoped_ptr web_dialog_helper_; - scoped_ptr dialog_manager_; + scoped_ptr guest_delegate_; - // Unique ID for a guest WebContents. - int guest_instance_id_; - - // |element_instance_id_| is an identifer that's unique to a particular - // element. - int element_instance_id_; - - DestructionCallback destruction_callback_; - - // Stores whether the contents of the guest can be transparent. - bool guest_opaque_; - - // Stores the WebContents that managed by this class. - scoped_ptr storage_; - - // The WebContents that attaches this guest view. - content::WebContents* embedder_web_contents_; - - // The size of the container element. - gfx::Size element_size_; - - // The size of the guest content. Note: In autosize mode, the container - // element may not match the size of the guest. - gfx::Size guest_size_; - - // A pointer to the guest_sizer. - content::GuestSizer* guest_sizer_; - - // Indicates whether autosize mode is enabled or not. - bool auto_size_enabled_; - - // The maximum size constraints of the container element in autosize mode. - gfx::Size max_auto_size_; - - // The minimum size constraints of the container element in autosize mode. - gfx::Size min_auto_size_; + // The type of current WebContents. + Type type_; DISALLOW_COPY_AND_ASSIGN(WebContents); }; diff --git a/atom/browser/api/atom_api_web_view_manager.cc b/atom/browser/api/atom_api_web_view_manager.cc index ed796be8e83f..e57c5ffb6bb4 100644 --- a/atom/browser/api/atom_api_web_view_manager.cc +++ b/atom/browser/api/atom_api_web_view_manager.cc @@ -3,19 +3,20 @@ // found in the LICENSE file. #include "atom/browser/api/atom_api_web_contents.h" +#include "atom/browser/web_contents_preferences.h" #include "atom/browser/web_view_manager.h" -#include "atom/common/native_mate_converters/gurl_converter.h" +#include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/node_includes.h" #include "content/public/browser/browser_context.h" #include "native_mate/dictionary.h" -#include "net/base/filename_util.h" -#include "atom/common/node_includes.h" +using atom::WebContentsPreferences; namespace mate { template<> struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Handle val, + static bool FromV8(v8::Isolate* isolate, v8::Local val, content::WebContents** out) { atom::api::WebContents* contents; if (!Converter::FromV8(isolate, val, &contents)) @@ -25,28 +26,6 @@ struct Converter { } }; -template<> -struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Handle val, - atom::WebViewManager::WebViewInfo* out) { - Dictionary options; - if (!ConvertFromV8(isolate, val, &options)) - return false; - - GURL preload_url; - if (!options.Get("preloadUrl", &preload_url)) - return false; - - if (!preload_url.is_empty() && - !net::FileURLToFilePath(preload_url, &(out->preload_script))) - return false; - - return options.Get("nodeIntegration", &(out->node_integration)) && - options.Get("plugins", &(out->plugins)) && - options.Get("disableWebSecurity", &(out->disable_web_security)); - } -}; - } // namespace mate namespace { @@ -65,14 +44,13 @@ void AddGuest(int guest_instance_id, int element_instance_id, content::WebContents* embedder, content::WebContents* guest_web_contents, - atom::WebViewManager::WebViewInfo info) { + const base::DictionaryValue& options) { auto manager = GetWebViewManager(embedder); - if (manager) { - info.guest_instance_id = guest_instance_id; - info.embedder = embedder; + if (manager) manager->AddGuest(guest_instance_id, element_instance_id, embedder, - guest_web_contents, info); - } + guest_web_contents); + + WebContentsPreferences::FromWebContents(guest_web_contents)->Merge(options); } void RemoveGuest(content::WebContents* embedder, int guest_instance_id) { @@ -81,8 +59,8 @@ void RemoveGuest(content::WebContents* embedder, int guest_instance_id) { manager->RemoveGuest(guest_instance_id); } -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("addGuest", &AddGuest); dict.SetMethod("removeGuest", &RemoveGuest); diff --git a/atom/browser/api/atom_api_window.cc b/atom/browser/api/atom_api_window.cc index b71499d3699c..3a44115da264 100644 --- a/atom/browser/api/atom_api_window.cc +++ b/atom/browser/api/atom_api_window.cc @@ -4,46 +4,46 @@ #include "atom/browser/api/atom_api_window.h" +#include "atom/browser/api/atom_api_menu.h" #include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/browser.h" #include "atom/browser/native_window.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" +#include "atom/common/node_includes.h" +#include "atom/common/options_switches.h" #include "content/public/browser/render_process_host.h" -#include "native_mate/callback.h" #include "native_mate/constructor.h" #include "native_mate/dictionary.h" #include "ui/gfx/geometry/rect.h" -#include "atom/common/node_includes.h" - -namespace { - -struct PrintSettings { - bool silent; - bool print_background; -}; - -} // namespace +#if defined(OS_WIN) +#include "atom/browser/native_window_views.h" +#include "atom/browser/ui/win/taskbar_host.h" +#endif +#if defined(OS_WIN) namespace mate { template<> -struct Converter { +struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Handle val, - PrintSettings* out) { + atom::TaskbarHost::ThumbarButton* out) { mate::Dictionary dict; if (!ConvertFromV8(isolate, val, &dict)) return false; - dict.Get("silent", &(out->silent)); - dict.Get("printBackground", &(out->print_background)); - return true; + dict.Get("click", &(out->clicked_callback)); + dict.Get("tooltip", &(out->tooltip)); + dict.Get("flags", &out->flags); + return dict.Get("icon", &(out->icon)); } }; } // namespace mate +#endif namespace atom { @@ -63,8 +63,29 @@ void OnCapturePageDone( } // namespace -Window::Window(const mate::Dictionary& options) - : window_(NativeWindow::Create(options)) { +Window::Window(v8::Isolate* isolate, const mate::Dictionary& options) { + // Use options['web-preferences'] to create WebContents. + mate::Dictionary web_preferences = mate::Dictionary::CreateEmpty(isolate); + options.Get(switches::kWebPreferences, &web_preferences); + + // Be compatible with old options which are now in web_preferences. + v8::Local value; + if (options.Get(switches::kNodeIntegration, &value)) + web_preferences.Set(switches::kNodeIntegration, value); + if (options.Get(switches::kPreloadScript, &value)) + web_preferences.Set(switches::kPreloadScript, value); + if (options.Get(switches::kZoomFactor, &value)) + web_preferences.Set(switches::kZoomFactor, value); + + // Creates the WebContents used by BrowserWindow. + auto web_contents = WebContents::Create(isolate, web_preferences); + web_contents_.Reset(isolate, web_contents.ToV8()); + api_web_contents_ = web_contents.get(); + + // Creates BrowserWindow. + window_.reset(NativeWindow::Create(web_contents->managed_web_contents(), + options)); + web_contents->SetOwnerWindow(window_.get()); window_->InitFromOptions(options); window_->AddObserver(this); } @@ -79,25 +100,24 @@ void Window::OnPageTitleUpdated(bool* prevent_default, *prevent_default = Emit("page-title-updated", title); } -void Window::WillCreatePopupWindow(const base::string16& frame_name, - const GURL& target_url, - const std::string& partition_id, - WindowOpenDisposition disposition) { - Emit("-new-window", target_url, frame_name, static_cast(disposition)); -} - -void Window::WillNavigate(bool* prevent_default, const GURL& url) { - *prevent_default = Emit("-will-navigate", url); -} - void Window::WillCloseWindow(bool* prevent_default) { *prevent_default = Emit("close"); } void Window::OnWindowClosed() { + if (api_web_contents_) { + api_web_contents_->DestroyWebContents(); + api_web_contents_ = nullptr; + web_contents_.Reset(); + } + + RemoveFromWeakMap(); + window_->RemoveObserver(this); + Emit("closed"); - window_->RemoveObserver(this); + // Clean up the resources after window has been closed. + base::MessageLoop::current()->DeleteSoon(FROM_HERE, window_.release()); } void Window::OnWindowBlur() { @@ -124,6 +144,18 @@ void Window::OnWindowRestore() { Emit("restore"); } +void Window::OnWindowResize() { + Emit("resize"); +} + +void Window::OnWindowMove() { + Emit("move"); +} + +void Window::OnWindowMoved() { + Emit("moved"); +} + void Window::OnWindowEnterFullScreen() { Emit("enter-full-screen"); } @@ -132,6 +164,14 @@ void Window::OnWindowLeaveFullScreen() { Emit("leave-full-screen"); } +void Window::OnWindowEnterHtmlFullScreen() { + Emit("enter-html-full-screen"); +} + +void Window::OnWindowLeaveHtmlFullScreen() { + Emit("leave-html-full-screen"); +} + void Window::OnRendererUnresponsive() { Emit("unresponsive"); } @@ -144,19 +184,46 @@ void Window::OnDevToolsFocus() { Emit("devtools-focused"); } +void Window::OnDevToolsOpened() { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + auto handle = WebContents::CreateFrom( + isolate(), api_web_contents_->GetDevToolsWebContents()); + devtools_web_contents_.Reset(isolate(), handle.ToV8()); + + Emit("devtools-opened"); +} + +void Window::OnDevToolsClosed() { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + devtools_web_contents_.Reset(); + + Emit("devtools-closed"); +} + +void Window::OnExecuteWindowsCommand(const std::string& command_name) { + Emit("app-command", command_name); +} + // static mate::Wrappable* Window::New(v8::Isolate* isolate, const mate::Dictionary& options) { if (!Browser::Get()->is_ready()) { - node::ThrowError("Cannot create BrowserWindow before app is ready"); + isolate->ThrowException(v8::Exception::Error(mate::StringToV8( + isolate, "Cannot create BrowserWindow before app is ready"))); return nullptr; } - return new Window(options); + return new Window(isolate, options); +} + +bool Window::IsDestroyed() const { + return !window_ || window_->IsClosed(); } void Window::Destroy() { - window_->DestroyWebContents(); - window_->CloseImmediately(); + if (window_) + window_->CloseContents(nullptr); } void Window::Close() { @@ -335,22 +402,6 @@ bool Window::IsKiosk() { return window_->IsKiosk(); } -void Window::OpenDevTools(bool can_dock) { - window_->OpenDevTools(can_dock); -} - -void Window::CloseDevTools() { - window_->CloseDevTools(); -} - -bool Window::IsDevToolsOpened() { - return window_->IsDevToolsOpened(); -} - -void Window::InspectElement(int x, int y) { - window_->InspectElement(x, y); -} - void Window::FocusOnWebView() { window_->FocusOnWebView(); } @@ -363,6 +414,10 @@ bool Window::IsWebViewFocused() { return window_->IsWebViewFocused(); } +bool Window::IsDevToolsFocused() { + return window_->IsDevToolsFocused(); +} + void Window::SetRepresentedFilename(const std::string& filename) { window_->SetRepresentedFilename(filename); } @@ -394,16 +449,6 @@ void Window::CapturePage(mate::Arguments* args) { rect, base::Bind(&OnCapturePageDone, args->isolate(), callback)); } -void Window::Print(mate::Arguments* args) { - PrintSettings settings = { false, false };; - if (args->Length() == 1 && !args->GetNext(&settings)) { - args->ThrowError(); - return; - } - - window_->Print(settings.silent, settings.print_background); -} - void Window::SetProgressBar(double progress) { window_->SetProgressBar(progress); } @@ -413,6 +458,37 @@ void Window::SetOverlayIcon(const gfx::Image& overlay, window_->SetOverlayIcon(overlay, description); } +bool Window::SetThumbarButtons(mate::Arguments* args) { +#if defined(OS_WIN) + std::vector buttons; + if (!args->GetNext(&buttons)) { + args->ThrowError(); + return false; + } + auto window = static_cast(window_.get()); + return window->taskbar_host().SetThumbarButtons( + window->GetAcceleratedWidget(), buttons); +#else + return false; +#endif +} + +void Window::SetMenu(v8::Isolate* isolate, v8::Local value) { + mate::Handle menu; + if (value->IsObject() && + mate::V8ToString(value->ToObject()->GetConstructorName()) == "Menu" && + mate::ConvertFromV8(isolate, value, &menu)) { + menu_.Reset(isolate, menu.ToV8()); + window_->SetMenu(menu->model()); + } else if (value->IsNull()) { + menu_.Reset(); + window_->SetMenu(nullptr); + } else { + isolate->ThrowException(v8::Exception::TypeError( + mate::StringToV8(isolate, "Invalid Menu"))); + } +} + void Window::SetAutoHideMenuBar(bool auto_hide) { window_->SetAutoHideMenuBar(auto_hide); } @@ -435,6 +511,12 @@ void Window::ShowDefinitionForSelection() { } #endif +void Window::SetAspectRatio(double aspect_ratio, mate::Arguments* args) { + gfx::Size extra_size; + args->GetNext(&extra_size); + window_->SetAspectRatio(aspect_ratio, extra_size); +} + void Window::SetVisibleOnAllWorkspaces(bool visible) { return window_->SetVisibleOnAllWorkspaces(visible); } @@ -443,20 +525,29 @@ bool Window::IsVisibleOnAllWorkspaces() { return window_->IsVisibleOnAllWorkspaces(); } -mate::Handle Window::GetWebContents(v8::Isolate* isolate) const { - return WebContents::CreateFrom(isolate, window_->GetWebContents()); +int32_t Window::ID() const { + return weak_map_id(); } -mate::Handle Window::GetDevToolsWebContents( - v8::Isolate* isolate) const { - return WebContents::CreateFrom(isolate, window_->GetDevToolsWebContents()); +v8::Local Window::WebContents(v8::Isolate* isolate) { + if (web_contents_.IsEmpty()) + return v8::Null(isolate); + else + return v8::Local::New(isolate, web_contents_); +} + +v8::Local Window::DevToolsWebContents(v8::Isolate* isolate) { + if (devtools_web_contents_.IsEmpty()) + return v8::Null(isolate); + else + return v8::Local::New(isolate, devtools_web_contents_); } // static void Window::BuildPrototype(v8::Isolate* isolate, - v8::Handle prototype) { + v8::Local prototype) { mate::ObjectTemplateBuilder(isolate, prototype) - .SetMethod("destroy", &Window::Destroy) + .SetMethod("destroy", &Window::Destroy, true) .SetMethod("close", &Window::Close) .SetMethod("isClosed", &Window::IsClosed) .SetMethod("focus", &Window::Focus) @@ -473,6 +564,7 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("isMinimized", &Window::IsMinimized) .SetMethod("setFullScreen", &Window::SetFullScreen) .SetMethod("isFullScreen", &Window::IsFullscreen) + .SetMethod("setAspectRatio", &Window::SetAspectRatio) .SetMethod("getBounds", &Window::GetBounds) .SetMethod("setBounds", &Window::SetBounds) .SetMethod("getSize", &Window::GetSize) @@ -500,17 +592,15 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("getRepresentedFilename", &Window::GetRepresentedFilename) .SetMethod("setDocumentEdited", &Window::SetDocumentEdited) .SetMethod("isDocumentEdited", &Window::IsDocumentEdited) - .SetMethod("_openDevTools", &Window::OpenDevTools) - .SetMethod("closeDevTools", &Window::CloseDevTools) - .SetMethod("isDevToolsOpened", &Window::IsDevToolsOpened) - .SetMethod("inspectElement", &Window::InspectElement) .SetMethod("focusOnWebView", &Window::FocusOnWebView) .SetMethod("blurWebView", &Window::BlurWebView) .SetMethod("isWebViewFocused", &Window::IsWebViewFocused) + .SetMethod("isDevToolsFocused", &Window::IsDevToolsFocused) .SetMethod("capturePage", &Window::CapturePage) - .SetMethod("print", &Window::Print) .SetMethod("setProgressBar", &Window::SetProgressBar) .SetMethod("setOverlayIcon", &Window::SetOverlayIcon) + .SetMethod("setThumbarButtons", &Window::SetThumbarButtons) + .SetMethod("setMenu", &Window::SetMenu) .SetMethod("setAutoHideMenuBar", &Window::SetAutoHideMenuBar) .SetMethod("isMenuBarAutoHide", &Window::IsMenuBarAutoHide) .SetMethod("setMenuBarVisibility", &Window::SetMenuBarVisibility) @@ -523,8 +613,9 @@ void Window::BuildPrototype(v8::Isolate* isolate, .SetMethod("showDefinitionForSelection", &Window::ShowDefinitionForSelection) #endif - .SetMethod("_getWebContents", &Window::GetWebContents) - .SetMethod("_getDevToolsWebContents", &Window::GetDevToolsWebContents); + .SetProperty("id", &Window::ID, true) + .SetProperty("webContents", &Window::WebContents, true) + .SetProperty("devToolsWebContents", &Window::DevToolsWebContents, true); } } // namespace api @@ -534,14 +625,21 @@ void Window::BuildPrototype(v8::Isolate* isolate, namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { - using atom::api::Window; +using atom::api::Window; + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); v8::Local constructor = mate::CreateConstructor( isolate, "BrowserWindow", base::Bind(&Window::New)); + mate::Dictionary browser_window(isolate, constructor); + browser_window.SetMethod("fromId", + &mate::TrackableObject::FromWeakMapID); + browser_window.SetMethod("getAllWindows", + &mate::TrackableObject::GetAll); + mate::Dictionary dict(isolate, exports); - dict.Set("BrowserWindow", static_cast>(constructor)); + dict.Set("BrowserWindow", browser_window); } } // namespace diff --git a/atom/browser/api/atom_api_window.h b/atom/browser/api/atom_api_window.h index d5c3ceedd2eb..d60bf0d87ea0 100644 --- a/atom/browser/api/atom_api_window.h +++ b/atom/browser/api/atom_api_window.h @@ -10,8 +10,9 @@ #include "base/memory/scoped_ptr.h" #include "ui/gfx/image/image.h" +#include "atom/browser/api/trackable_object.h" +#include "atom/browser/native_window.h" #include "atom/browser/native_window_observer.h" -#include "atom/browser/api/event_emitter.h" #include "native_mate/handle.h" class GURL; @@ -33,29 +34,24 @@ namespace api { class WebContents; -class Window : public mate::EventEmitter, +class Window : public mate::TrackableObject, public NativeWindowObserver { public: static mate::Wrappable* New(v8::Isolate* isolate, const mate::Dictionary& options); static void BuildPrototype(v8::Isolate* isolate, - v8::Handle prototype); + v8::Local prototype); NativeWindow* window() const { return window_.get(); } protected: - explicit Window(const mate::Dictionary& options); + Window(v8::Isolate* isolate, const mate::Dictionary& options); virtual ~Window(); // NativeWindowObserver: void OnPageTitleUpdated(bool* prevent_default, const std::string& title) override; - void WillCreatePopupWindow(const base::string16& frame_name, - const GURL& target_url, - const std::string& partition_id, - WindowOpenDisposition disposition) override; - void WillNavigate(bool* prevent_default, const GURL& url) override; void WillCloseWindow(bool* prevent_default) override; void OnWindowClosed() override; void OnWindowBlur() override; @@ -64,15 +60,28 @@ class Window : public mate::EventEmitter, void OnWindowUnmaximize() override; void OnWindowMinimize() override; void OnWindowRestore() override; + void OnWindowResize() override; + void OnWindowMove() override; + void OnWindowMoved() override; void OnWindowEnterFullScreen() override; void OnWindowLeaveFullScreen() override; + void OnWindowEnterHtmlFullScreen() override; + void OnWindowLeaveHtmlFullScreen() override; void OnRendererUnresponsive() override; void OnRendererResponsive() override; void OnDevToolsFocus() override; + void OnDevToolsOpened() override; + void OnDevToolsClosed() override; + void OnExecuteWindowsCommand(const std::string& command_name) override; + + // mate::Wrappable: + bool IsDestroyed() const override; private: + // mate::TrackableObject: + void Destroy() override; + // APIs for NativeWindow. - void Destroy(); void Close(); bool IsClosed(); void Focus(); @@ -112,26 +121,25 @@ class Window : public mate::EventEmitter, void SetSkipTaskbar(bool skip); void SetKiosk(bool kiosk); bool IsKiosk(); - void OpenDevTools(bool can_dock); - void CloseDevTools(); - bool IsDevToolsOpened(); - void InspectElement(int x, int y); void FocusOnWebView(); void BlurWebView(); bool IsWebViewFocused(); + bool IsDevToolsFocused(); void SetRepresentedFilename(const std::string& filename); std::string GetRepresentedFilename(); void SetDocumentEdited(bool edited); bool IsDocumentEdited(); void CapturePage(mate::Arguments* args); - void Print(mate::Arguments* args); void SetProgressBar(double progress); void SetOverlayIcon(const gfx::Image& overlay, const std::string& description); + bool SetThumbarButtons(mate::Arguments* args); + void SetMenu(v8::Isolate* isolate, v8::Local menu); void SetAutoHideMenuBar(bool auto_hide); bool IsMenuBarAutoHide(); void SetMenuBarVisibility(bool visible); bool IsMenuBarVisible(); + void SetAspectRatio(double aspect_ratio, mate::Arguments* args); #if defined(OS_MACOSX) void ShowDefinitionForSelection(); @@ -140,9 +148,15 @@ class Window : public mate::EventEmitter, void SetVisibleOnAllWorkspaces(bool visible); bool IsVisibleOnAllWorkspaces(); - // APIs for WebContents. - mate::Handle GetWebContents(v8::Isolate* isolate) const; - mate::Handle GetDevToolsWebContents(v8::Isolate* isolate) const; + int32_t ID() const; + v8::Local WebContents(v8::Isolate* isolate); + v8::Local DevToolsWebContents(v8::Isolate* isolate); + + v8::Global web_contents_; + v8::Global devtools_web_contents_; + v8::Global menu_; + + api::WebContents* api_web_contents_; scoped_ptr window_; @@ -158,7 +172,7 @@ namespace mate { template<> struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Handle val, + static bool FromV8(v8::Isolate* isolate, v8::Local val, atom::NativeWindow** out) { // null would be tranfered to NULL. if (val->IsNull()) { diff --git a/atom/browser/api/event_emitter.cc b/atom/browser/api/event_emitter.cc index 87ab69a16f83..be7018dafa44 100644 --- a/atom/browser/api/event_emitter.cc +++ b/atom/browser/api/event_emitter.cc @@ -6,10 +6,9 @@ #include "atom/browser/api/event.h" #include "native_mate/arguments.h" +#include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" -#include "atom/common/node_includes.h" - namespace mate { namespace { @@ -17,8 +16,8 @@ namespace { v8::Persistent event_template; void PreventDefault(mate::Arguments* args) { - args->GetThis()->Set(StringToV8(args->isolate(), "defaultPrevented"), - v8::True(args->isolate())); + mate::Dictionary self(args->isolate(), args->GetThis()); + self.Set("defaultPrevented", true); } // Create a pure JavaScript Event object. @@ -38,31 +37,28 @@ v8::Local CreateEventObject(v8::Isolate* isolate) { EventEmitter::EventEmitter() { } -bool EventEmitter::CallEmit(v8::Isolate* isolate, - const base::StringPiece& name, - content::WebContents* sender, - IPC::Message* message, - ValueArray args) { - v8::Handle event; +v8::Local EventEmitter::CreateJSEvent( + v8::Isolate* isolate, content::WebContents* sender, IPC::Message* message) { + v8::Local event; bool use_native_event = sender && message; if (use_native_event) { mate::Handle native_event = mate::Event::Create(isolate); native_event->SetSenderAndMessage(sender, message); - event = v8::Handle::Cast(native_event.ToV8()); + event = v8::Local::Cast(native_event.ToV8()); } else { event = CreateEventObject(isolate); } + mate::Dictionary(isolate, event).Set("sender", GetWrapper(isolate)); + return event; +} - // args = [name, event, args...]; - args.insert(args.begin(), event); - args.insert(args.begin(), mate::StringToV8(isolate, name)); - - // this.emit.apply(this, args); - node::MakeCallback(isolate, GetWrapper(isolate), "emit", args.size(), - &args[0]); - - return event->Get(StringToV8(isolate, "defaultPrevented"))->BooleanValue(); +v8::Local EventEmitter::CreateCustomEvent( + v8::Isolate* isolate, v8::Local custom_event) { + v8::Local event = CreateEventObject(isolate); + (void)event->SetPrototype(custom_event->CreationContext(), custom_event); + mate::Dictionary(isolate, event).Set("sender", GetWrapper(isolate)); + return event; } } // namespace mate diff --git a/atom/browser/api/event_emitter.h b/atom/browser/api/event_emitter.h index 854076044bde..42816d42a45b 100644 --- a/atom/browser/api/event_emitter.h +++ b/atom/browser/api/event_emitter.h @@ -7,6 +7,7 @@ #include +#include "atom/common/api/event_emitter_caller.h" #include "native_mate/wrappable.h" namespace content { @@ -22,10 +23,15 @@ namespace mate { // Provide helperers to emit event in JavaScript. class EventEmitter : public Wrappable { public: - typedef std::vector> ValueArray; + typedef std::vector> ValueArray; - protected: - EventEmitter(); + // this.emit(name, event, args...); + template + bool EmitCustomEvent(const base::StringPiece& name, + v8::Local event, + const Args&... args) { + return EmitWithEvent(name, CreateCustomEvent(isolate(), event), args...); + } // this.emit(name, new Event(), args...); template @@ -39,21 +45,33 @@ class EventEmitter : public Wrappable { content::WebContents* sender, IPC::Message* message, const Args&... args) { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - v8::Locker locker(isolate); - v8::HandleScope handle_scope(isolate); - - ValueArray converted = { ConvertToV8(isolate, args)... }; - return CallEmit(isolate, name, sender, message, converted); + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + v8::Local event = CreateJSEvent(isolate(), sender, message); + return EmitWithEvent(name, event, args...); } + protected: + EventEmitter(); + private: - // Lower level implementations. - bool CallEmit(v8::Isolate* isolate, - const base::StringPiece& name, - content::WebContents* sender, - IPC::Message* message, - ValueArray args); + // this.emit(name, event, args...); + template + bool EmitWithEvent(const base::StringPiece& name, + v8::Local event, + const Args&... args) { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + EmitEvent(isolate(), GetWrapper(isolate()), name, event, args...); + return event->Get( + StringToV8(isolate(), "defaultPrevented"))->BooleanValue(); + } + + v8::Local CreateJSEvent(v8::Isolate* isolate, + content::WebContents* sender, + IPC::Message* message); + v8::Local CreateCustomEvent( + v8::Isolate* isolate, v8::Local event); DISALLOW_COPY_AND_ASSIGN(EventEmitter); }; diff --git a/atom/browser/api/frame_subscriber.cc b/atom/browser/api/frame_subscriber.cc new file mode 100644 index 000000000000..526769f9cd6c --- /dev/null +++ b/atom/browser/api/frame_subscriber.cc @@ -0,0 +1,66 @@ +// 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 "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" + +namespace atom { + +namespace api { + +FrameSubscriber::FrameSubscriber(v8::Isolate* isolate, + const gfx::Size& size, + const FrameCaptureCallback& callback) + : isolate_(isolate), size_(size), callback_(callback) { +} + +bool FrameSubscriber::ShouldCaptureFrame( + const gfx::Rect& damage_rect, + base::TimeTicks present_time, + scoped_refptr* storage, + DeliverFrameCallback* callback) { + *storage = media::VideoFrame::CreateFrame(media::VideoFrame::YV12, size_, + gfx::Rect(size_), size_, + base::TimeDelta()); + *callback = base::Bind(&FrameSubscriber::OnFrameDelivered, + base::Unretained(this), + *storage); + return true; +} + +void FrameSubscriber::OnFrameDelivered( + scoped_refptr frame, base::TimeTicks, bool result) { + if (!result) + return; + + gfx::Rect rect = frame->visible_rect(); + size_t rgb_arr_size = rect.width() * rect.height() * 4; + 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); + + v8::Locker locker(isolate_); + v8::HandleScope handle_scope(isolate_); + callback_.Run(buffer.ToLocalChecked()); +} + +} // namespace api + +} // namespace atom diff --git a/atom/browser/api/frame_subscriber.h b/atom/browser/api/frame_subscriber.h new file mode 100644 index 000000000000..f7748aa5790d --- /dev/null +++ b/atom/browser/api/frame_subscriber.h @@ -0,0 +1,45 @@ +// 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_BROWSER_API_FRAME_SUBSCRIBER_H_ +#define ATOM_BROWSER_API_FRAME_SUBSCRIBER_H_ + +#include "base/callback.h" +#include "content/public/browser/render_widget_host_view_frame_subscriber.h" +#include "ui/gfx/geometry/size.h" +#include "v8/include/v8.h" + +namespace atom { + +namespace api { + +class FrameSubscriber : public content::RenderWidgetHostViewFrameSubscriber { + public: + using FrameCaptureCallback = base::Callback)>; + + FrameSubscriber(v8::Isolate* isolate, + const gfx::Size& size, + const FrameCaptureCallback& callback); + + bool ShouldCaptureFrame(const gfx::Rect& damage_rect, + base::TimeTicks present_time, + scoped_refptr* storage, + DeliverFrameCallback* callback) override; + + private: + void OnFrameDelivered( + scoped_refptr frame, base::TimeTicks, bool); + + v8::Isolate* isolate_; + gfx::Size size_; + FrameCaptureCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(FrameSubscriber); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_FRAME_SUBSCRIBER_H_ diff --git a/atom/browser/api/lib/app.coffee b/atom/browser/api/lib/app.coffee index aad56fd91238..f5b137eb0bb2 100644 --- a/atom/browser/api/lib/app.coffee +++ b/atom/browser/api/lib/app.coffee @@ -1,10 +1,15 @@ EventEmitter = require('events').EventEmitter bindings = process.atomBinding 'app' +sessionBindings = process.atomBinding 'session' app = bindings.app app.__proto__ = EventEmitter.prototype +wrapSession = (session) -> + # session is an Event Emitter. + session.__proto__ = EventEmitter.prototype + app.setApplicationMenu = (menu) -> require('menu').setApplicationMenu menu @@ -25,13 +30,26 @@ if process.platform is 'darwin' show: bindings.dockShow setMenu: bindings.dockSetMenu +appPath = null +app.setAppPath = (path) -> + appPath = path + +app.getAppPath = -> + appPath + # Be compatible with old API. -app.once 'ready', -> app.emit 'finish-launching' +app.once 'ready', -> @emit 'finish-launching' app.terminate = app.quit app.exit = process.exit -app.getHomeDir = -> app.getPath 'home' -app.getDataPath = -> app.getPath 'userData' -app.setDataPath = (path) -> app.setPath 'userData', path +app.getHomeDir = -> @getPath 'home' +app.getDataPath = -> @getPath 'userData' +app.setDataPath = (path) -> @setPath 'userData', path +app.resolveProxy = -> @defaultSession.resolveProxy.apply @defaultSession, arguments +app.on 'activate', (event, hasVisibleWindows) -> @emit 'activate-with-no-open-windows' if not hasVisibleWindows + +# Session wrapper. +sessionBindings._setWrapSession wrapSession +process.once 'exit', sessionBindings._clearWrapSession # Only one App object pemitted. module.exports = app diff --git a/atom/browser/api/lib/browser-window.coffee b/atom/browser/api/lib/browser-window.coffee index 93e744233885..2a92cfc55f88 100644 --- a/atom/browser/api/lib/browser-window.coffee +++ b/atom/browser/api/lib/browser-window.coffee @@ -1,78 +1,52 @@ EventEmitter = require('events').EventEmitter -IDWeakMap = require 'id-weak-map' app = require 'app' ipc = require 'ipc' -wrapWebContents = require('web-contents').wrap BrowserWindow = process.atomBinding('window').BrowserWindow BrowserWindow::__proto__ = EventEmitter.prototype -# Store all created windows in the weak map. -BrowserWindow.windows = new IDWeakMap - BrowserWindow::_init = -> # Simulate the application menu on platforms other than OS X. if process.platform isnt 'darwin' menu = app.getApplicationMenu() @setMenu menu if menu? - @webContents = @getWebContents() - @devToolsWebContents = null - @webContents.once 'destroyed', => @webContents = null - - # Remember the window ID. - Object.defineProperty this, 'id', - value: BrowserWindow.windows.add(this) - enumerable: true - # Make new windows requested by links behave like "window.open" - @on '-new-window', (event, url, frameName) => - event.sender = @webContents + @webContents.on '-new-window', (event, url, frameName) -> options = show: true, width: 800, height: 600 ipc.emit 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, options - # Redirect "will-navigate" to webContents. - @on '-will-navigate', (event, url) => - @webContents.emit 'will-navigate', event, url + # window.resizeTo(...) + # window.moveTo(...) + @webContents.on 'move', (event, size) => + @setBounds size - # Remove the window from weak map immediately when it's destroyed, since we - # could be iterating windows before GC happened. - @once 'closed', => - BrowserWindow.windows.remove @id if BrowserWindow.windows.has @id + # Hide the auto-hide menu when webContents is focused. + @webContents.on 'activate', => + if process.platform isnt 'darwin' and @isMenuBarAutoHide() and @isMenuBarVisible() + @setMenuBarVisibility false -BrowserWindow::openDevTools = (options={}) -> - options.detach ?= false - @_openDevTools !options.detach + # Forward the crashed event. + @webContents.on 'crashed', => + @emit 'crashed' - # Force devToolsWebContents to be created. - @devToolsWebContents = @getDevToolsWebContents() - @devToolsWebContents.once 'destroyed', => @devToolsWebContents = null + # 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. + @webContents.once 'load-url', -> + @focus() - # Emit devtools events. - @devToolsWebContents.once 'did-finish-load', => @emit 'devtools-opened' - @devToolsWebContents.once 'destroyed', => @emit 'devtools-closed' + # Redirect focus/blur event to app instance too. + @on 'blur', (event) => + app.emit 'browser-window-blur', event, this + @on 'focus', (event) => + app.emit 'browser-window-focus', event, this -BrowserWindow::toggleDevTools = -> - if @isDevToolsOpened() then @closeDevTools() else @openDevTools() - -BrowserWindow::getWebContents = -> - wrapWebContents @_getWebContents() - -BrowserWindow::getDevToolsWebContents = -> - wrapWebContents @_getDevToolsWebContents() - -BrowserWindow::setMenu = (menu) -> - if process.platform is 'darwin' - throw new Error('BrowserWindow.setMenu is not available on OS X') - - throw new TypeError('Invalid menu') unless menu?.constructor?.name is 'Menu' - - @menu = menu # Keep a reference of menu in case of GC. - @menu.attachToWindow this - -BrowserWindow.getAllWindows = -> - windows = BrowserWindow.windows - windows.get key for key in windows.keys() + # Notify the creation of the window. + app.emit 'browser-window-created', {}, this BrowserWindow.getFocusedWindow = -> windows = BrowserWindow.getAllWindows() @@ -80,20 +54,23 @@ BrowserWindow.getFocusedWindow = -> BrowserWindow.fromWebContents = (webContents) -> windows = BrowserWindow.getAllWindows() - return window for window in windows when webContents.equal window.webContents + return window for window in windows when window.webContents?.equal webContents BrowserWindow.fromDevToolsWebContents = (webContents) -> windows = BrowserWindow.getAllWindows() - return window for window in windows when webContents.equal window.devToolsWebContents - -BrowserWindow.fromId = (id) -> - BrowserWindow.windows.get id + return window for window in windows when window.devToolsWebContents?.equal webContents # Helpers. BrowserWindow::loadUrl = -> @webContents.loadUrl.apply @webContents, arguments BrowserWindow::send = -> @webContents.send.apply @webContents, arguments # Be compatible with old API. +BrowserWindow::undo = -> @webContents.undo() +BrowserWindow::redo = -> @webContents.redo() +BrowserWindow::cut = -> @webContents.cut() +BrowserWindow::copy = -> @webContents.copy() +BrowserWindow::paste = -> @webContents.paste() +BrowserWindow::selectAll = -> @webContents.selectAll() BrowserWindow::restart = -> @webContents.reload() BrowserWindow::getUrl = -> @webContents.getUrl() BrowserWindow::reload = -> @webContents.reload.apply @webContents, arguments @@ -102,10 +79,15 @@ BrowserWindow::getPageTitle = -> @webContents.getTitle() BrowserWindow::isLoading = -> @webContents.isLoading() BrowserWindow::isWaitingForResponse = -> @webContents.isWaitingForResponse() BrowserWindow::stop = -> @webContents.stop() -BrowserWindow::getRoutingId = -> @webContents.getRoutingId() -BrowserWindow::getProcessId = -> @webContents.getProcessId() BrowserWindow::isCrashed = -> @webContents.isCrashed() -BrowserWindow::executeJavaScriptInDevTools = (code) -> - @devToolsWebContents.executeJavaScript code +BrowserWindow::executeJavaScriptInDevTools = (code) -> @devToolsWebContents?.executeJavaScript code +BrowserWindow::openDevTools = -> @webContents.openDevTools.apply @webContents, arguments +BrowserWindow::closeDevTools = -> @webContents.closeDevTools() +BrowserWindow::isDevToolsOpened = -> @webContents.isDevToolsOpened() +BrowserWindow::toggleDevTools = -> @webContents.toggleDevTools() +BrowserWindow::inspectElement = -> @webContents.inspectElement.apply @webContents, arguments +BrowserWindow::inspectServiceWorker = -> @webContents.inspectServiceWorker() +BrowserWindow::print = -> @webContents.print.apply @webContents, arguments +BrowserWindow::printToPDF = -> @webContents.printToPDF.apply @webContents, arguments module.exports = BrowserWindow diff --git a/atom/browser/api/lib/content-tracing.coffee b/atom/browser/api/lib/content-tracing.coffee index 774661a1b867..08cd36e4aa59 100644 --- a/atom/browser/api/lib/content-tracing.coffee +++ b/atom/browser/api/lib/content-tracing.coffee @@ -1,7 +1 @@ module.exports = process.atomBinding 'content_tracing' - -# Mirrored from content::TracingController::Options -module.exports.DEFAULT_OPTIONS = 0 -module.exports.ENABLE_SYSTRACE = 1 << 0 -module.exports.ENABLE_SAMPLING = 1 << 1 -module.exports.RECORD_CONTINUOUSLY = 1 << 2 diff --git a/atom/browser/api/lib/dialog.coffee b/atom/browser/api/lib/dialog.coffee index 95ecf99d3e03..0843af04282e 100644 --- a/atom/browser/api/lib/dialog.coffee +++ b/atom/browser/api/lib/dialog.coffee @@ -9,7 +9,10 @@ fileDialogProperties = multiSelections: 1 << 2 createDirectory: 1 << 3 -messageBoxTypes = ['none', 'info', 'warning'] +messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] + +messageBoxOptions = + noLink: 1 << 0 parseArgs = (window, options, callback) -> unless window is null or window?.constructor is BrowserWindow @@ -93,9 +96,23 @@ module.exports = options.detail ?= '' options.icon ?= null + # Choose a default button to get selected when dialog is cancelled. + unless options.cancelId? + options.cancelId = 0 + for text, i in options.buttons + if text.toLowerCase() in ['cancel', 'no'] + options.cancelId = i + break + + flags = if options.noLink then messageBoxOptions.noLink else 0 + binding.showMessageBox messageBoxType, options.buttons, - [options.title, options.message, options.detail], + options.cancelId, + flags, + options.title, + options.message, + options.detail, options.icon, window, callback @@ -104,4 +121,5 @@ module.exports = binding.showErrorBox args... # Mark standard asynchronous functions. -v8Util.setHiddenValue f, 'asynchronous', true for k, f of module.exports +for api in ['showMessageBox', 'showOpenDialog', 'showSaveDialog'] + v8Util.setHiddenValue module.exports[api], 'asynchronous', true diff --git a/atom/browser/api/lib/menu-item.coffee b/atom/browser/api/lib/menu-item.coffee index d8e896f8ffa1..cfefeec4edbb 100644 --- a/atom/browser/api/lib/menu-item.coffee +++ b/atom/browser/api/lib/menu-item.coffee @@ -3,18 +3,30 @@ v8Util = process.atomBinding 'v8_util' 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' + class MenuItem @types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'] constructor: (options) -> Menu = require 'menu' - {click, @selector, @type, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked, @submenu} = options + {click, @selector, @type, @role, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked, @submenu} = options @type = 'submenu' if not @type? and @submenu? throw new Error('Invalid submenu') if @type is 'submenu' and @submenu?.constructor isnt Menu @overrideReadOnlyProperty 'type', 'normal' + @overrideReadOnlyProperty 'role' @overrideReadOnlyProperty 'accelerator' @overrideReadOnlyProperty 'icon' @overrideReadOnlyProperty 'submenu' @@ -27,12 +39,14 @@ class MenuItem throw new Error("Unknown menu type #{@type}") if MenuItem.types.indexOf(@type) is -1 @commandId = ++nextCommandId - @click = => + @click = (focusedWindow) => # Manually flip the checked flags when clicked. @checked = !@checked if @type in ['checkbox', 'radio'] - if typeof click is 'function' - click this, BrowserWindow.getFocusedWindow() + if @role and rolesMap[@role] and process.platform isnt 'darwin' + focusedWindow?[rolesMap[@role]]() + else if typeof click is 'function' + click this, focusedWindow else if typeof @selector is 'string' Menu.sendActionToFirstResponder @selector diff --git a/atom/browser/api/lib/menu.coffee b/atom/browser/api/lib/menu.coffee index b4b0dec23e20..3595ba1fe7db 100644 --- a/atom/browser/api/lib/menu.coffee +++ b/atom/browser/api/lib/menu.coffee @@ -67,7 +67,8 @@ Menu::_init = -> isCommandIdVisible: (commandId) => @commandsMap[commandId]?.visible getAcceleratorForCommandId: (commandId) => @commandsMap[commandId]?.accelerator getIconForCommandId: (commandId) => @commandsMap[commandId]?.icon - executeCommand: (commandId) => @commandsMap[commandId]?.click() + executeCommand: (commandId) => + @commandsMap[commandId]?.click BrowserWindow.getFocusedWindow() menuWillShow: => # Make sure radio groups have at least one menu item seleted. for id, group of @groupsMap @@ -115,6 +116,7 @@ Menu::insert = (pos, item) -> @setSublabel pos, item.sublabel if item.sublabel? @setIcon pos, item.icon if item.icon? + @setRole pos, item.role if item.role? # Make menu accessable to items. item.overrideReadOnlyProperty 'menu', this @@ -130,10 +132,11 @@ Menu::_callMenuWillShow = -> applicationMenu = null Menu.setApplicationMenu = (menu) -> - throw new TypeError('Invalid menu') unless menu?.constructor is Menu + throw new TypeError('Invalid menu') unless menu is null or menu.constructor is Menu applicationMenu = menu # Keep a reference. if process.platform is 'darwin' + return if menu is null menu._callMenuWillShow() bindings.setApplicationMenu menu else diff --git a/atom/browser/api/lib/navigation-controller.coffee b/atom/browser/api/lib/navigation-controller.coffee index f7945e07bf40..f78d92c341d6 100644 --- a/atom/browser/api/lib/navigation-controller.coffee +++ b/atom/browser/api/lib/navigation-controller.coffee @@ -4,6 +4,9 @@ ipc = require 'ipc' ipc.on 'ATOM_SHELL_NAVIGATION_CONTROLLER', (event, method, args...) -> event.sender[method] args... +ipc.on 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', (event, method, args...) -> + event.returnValue = event.sender[method] 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. @@ -11,10 +14,12 @@ ipc.on 'ATOM_SHELL_NAVIGATION_CONTROLLER', (event, method, args...) -> # process is restarted everytime. class NavigationController constructor: (@webContents) -> - @history = [] - @currentIndex = -1 - @pendingIndex = -1 - @inPageIndex = -1 + @clearHistory() + + # webContents may have already navigated to a page. + if @webContents._getUrl() + @currentIndex++ + @history.push @webContents._getUrl() @webContents.on 'navigation-entry-commited', (event, url, inPage, replaceEntry) => if @inPageIndex > -1 and not inPage @@ -40,6 +45,7 @@ class NavigationController loadUrl: (url, options={}) -> @pendingIndex = -1 @webContents._loadUrl url, options + @webContents.emit 'load-url', url, options getUrl: -> if @currentIndex is -1 @@ -71,6 +77,12 @@ class NavigationController canGoToOffset: (offset) -> @canGoToIndex @currentIndex + offset + clearHistory: -> + @history = [] + @currentIndex = -1 + @pendingIndex = -1 + @inPageIndex = -1 + goBack: -> return unless @canGoBack() @pendingIndex = @getActiveIndex() - 1 @@ -90,7 +102,7 @@ class NavigationController goToIndex: (index) -> return unless @canGoToIndex index @pendingIndex = index - @webContents._loadUrl @history[@pendingIndex].url, {} + @webContents._loadUrl @history[@pendingIndex], {} goToOffset: (offset) -> return unless @canGoToOffset offset @@ -104,4 +116,7 @@ class NavigationController getActiveIndex: -> if @pendingIndex is -1 then @currentIndex else @pendingIndex + length: -> + @history.length + module.exports = NavigationController diff --git a/atom/browser/api/lib/power-save-blocker.coffee b/atom/browser/api/lib/power-save-blocker.coffee new file mode 100644 index 000000000000..7f428bc40f19 --- /dev/null +++ b/atom/browser/api/lib/power-save-blocker.coffee @@ -0,0 +1,3 @@ +bindings = process.atomBinding 'power_save_blocker' + +module.exports = bindings.powerSaveBlocker diff --git a/atom/browser/api/lib/protocol.coffee b/atom/browser/api/lib/protocol.coffee index c2db7800c08d..13f2a6d10270 100644 --- a/atom/browser/api/lib/protocol.coffee +++ b/atom/browser/api/lib/protocol.coffee @@ -2,36 +2,23 @@ app = require 'app' throw new Error('Can not initialize protocol module before app is ready') unless app.isReady() protocol = process.atomBinding('protocol').protocol -EventEmitter = require('events').EventEmitter -protocol.__proto__ = EventEmitter.prototype - -protocol.RequestStringJob = -class RequestStringJob - constructor: ({mimeType, charset, data}) -> - if typeof data isnt 'string' and not data instanceof Buffer - throw new TypeError('Data should be string or Buffer') - - @mimeType = mimeType ? 'text/plain' - @charset = charset ? 'UTF-8' - @data = String data - -protocol.RequestBufferJob = -class RequestBufferJob - constructor: ({mimeType, encoding, data}) -> - if not data instanceof Buffer - throw new TypeError('Data should be Buffer') - - @mimeType = mimeType ? 'application/octet-stream' - @encoding = encoding ? 'utf8' - @data = new Buffer(data) - -protocol.RequestFileJob = -class RequestFileJob - constructor: (@path) -> - -protocol.RequestErrorJob = -class RequestErrorJob - constructor: (@error) -> +# Warn about removed APIs. +logAndThrow = (callback, message) -> + console.error message + if callback then callback(new Error(message)) else throw new Error(message) +protocol.registerProtocol = (scheme, handler, callback) -> + 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 = (scheme, callback) -> + logAndThrow callback, + 'isHandledProtocol API has been replaced by isProtocolHandled.' +protocol.interceptProtocol = (scheme, handler, callback) -> + 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/tray.coffee b/atom/browser/api/lib/tray.coffee index 7d158a9a0104..1c225ddd403c 100644 --- a/atom/browser/api/lib/tray.coffee +++ b/atom/browser/api/lib/tray.coffee @@ -3,8 +3,12 @@ bindings = process.atomBinding 'tray' Tray = bindings.Tray Tray::__proto__ = EventEmitter.prototype + Tray::setContextMenu = (menu) -> @_setContextMenu menu @menu = menu # Keep a strong reference of menu. +# Keep compatibility with old APIs. +Tray::popContextMenu = Tray::popUpContextMenu + module.exports = Tray diff --git a/atom/browser/api/lib/web-contents.coffee b/atom/browser/api/lib/web-contents.coffee index eb86c94500ce..3a2abfb5155f 100644 --- a/atom/browser/api/lib/web-contents.coffee +++ b/atom/browser/api/lib/web-contents.coffee @@ -3,9 +3,38 @@ NavigationController = require './navigation-controller' binding = process.atomBinding 'web_contents' ipc = require 'ipc' -module.exports.wrap = (webContents) -> - return null unless webContents.isAlive() +nextId = 0 +getNextId = -> ++nextId +PDFPageSize = + 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" + +wrapWebContents = (webContents) -> # webContents is an EventEmitter. webContents.__proto__ = EventEmitter.prototype @@ -15,17 +44,11 @@ module.exports.wrap = (webContents) -> # Make sure webContents.executeJavaScript would run the code only when the # web contents has been loaded. - webContents.loaded = false - webContents.once 'did-finish-load', -> @loaded = true - webContents.executeJavaScript = (code) -> - if @loaded - @_executeJavaScript code + webContents.executeJavaScript = (code, hasUserGesture=false) -> + if @getUrl() and not @isLoading() + @_executeJavaScript code, hasUserGesture else - webContents.once 'did-finish-load', @_executeJavaScript.bind(this, code) - - # The processId and routingId and identify a webContents. - webContents.getId = -> "#{@getProcessId()}-#{@getRoutingId()}" - webContents.equal = (other) -> @getId() is other.getId() + webContents.once 'did-finish-load', @_executeJavaScript.bind(this, code, hasUserGesture) # The navigation controller. controller = new NavigationController(webContents) @@ -33,34 +56,57 @@ module.exports.wrap = (webContents) -> do (name, method) -> webContents[name] = -> method.apply controller, arguments - # Translate |disposition| to string for 'new-window' event. - webContents.on '-new-window', (args..., disposition) -> - disposition = - switch disposition - when 2 then 'default' - when 4 then 'foreground-tab' - when 5 then 'background-tab' - when 6, 7 then 'new-window' - else 'other' - @emit 'new-window', args..., disposition - - # Tell the rpc server that a render view has been deleted and we need to - # release all objects owned by it. - webContents.on 'render-view-deleted', (event, processId, routingId) -> - process.emit 'ATOM_BROWSER_RELEASE_RENDER_VIEW', "#{processId}-#{routingId}" - # Dispatch IPC messages to the ipc module. webContents.on 'ipc-message', (event, packed) -> [channel, args...] = packed - Object.defineProperty event, 'sender', value: webContents ipc.emit channel, event, args... webContents.on 'ipc-message-sync', (event, packed) -> [channel, args...] = packed Object.defineProperty event, 'returnValue', set: (value) -> event.sendReply JSON.stringify(value) - Object.defineProperty event, 'sender', value: webContents ipc.emit channel, event, args... - webContents + webContents.printToPDF = (options, callback) -> + 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 and PDFPageSize[options.pageSize] + printingSetting.mediaSize = PDFPageSize[options.pageSize] + else + printingSetting.mediaSize = PDFPageSize['A4'] + + @_printToPDF printingSetting, callback + +binding._setWrapWebContents wrapWebContents +process.once 'exit', binding._clearWrapWebContents module.exports.create = (options={}) -> - @wrap binding.create(options) + binding.create(options) diff --git a/atom/browser/api/trackable_object.cc b/atom/browser/api/trackable_object.cc new file mode 100644 index 000000000000..50bfa59e6a7a --- /dev/null +++ b/atom/browser/api/trackable_object.cc @@ -0,0 +1,71 @@ +// 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 "atom/browser/api/trackable_object.h" + +#include "atom/browser/atom_browser_main_parts.h" +#include "base/bind.h" +#include "base/supports_user_data.h" + +namespace mate { + +namespace { + +const char* kTrackedObjectKey = "TrackedObjectKey"; + +class IDUserData : public base::SupportsUserData::Data { + public: + explicit IDUserData(int32_t id) : id_(id) {} + + operator int32_t() const { return id_; } + + private: + int32_t id_; + + DISALLOW_COPY_AND_ASSIGN(IDUserData); +}; + +} // namespace + +TrackableObjectBase::TrackableObjectBase() + : weak_map_id_(0), wrapped_(nullptr), weak_factory_(this) { + RegisterDestructionCallback( + base::Bind(&TrackableObjectBase::Destroy, weak_factory_.GetWeakPtr())); +} + +TrackableObjectBase::~TrackableObjectBase() { +} + +void TrackableObjectBase::AfterInit(v8::Isolate* isolate) { + if (wrapped_) + AttachAsUserData(wrapped_); +} + +void TrackableObjectBase::AttachAsUserData(base::SupportsUserData* wrapped) { + if (weak_map_id_ != 0) { + wrapped->SetUserData(kTrackedObjectKey, new IDUserData(weak_map_id_)); + wrapped_ = nullptr; + } else { + // If the TrackableObjectBase is not ready yet then delay SetUserData until + // AfterInit is called. + wrapped_ = wrapped; + } +} + +// static +int32_t TrackableObjectBase::GetIDFromWrappedClass(base::SupportsUserData* w) { + auto id = static_cast(w->GetUserData(kTrackedObjectKey)); + if (id) + return *id; + else + return 0; +} + +// static +void TrackableObjectBase::RegisterDestructionCallback( + const base::Closure& closure) { + atom::AtomBrowserMainParts::Get()->RegisterDestructionCallback(closure); +} + +} // namespace mate diff --git a/atom/browser/api/trackable_object.h b/atom/browser/api/trackable_object.h new file mode 100644 index 000000000000..8ff7d1c04041 --- /dev/null +++ b/atom/browser/api/trackable_object.h @@ -0,0 +1,133 @@ +// 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_BROWSER_API_TRACKABLE_OBJECT_H_ +#define ATOM_BROWSER_API_TRACKABLE_OBJECT_H_ + +#include + +#include "atom/browser/api/event_emitter.h" +#include "atom/common/id_weak_map.h" +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" + +namespace base { +class SupportsUserData; +} + +namespace mate { + +// Users should use TrackableObject instead. +class TrackableObjectBase : public mate::EventEmitter { + public: + TrackableObjectBase(); + + // The ID in weak map. + int32_t weak_map_id() const { return weak_map_id_; } + + // Wrap TrackableObject into a class that SupportsUserData. + void AttachAsUserData(base::SupportsUserData* wrapped); + + // Subclasses should implement this to destroy their native types. + virtual void Destroy() = 0; + + protected: + ~TrackableObjectBase() override; + + // mate::Wrappable: + void AfterInit(v8::Isolate* isolate) override; + + // Get the weak_map_id from SupportsUserData. + static int32_t GetIDFromWrappedClass(base::SupportsUserData* wrapped); + + // Register a callback that should be destroyed before JavaScript environment + // gets destroyed. + static void RegisterDestructionCallback(const base::Closure& closure); + + int32_t weak_map_id_; + base::SupportsUserData* wrapped_; + + private: + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(TrackableObjectBase); +}; + +// All instances of TrackableObject will be kept in a weak map and can be got +// from its ID. +template +class TrackableObject : public TrackableObjectBase { + public: + // Finds out the TrackableObject from its ID in weak map. + static T* FromWeakMapID(v8::Isolate* isolate, int32_t id) { + if (!weak_map_) + return nullptr; + + v8::MaybeLocal object = weak_map_->Get(isolate, id); + if (object.IsEmpty()) + return nullptr; + + T* self = nullptr; + mate::ConvertFromV8(isolate, object.ToLocalChecked(), &self); + return self; + } + + // Finds out the TrackableObject from the class it wraps. + static T* FromWrappedClass(v8::Isolate* isolate, + base::SupportsUserData* wrapped) { + int32_t id = GetIDFromWrappedClass(wrapped); + if (!id) + return nullptr; + return FromWeakMapID(isolate, id); + } + + // Returns all objects in this class's weak map. + static std::vector> GetAll(v8::Isolate* isolate) { + if (weak_map_) + return weak_map_->Values(isolate); + else + return std::vector>(); + } + + TrackableObject() { + RegisterDestructionCallback( + base::Bind(&TrackableObject::ReleaseAllWeakReferences)); + } + + // Removes this instance from the weak map. + void RemoveFromWeakMap() { + if (weak_map_ && weak_map_->Has(weak_map_id())) + weak_map_->Remove(weak_map_id()); + } + + protected: + ~TrackableObject() override { + RemoveFromWeakMap(); + } + + void AfterInit(v8::Isolate* isolate) override { + if (!weak_map_) + weak_map_.reset(new atom::IDWeakMap); + weak_map_id_ = weak_map_->Add(isolate, GetWrapper(isolate)); + TrackableObjectBase::AfterInit(isolate); + } + + private: + // Releases all weak references in weak map, called when app is terminating. + static void ReleaseAllWeakReferences() { + weak_map_.reset(); + } + + static scoped_ptr weak_map_; + + DISALLOW_COPY_AND_ASSIGN(TrackableObject); +}; + +template +scoped_ptr TrackableObject::weak_map_; + +} // namespace mate + +#endif // ATOM_BROWSER_API_TRACKABLE_OBJECT_H_ diff --git a/atom/browser/atom_access_token_store.cc b/atom/browser/atom_access_token_store.cc index 5e117869a4ee..3d254f060188 100644 --- a/atom/browser/atom_access_token_store.cc +++ b/atom/browser/atom_access_token_store.cc @@ -7,7 +7,9 @@ #include #include "atom/browser/atom_browser_context.h" +#include "atom/browser/atom_browser_main_parts.h" #include "atom/common/google_api_key.h" +#include "content/public/browser/geolocation_provider.h" namespace atom { @@ -23,6 +25,7 @@ const char* kGeolocationProviderUrl = } // namespace AtomAccessTokenStore::AtomAccessTokenStore() { + content::GeolocationProvider::GetInstance()->UserDidOptIntoLocationServices(); } AtomAccessTokenStore::~AtomAccessTokenStore() { @@ -39,8 +42,8 @@ void AtomAccessTokenStore::LoadAccessTokens( token_pair.first = GURL(kGeolocationProviderUrl); access_token_set.insert(token_pair); - callback.Run(access_token_set, - AtomBrowserContext::Get()->url_request_context_getter()); + auto browser_context = AtomBrowserMainParts::Get()->browser_context(); + callback.Run(access_token_set, browser_context->url_request_context_getter()); } void AtomAccessTokenStore::SaveAccessToken(const GURL& server_url, diff --git a/atom/browser/atom_browser_client.cc b/atom/browser/atom_browser_client.cc index fbc09d9fe15f..e45caceab01e 100644 --- a/atom/browser/atom_browser_client.cc +++ b/atom/browser/atom_browser_client.cc @@ -4,53 +4,75 @@ #include "atom/browser/atom_browser_client.h" +#if defined(OS_WIN) +#include +#endif + #include "atom/browser/atom_access_token_store.h" #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" -#include "atom/browser/atom_resource_dispatcher_host_delegate.h" +#include "atom/browser/atom_quota_permission_context.h" #include "atom/browser/atom_speech_recognition_manager_delegate.h" +#include "atom/browser/browser.h" #include "atom/browser/native_window.h" -#include "atom/browser/web_view_manager.h" +#include "atom/browser/web_contents_preferences.h" #include "atom/browser/window_list.h" #include "atom/common/options_switches.h" #include "base/command_line.h" +#include "base/files/file_util.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" #include "base/strings/string_number_conversions.h" #include "chrome/browser/printing/printing_message_filter.h" #include "chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h" #include "chrome/browser/speech/tts_message_filter.h" #include "content/public/browser/browser_ppapi_host.h" +#include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" -#include "content/public/browser/resource_dispatcher_host.h" #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" #include "content/public/common/web_preferences.h" +#include "net/cert/x509_certificate.h" +#include "net/ssl/ssl_cert_request_info.h" #include "ppapi/host/ppapi_host.h" #include "ui/base/l10n/l10n_util.h" +#include "v8/include/v8.h" 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; -struct FindByProcessId { - explicit FindByProcessId(int child_process_id) - : child_process_id_(child_process_id) { - } +// Custom schemes to be registered to standard. +std::string g_custom_schemes = ""; - bool operator() (NativeWindow* const window) { - content::WebContents* web_contents = window->GetWebContents(); - if (!web_contents) - return false; +scoped_refptr ImportCertFromFile( + const base::FilePath& path) { + if (path.empty()) + return nullptr; - int id = window->GetWebContents()->GetRenderProcessHost()->GetID(); - return id == child_process_id_; - } + std::string cert_data; + if (!base::ReadFileToString(path, &cert_data)) + return nullptr; - int child_process_id_; -}; + net::CertificateList certs = + net::X509Certificate::CreateCertificateListFromBytes( + cert_data.data(), cert_data.size(), + net::X509Certificate::FORMAT_AUTO); + + if (certs.empty()) + return nullptr; + + return certs[0]; +} } // namespace @@ -59,8 +81,12 @@ void AtomBrowserClient::SuppressRendererProcessRestartForOnce() { g_suppress_renderer_process_restart = true; } -AtomBrowserClient::AtomBrowserClient() - : dying_render_process_(nullptr) { +void AtomBrowserClient::SetCustomSchemes( + const std::vector& schemes) { + g_custom_schemes = JoinString(schemes, ','); +} + +AtomBrowserClient::AtomBrowserClient() { } AtomBrowserClient::~AtomBrowserClient() { @@ -68,9 +94,9 @@ AtomBrowserClient::~AtomBrowserClient() { void AtomBrowserClient::RenderProcessWillLaunch( content::RenderProcessHost* host) { - int id = host->GetID(); - host->AddFilter(new printing::PrintingMessageFilter(host->GetID())); - host->AddFilter(new TtsMessageFilter(id, host->GetBrowserContext())); + int process_id = host->GetID(); + host->AddFilter(new printing::PrintingMessageFilter(process_id)); + host->AddFilter(new TtsMessageFilter(process_id, host->GetBrowserContext())); } content::SpeechRecognitionManagerDelegate* @@ -82,15 +108,8 @@ content::AccessTokenStore* AtomBrowserClient::CreateAccessTokenStore() { return new AtomAccessTokenStore; } -void AtomBrowserClient::ResourceDispatcherHostCreated() { - resource_dispatcher_delegate_.reset(new AtomResourceDispatcherHostDelegate); - content::ResourceDispatcherHost::Get()->SetDelegate( - resource_dispatcher_delegate_.get()); -} - void AtomBrowserClient::OverrideWebkitPrefs( - content::RenderViewHost* render_view_host, - content::WebPreferences* prefs) { + content::RenderViewHost* host, content::WebPreferences* prefs) { prefs->javascript_enabled = true; prefs->web_security_enabled = true; prefs->javascript_can_open_windows_automatically = true; @@ -108,26 +127,9 @@ void AtomBrowserClient::OverrideWebkitPrefs( prefs->allow_displaying_insecure_content = false; prefs->allow_running_insecure_content = false; - // Turn off web security for devtools. - auto web_contents = content::WebContents::FromRenderViewHost( - render_view_host); - if (web_contents && web_contents->GetURL().SchemeIs("chrome-devtools")) { - prefs->web_security_enabled = false; - return; - } - // Custom preferences of guest page. - auto process = render_view_host->GetProcess(); - WebViewManager::WebViewInfo info; - if (WebViewManager::GetInfoForProcess(process, &info)) { - prefs->web_security_enabled = !info.disable_web_security; - return; - } - - NativeWindow* window = NativeWindow::FromRenderView( - process->GetID(), render_view_host->GetRoutingID()); - if (window) - window->OverrideWebkitPrefs(prefs); + auto web_contents = content::WebContents::FromRenderViewHost(host); + WebContentsPreferences::OverrideWebkitPrefs(web_contents, prefs); } std::string AtomBrowserClient::GetApplicationLocale() { @@ -144,82 +146,84 @@ void AtomBrowserClient::OverrideSiteInstanceForNavigation( return; } - if (current_instance->HasProcess()) - dying_render_process_ = current_instance->GetProcess(); + // Restart renderer process for all navigations except "javacript:" scheme. + if (url.SchemeIs(url::kJavaScriptScheme)) + return; - // Restart renderer process for all navigations. *new_instance = content::SiteInstance::CreateForURL(browser_context, url); + + // Remember the original renderer process of the pending renderer process. + auto current_process = current_instance->GetProcess(); + auto pending_process = (*new_instance)->GetProcess(); + pending_processes_[pending_process->GetID()] = current_process->GetID(); + // Clear the entry in map when process ends. + current_process->AddObserver(this); } void AtomBrowserClient::AppendExtraCommandLineSwitches( base::CommandLine* command_line, - int child_process_id) { + int process_id) { std::string process_type = command_line->GetSwitchValueASCII("type"); if (process_type != "renderer") return; - WindowList* list = WindowList::GetInstance(); - NativeWindow* window = nullptr; + // The registered standard schemes. + if (!g_custom_schemes.empty()) + command_line->AppendSwitchASCII(switches::kRegisterStandardSchemes, + g_custom_schemes); - // Find the owner of this child process. - WindowList::const_iterator iter = std::find_if( - list->begin(), list->end(), FindByProcessId(child_process_id)); - if (iter != list->end()) - window = *iter; - - // If the render process is a newly started one, which means the window still - // uses the old going-to-be-swapped render process, then we try to find the - // window from the swapped render process. - if (!window && dying_render_process_) { - int dying_process_id = dying_render_process_->GetID(); - WindowList::const_iterator iter = std::find_if( - list->begin(), list->end(), FindByProcessId(dying_process_id)); - if (iter != list->end()) { - window = *iter; - child_process_id = dying_process_id; - } else { - // It appears that the dying process doesn't belong to a BrowserWindow, - // then it might be a guest process, if it is we should update its - // process ID in the WebViewManager. - auto child_process = content::RenderProcessHost::FromID(child_process_id); - // Update the process ID in webview guests. - WebViewManager::UpdateGuestProcessID(dying_render_process_, - child_process); - } +#if defined(OS_WIN) + // Append --app-user-model-id. + PWSTR current_app_id; + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(¤t_app_id))) { + command_line->AppendSwitchNative(switches::kAppUserModelId, current_app_id); + CoTaskMemFree(current_app_id); } +#endif - if (window) { - window->AppendExtraCommandLineSwitches(command_line, child_process_id); - } else { - // Append commnad line arguments for guest web view. - auto child_process = content::RenderProcessHost::FromID(child_process_id); - WebViewManager::WebViewInfo info; - if (WebViewManager::GetInfoForProcess(child_process, &info)) { - command_line->AppendSwitchASCII( - switches::kGuestInstanceID, - base::IntToString(info.guest_instance_id)); - command_line->AppendSwitchASCII( - switches::kNodeIntegration, - info.node_integration ? "true" : "false"); - if (info.plugins) - command_line->AppendSwitch(switches::kEnablePlugins); - if (!info.preload_script.empty()) - command_line->AppendSwitchPath( - switches::kPreloadScript, - info.preload_script); - } - } + // If the process is a pending process, we should use the old one. + if (ContainsKey(pending_processes_, process_id)) + process_id = pending_processes_[process_id]; - dying_render_process_ = nullptr; + // Get the WebContents of the render process. + content::WebContents* web_contents = content::WebContents::FromRenderViewHost( + content::RenderViewHost::FromID(process_id, kDefaultRoutingID)); + if (!web_contents) + return; + + WebContentsPreferences::AppendExtraCommandLineSwitches( + web_contents, command_line); } void AtomBrowserClient::DidCreatePpapiPlugin( - content::BrowserPpapiHost* browser_host) { - auto command_line = base::CommandLine::ForCurrentProcess(); - if (command_line->HasSwitch(switches::kEnablePlugins)) - browser_host->GetPpapiHost()->AddHostFactoryFilter( - scoped_ptr( - new chrome::ChromeBrowserPepperHostFactory(browser_host))); + content::BrowserPpapiHost* host) { + host->GetPpapiHost()->AddHostFactoryFilter( + make_scoped_ptr(new chrome::ChromeBrowserPepperHostFactory(host))); +} + +content::QuotaPermissionContext* + AtomBrowserClient::CreateQuotaPermissionContext() { + return new AtomQuotaPermissionContext; +} + +void AtomBrowserClient::SelectClientCertificate( + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + scoped_ptr delegate) { + // --client-certificate=`path` + auto cmd = base::CommandLine::ForCurrentProcess(); + if (cmd->HasSwitch(switches::kClientCertificate)) { + auto cert_path = cmd->GetSwitchValuePath(switches::kClientCertificate); + auto certificate = ImportCertFromFile(cert_path); + if (certificate.get()) + delegate->ContinueWithCertificate(certificate.get()); + return; + } + + if (!cert_request_info->client_certs.empty()) + Browser::Get()->ClientCertificateSelector(web_contents, + cert_request_info, + delegate.Pass()); } brightray::BrowserMainParts* AtomBrowserClient::OverrideCreateBrowserMainParts( @@ -228,4 +232,15 @@ brightray::BrowserMainParts* AtomBrowserClient::OverrideCreateBrowserMainParts( return new AtomBrowserMainParts; } +void AtomBrowserClient::RenderProcessHostDestroyed( + content::RenderProcessHost* host) { + int process_id = host->GetID(); + for (const auto& entry : pending_processes_) { + if (entry.first == process_id || entry.second == process_id) { + pending_processes_.erase(entry.first); + break; + } + } +} + } // namespace atom diff --git a/atom/browser/atom_browser_client.h b/atom/browser/atom_browser_client.h index cc69a1e12de8..a0217efede9f 100644 --- a/atom/browser/atom_browser_client.h +++ b/atom/browser/atom_browser_client.h @@ -5,21 +5,34 @@ #ifndef ATOM_BROWSER_ATOM_BROWSER_CLIENT_H_ #define ATOM_BROWSER_ATOM_BROWSER_CLIENT_H_ +#include #include +#include #include "brightray/browser/browser_client.h" +#include "content/public/browser/render_process_host_observer.h" + +namespace content { +class QuotaPermissionContext; +class ClientCertificateDelegate; +} + +namespace net { +class SSLCertRequestInfo; +} namespace atom { -class AtomResourceDispatcherHostDelegate; - -class AtomBrowserClient : public brightray::BrowserClient { +class AtomBrowserClient : public brightray::BrowserClient, + public content::RenderProcessHostObserver { public: AtomBrowserClient(); virtual ~AtomBrowserClient(); // Don't force renderer process to restart for once. static void SuppressRendererProcessRestartForOnce(); + // Custom schemes to be registered to standard. + static void SetCustomSchemes(const std::vector& schemes); protected: // content::ContentBrowserClient: @@ -27,7 +40,6 @@ class AtomBrowserClient : public brightray::BrowserClient { content::SpeechRecognitionManagerDelegate* CreateSpeechRecognitionManagerDelegate() override; content::AccessTokenStore* CreateAccessTokenStore() override; - void ResourceDispatcherHostCreated() override; void OverrideWebkitPrefs(content::RenderViewHost* render_view_host, content::WebPreferences* prefs) override; std::string GetApplicationLocale() override; @@ -35,19 +47,26 @@ class AtomBrowserClient : public brightray::BrowserClient { content::BrowserContext* browser_context, content::SiteInstance* current_instance, const GURL& dest_url, - content::SiteInstance** new_instance); + content::SiteInstance** new_instance) override; void AppendExtraCommandLineSwitches(base::CommandLine* command_line, int child_process_id) override; void DidCreatePpapiPlugin(content::BrowserPpapiHost* browser_host) override; + content::QuotaPermissionContext* CreateQuotaPermissionContext() override; + void SelectClientCertificate( + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + scoped_ptr delegate) override; - private: + // brightray::BrowserClient: brightray::BrowserMainParts* OverrideCreateBrowserMainParts( const content::MainFunctionParams&) override; - scoped_ptr resource_dispatcher_delegate_; + // content::RenderProcessHostObserver: + void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; - // The render process which would be swapped out soon. - content::RenderProcessHost* dying_render_process_; + private: + // pending_render_process => current_render_process. + std::map pending_processes_; DISALLOW_COPY_AND_ASSIGN(AtomBrowserClient); }; diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index f0a92af9a3f0..d1ef09e70ff6 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -5,18 +5,31 @@ #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/atom_download_manager_delegate.h" +#include "atom/browser/browser.h" #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/web_view_manager.h" +#include "atom/common/atom_version.h" +#include "atom/common/chrome_version.h" #include "atom/common/options_switches.h" #include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" #include "base/threading/sequenced_worker_pool.h" #include "base/threading/worker_pool.h" -#include "chrome/browser/browser_process.h" +#include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" #include "content/public/common/url_constants.h" +#include "content/public/common/user_agent.h" +#include "net/ftp/ftp_network_layer.h" #include "net/url_request/data_protocol_handler.h" +#include "net/url_request/ftp_protocol_handler.h" #include "net/url_request/url_request_intercepting_job_factory.h" +#include "net/url_request/url_request_context.h" #include "url/url_constants.h" using content::BrowserThread; @@ -33,32 +46,78 @@ class NoCacheBackend : public net::HttpCache::BackendFactory { } }; +std::string RemoveWhitespace(const std::string& str) { + std::string trimmed; + if (base::RemoveChars(str, " ", &trimmed)) + return trimmed; + else + return str; +} + } // namespace -AtomBrowserContext::AtomBrowserContext() - : fake_browser_process_(new BrowserProcess), +AtomBrowserContext::AtomBrowserContext(const std::string& partition, + bool in_memory) + : brightray::BrowserContext(partition, in_memory), job_factory_(new AtomURLRequestJobFactory) { } AtomBrowserContext::~AtomBrowserContext() { } +std::string AtomBrowserContext::GetUserAgent() { + Browser* browser = Browser::Get(); + std::string name = RemoveWhitespace(browser->GetName()); + std::string user_agent; + if (name == ATOM_PRODUCT_NAME) { + user_agent = "Chrome/" CHROME_VERSION_STRING " " + ATOM_PRODUCT_NAME "/" ATOM_VERSION_STRING; + } else { + user_agent = base::StringPrintf( + "%s/%s Chrome/%s " ATOM_PRODUCT_NAME "/" ATOM_VERSION_STRING, + name.c_str(), + browser->GetVersion().c_str(), + CHROME_VERSION_STRING); + } + return content::BuildUserAgentFromProduct(user_agent); +} + net::URLRequestJobFactory* AtomBrowserContext::CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, content::URLRequestInterceptorScopedVector* interceptors) { scoped_ptr job_factory(job_factory_); - for (content::ProtocolHandlerMap::iterator it = handlers->begin(); - it != handlers->end(); ++it) - job_factory->SetProtocolHandler(it->first, it->second.release()); + for (auto& it : *handlers) { + job_factory->SetProtocolHandler(it.first, + make_scoped_ptr(it.second.release())); + } handlers->clear(); job_factory->SetProtocolHandler( - url::kDataScheme, new net::DataProtocolHandler); + url::kDataScheme, make_scoped_ptr(new net::DataProtocolHandler)); job_factory->SetProtocolHandler( - url::kFileScheme, new asar::AsarProtocolHandler( + url::kFileScheme, make_scoped_ptr(new asar::AsarProtocolHandler( BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( - base::SequencedWorkerPool::SKIP_ON_SHUTDOWN))); + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)))); + job_factory->SetProtocolHandler( + url::kHttpScheme, + make_scoped_ptr(new HttpProtocolHandler(url::kHttpScheme))); + job_factory->SetProtocolHandler( + url::kHttpsScheme, + make_scoped_ptr(new HttpProtocolHandler(url::kHttpsScheme))); + job_factory->SetProtocolHandler( + url::kWsScheme, + make_scoped_ptr(new HttpProtocolHandler(url::kWsScheme))); + job_factory->SetProtocolHandler( + url::kWssScheme, + make_scoped_ptr(new HttpProtocolHandler(url::kWssScheme))); + + auto host_resolver = + url_request_context_getter()->GetURLRequestContext()->host_resolver(); + job_factory->SetProtocolHandler( + url::kFtpScheme, + make_scoped_ptr(new net::FtpProtocolHandler( + new net::FtpNetworkLayer(host_resolver)))); // Set up interceptors in the reverse order. scoped_ptr top_job_factory = job_factory.Pass(); @@ -81,16 +140,37 @@ AtomBrowserContext::CreateHttpCacheBackendFactory( return brightray::BrowserContext::CreateHttpCacheBackendFactory(base_path); } +content::DownloadManagerDelegate* +AtomBrowserContext::GetDownloadManagerDelegate() { + if (!download_manager_delegate_.get()) { + auto download_manager = content::BrowserContext::GetDownloadManager(this); + download_manager_delegate_.reset( + new AtomDownloadManagerDelegate(download_manager)); + } + return download_manager_delegate_.get(); +} + content::BrowserPluginGuestManager* AtomBrowserContext::GetGuestManager() { if (!guest_manager_) - guest_manager_.reset(new WebViewManager(this)); + guest_manager_.reset(new WebViewManager); return guest_manager_.get(); } -// static -AtomBrowserContext* AtomBrowserContext::Get() { - return static_cast( - AtomBrowserMainParts::Get()->browser_context()); +void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { + pref_registry->RegisterFilePathPref(prefs::kSelectFileLastDirectory, + base::FilePath()); + pref_registry->RegisterFilePathPref(prefs::kDownloadDefaultDirectory, + base::FilePath()); } } // namespace atom + +namespace brightray { + +// static +scoped_refptr BrowserContext::Create( + const std::string& partition, bool in_memory) { + return make_scoped_refptr(new atom::AtomBrowserContext(partition, in_memory)); +} + +} // namespace brightray diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index ab28c3e81a0c..c99461ad9a86 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -5,24 +5,23 @@ #ifndef ATOM_BROWSER_ATOM_BROWSER_CONTEXT_H_ #define ATOM_BROWSER_ATOM_BROWSER_CONTEXT_H_ -#include "brightray/browser/browser_context.h" +#include -class BrowserProcess; +#include "brightray/browser/browser_context.h" namespace atom { +class AtomDownloadManagerDelegate; class AtomURLRequestJobFactory; class WebViewManager; class AtomBrowserContext : public brightray::BrowserContext { public: - AtomBrowserContext(); - virtual ~AtomBrowserContext(); - - // Returns the browser context singleton. - static AtomBrowserContext* Get(); + AtomBrowserContext(const std::string& partition, bool in_memory); + ~AtomBrowserContext() override; // brightray::URLRequestContextGetter::Delegate: + std::string GetUserAgent() override; net::URLRequestJobFactory* CreateURLRequestJobFactory( content::ProtocolHandlerMap* handlers, content::URLRequestInterceptorScopedVector* interceptors) override; @@ -30,16 +29,20 @@ class AtomBrowserContext : public brightray::BrowserContext { const base::FilePath& base_path) override; // content::BrowserContext: + content::DownloadManagerDelegate* GetDownloadManagerDelegate() override; content::BrowserPluginGuestManager* GetGuestManager() override; + // brightray::BrowserContext: + void RegisterPrefs(PrefRegistrySimple* pref_registry) override; + AtomURLRequestJobFactory* job_factory() const { return job_factory_; } private: - // A fake BrowserProcess object that used to feed the source code from chrome. - scoped_ptr fake_browser_process_; + scoped_ptr download_manager_delegate_; scoped_ptr guest_manager_; - AtomURLRequestJobFactory* job_factory_; // Weak reference. + // Managed by brightray::BrowserContext. + AtomURLRequestJobFactory* job_factory_; DISALLOW_COPY_AND_ASSIGN(AtomBrowserContext); }; diff --git a/atom/browser/atom_browser_main_parts.cc b/atom/browser/atom_browser_main_parts.cc index aaf68836d0c3..a1a1192b2768 100644 --- a/atom/browser/atom_browser_main_parts.cc +++ b/atom/browser/atom_browser_main_parts.cc @@ -4,28 +4,33 @@ #include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/api/trackable_object.h" #include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_context.h" +#include "atom/browser/bridge_task_runner.h" #include "atom/browser/browser.h" #include "atom/browser/javascript_environment.h" +#include "atom/browser/node_debugger.h" #include "atom/common/api/atom_bindings.h" #include "atom/common/node_bindings.h" +#include "atom/common/node_includes.h" #include "base/command_line.h" +#include "base/thread_task_runner_handle.h" +#include "chrome/browser/browser_process.h" #include "v8/include/v8-debug.h" #if defined(USE_X11) #include "chrome/browser/ui/libgtk2ui/gtk2_util.h" #endif -#include "atom/common/node_includes.h" - namespace atom { // static AtomBrowserMainParts* AtomBrowserMainParts::self_ = NULL; AtomBrowserMainParts::AtomBrowserMainParts() - : browser_(new Browser), + : fake_browser_process_(new BrowserProcess), + browser_(new Browser), node_bindings_(NodeBindings::Create(true)), atom_bindings_(new AtomBindings), gc_timer_(true, true) { @@ -42,8 +47,9 @@ AtomBrowserMainParts* AtomBrowserMainParts::Get() { return self_; } -brightray::BrowserContext* AtomBrowserMainParts::CreateBrowserContext() { - return new AtomBrowserContext(); +void AtomBrowserMainParts::RegisterDestructionCallback( + const base::Closure& callback) { + destruction_callbacks_.push_back(callback); } void AtomBrowserMainParts::PostEarlyInitialization() { @@ -53,15 +59,30 @@ void AtomBrowserMainParts::PostEarlyInitialization() { SetDPIFromGSettings(); #endif - // The ProxyResolverV8 has setup a complete V8 environment, in order to avoid - // conflicts we only initialize our V8 environment after that. - js_env_.reset(new JavascriptEnvironment); + { + // Temporary set the bridge_task_runner_ as current thread's task runner, + // so we can fool gin::PerIsolateData to use it as its task runner, instead + // of getting current message loop's task runner, which is null for now. + bridge_task_runner_ = new BridgeTaskRunner; + base::ThreadTaskRunnerHandle handle(bridge_task_runner_); + + // The ProxyResolverV8 has setup a complete V8 environment, in order to + // avoid conflicts we only initialize our V8 environment after that. + js_env_.reset(new JavascriptEnvironment); + } node_bindings_->Initialize(); + // Support the "--debug" switch. + node_debugger_.reset(new NodeDebugger(js_env_->isolate())); + // Create the global environment. global_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()); + // Add atom-shell extended APIs. atom_bindings_->BindTo(js_env_->isolate(), global_env->process_object()); @@ -95,4 +116,14 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { #endif } +void AtomBrowserMainParts::PostMainMessageLoopRun() { + brightray::BrowserMainParts::PostMainMessageLoopRun(); + + // Make sure destruction callbacks are called before message loop is + // destroyed, otherwise some objects that need to be deleted on IO thread + // won't be freed. + for (const auto& callback : destruction_callbacks_) + callback.Run(); +} + } // namespace atom diff --git a/atom/browser/atom_browser_main_parts.h b/atom/browser/atom_browser_main_parts.h index 6ca0686655ba..bcebc86f16ca 100644 --- a/atom/browser/atom_browser_main_parts.h +++ b/atom/browser/atom_browser_main_parts.h @@ -5,8 +5,15 @@ #ifndef ATOM_BROWSER_ATOM_BROWSER_MAIN_PARTS_H_ #define ATOM_BROWSER_ATOM_BROWSER_MAIN_PARTS_H_ +#include +#include + +#include "base/callback.h" #include "base/timer/timer.h" #include "brightray/browser/browser_main_parts.h" +#include "content/public/browser/browser_context.h" + +class BrowserProcess; namespace atom { @@ -14,6 +21,8 @@ class AtomBindings; class Browser; class JavascriptEnvironment; class NodeBindings; +class NodeDebugger; +class BridgeTaskRunner; class AtomBrowserMainParts : public brightray::BrowserMainParts { public: @@ -22,15 +31,17 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { static AtomBrowserMainParts* Get(); + // Register a callback that should be destroyed before JavaScript environment + // gets destroyed. + void RegisterDestructionCallback(const base::Closure& callback); + Browser* browser() { return browser_.get(); } protected: - // Implementations of brightray::BrowserMainParts. - brightray::BrowserContext* CreateBrowserContext() override; - - // Implementations of content::BrowserMainParts. + // content::BrowserMainParts: void PostEarlyInitialization() override; void PreMainMessageLoopRun() override; + void PostMainMessageLoopRun() override; #if defined(OS_MACOSX) void PreMainMessageLoopStart() override; void PostDestroyThreads() override; @@ -41,13 +52,24 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { void SetDPIFromGSettings(); #endif + // A fake BrowserProcess object that used to feed the source code from chrome. + scoped_ptr fake_browser_process_; + + // The gin::PerIsolateData requires a task runner to create, so we feed it + // with a task runner that will post all work to main loop. + scoped_refptr bridge_task_runner_; + scoped_ptr browser_; scoped_ptr js_env_; scoped_ptr node_bindings_; scoped_ptr atom_bindings_; + scoped_ptr node_debugger_; base::Timer gc_timer_; + // List of callbacks should be executed before destroying JS env. + std::list destruction_callbacks_; + static AtomBrowserMainParts* self_; DISALLOW_COPY_AND_ASSIGN(AtomBrowserMainParts); diff --git a/atom/browser/atom_download_manager_delegate.cc b/atom/browser/atom_download_manager_delegate.cc new file mode 100644 index 000000000000..b573a396332f --- /dev/null +++ b/atom/browser/atom_download_manager_delegate.cc @@ -0,0 +1,151 @@ +// 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 "atom/browser/atom_download_manager_delegate.h" + +#include + +#include "atom/browser/atom_browser_context.h" +#include "atom/browser/native_window.h" +#include "atom/browser/ui/file_dialog.h" +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/prefs/pref_service.h" +#include "chrome/common/pref_names.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/download_manager.h" +#include "net/base/filename_util.h" + +namespace atom { + +AtomDownloadManagerDelegate::AtomDownloadManagerDelegate( + content::DownloadManager* manager) + : download_manager_(manager), + weak_ptr_factory_(this) {} + +AtomDownloadManagerDelegate::~AtomDownloadManagerDelegate() { + if (download_manager_) { + DCHECK_EQ(static_cast(this), + download_manager_->GetDelegate()); + download_manager_->SetDelegate(nullptr); + download_manager_ = nullptr; + } +} + +void AtomDownloadManagerDelegate::CreateDownloadPath( + const GURL& url, + const std::string& content_disposition, + const std::string& suggested_filename, + const std::string& mime_type, + const base::FilePath& default_download_path, + const CreateDownloadPathCallback& callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); + + auto generated_name = net::GenerateFileName(url, + content_disposition, + std::string(), + suggested_filename, + mime_type, + std::string()); + + if (!base::PathExists(default_download_path)) + base::CreateDirectory(default_download_path); + + base::FilePath path(default_download_path.Append(generated_name)); + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(callback, path)); +} + +void AtomDownloadManagerDelegate::OnDownloadPathGenerated( + uint32 download_id, + const content::DownloadTargetCallback& callback, + const base::FilePath& default_path) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + auto item = download_manager_->GetDownload(download_id); + if (!item) + return; + + NativeWindow* window = nullptr; + auto relay = NativeWindowRelay::FromWebContents(item->GetWebContents()); + if (relay) + window = relay->window.get(); + + file_dialog::Filters filters; + base::FilePath path; + if (!file_dialog::ShowSaveDialog(window, item->GetURL().spec(), default_path, + filters, &path)) { + return; + } + + // Remeber the last selected download directory. + AtomBrowserContext* browser_context = static_cast( + download_manager_->GetBrowserContext()); + browser_context->prefs()->SetFilePath(prefs::kDownloadDefaultDirectory, + path.DirName()); + callback.Run(path, + content::DownloadItem::TARGET_DISPOSITION_PROMPT, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, path); +} + +void AtomDownloadManagerDelegate::Shutdown() { + weak_ptr_factory_.InvalidateWeakPtrs(); + download_manager_ = nullptr; +} + +bool AtomDownloadManagerDelegate::DetermineDownloadTarget( + content::DownloadItem* download, + const content::DownloadTargetCallback& callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + AtomBrowserContext* browser_context = static_cast( + download_manager_->GetBrowserContext()); + base::FilePath default_download_path = browser_context->prefs()->GetFilePath( + prefs::kDownloadDefaultDirectory); + // If users didn't set download path, use 'Downloads' directory by default. + if (default_download_path.empty()) { + auto path = download_manager_->GetBrowserContext()->GetPath(); + default_download_path = path.Append(FILE_PATH_LITERAL("Downloads")); + } + + if (!download->GetForcedFilePath().empty()) { + callback.Run(download->GetForcedFilePath(), + content::DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + download->GetForcedFilePath()); + return true; + } + + CreateDownloadPathCallback download_path_callback = + base::Bind(&AtomDownloadManagerDelegate::OnDownloadPathGenerated, + weak_ptr_factory_.GetWeakPtr(), + download->GetId(), callback); + + content::BrowserThread::PostTask( + content::BrowserThread::FILE, FROM_HERE, + base::Bind(&AtomDownloadManagerDelegate::CreateDownloadPath, + weak_ptr_factory_.GetWeakPtr(), + download->GetURL(), + download->GetContentDisposition(), + download->GetSuggestedFilename(), + download->GetMimeType(), + default_download_path, + download_path_callback)); + return true; +} + +bool AtomDownloadManagerDelegate::ShouldOpenDownload( + content::DownloadItem* download, + const content::DownloadOpenDelayedCallback& callback) { + return true; +} + +void AtomDownloadManagerDelegate::GetNextId( + const content::DownloadIdCallback& callback) { + static uint32 next_id = content::DownloadItem::kInvalidId + 1; + callback.Run(next_id++); +} + +} // namespace atom diff --git a/atom/browser/atom_download_manager_delegate.h b/atom/browser/atom_download_manager_delegate.h new file mode 100644 index 000000000000..2df3a7d45a6b --- /dev/null +++ b/atom/browser/atom_download_manager_delegate.h @@ -0,0 +1,57 @@ +// 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_BROWSER_ATOM_DOWNLOAD_MANAGER_DELEGATE_H_ +#define ATOM_BROWSER_ATOM_DOWNLOAD_MANAGER_DELEGATE_H_ + +#include + +#include "base/memory/weak_ptr.h" +#include "content/public/browser/download_manager_delegate.h" + +namespace content { +class DownloadManager; +} + +namespace atom { + +class AtomDownloadManagerDelegate : public content::DownloadManagerDelegate { + public: + using CreateDownloadPathCallback = + base::Callback; + + explicit AtomDownloadManagerDelegate(content::DownloadManager* manager); + virtual ~AtomDownloadManagerDelegate(); + + // Generate default file path to save the download. + void CreateDownloadPath(const GURL& url, + const std::string& suggested_filename, + const std::string& content_disposition, + const std::string& mime_type, + const base::FilePath& path, + const CreateDownloadPathCallback& callback); + void OnDownloadPathGenerated(uint32 download_id, + const content::DownloadTargetCallback& callback, + const base::FilePath& default_path); + + // content::DownloadManagerDelegate: + void Shutdown() override; + bool DetermineDownloadTarget( + content::DownloadItem* download, + const content::DownloadTargetCallback& callback) override; + bool ShouldOpenDownload( + content::DownloadItem* download, + const content::DownloadOpenDelayedCallback& callback) override; + void GetNextId(const content::DownloadIdCallback& callback) override; + + private: + content::DownloadManager* download_manager_; + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(AtomDownloadManagerDelegate); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_DOWNLOAD_MANAGER_DELEGATE_H_ diff --git a/atom/browser/atom_javascript_dialog_manager.h b/atom/browser/atom_javascript_dialog_manager.h index 3712b81a49fc..c0a0dccf0fa9 100644 --- a/atom/browser/atom_javascript_dialog_manager.h +++ b/atom/browser/atom_javascript_dialog_manager.h @@ -30,7 +30,7 @@ class AtomJavaScriptDialogManager : public content::JavaScriptDialogManager { const DialogClosedCallback& callback) override; void CancelActiveAndPendingDialogs( content::WebContents* web_contents) override {} - void WebContentsDestroyed(content::WebContents* web_contents) override {} + void ResetDialogState(content::WebContents* web_contents) override {}; }; } // namespace atom diff --git a/atom/browser/atom_quota_permission_context.cc b/atom/browser/atom_quota_permission_context.cc new file mode 100644 index 000000000000..8775f950ca9a --- /dev/null +++ b/atom/browser/atom_quota_permission_context.cc @@ -0,0 +1,24 @@ +// 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 "atom/browser/atom_quota_permission_context.h" + +#include "storage/common/quota/quota_types.h" + +namespace atom { + +AtomQuotaPermissionContext::AtomQuotaPermissionContext() { +} + +AtomQuotaPermissionContext::~AtomQuotaPermissionContext() { +} + +void AtomQuotaPermissionContext::RequestQuotaPermission( + const content::StorageQuotaParams& params, + int render_process_id, + const PermissionCallback& callback) { + callback.Run(response::QUOTA_PERMISSION_RESPONSE_ALLOW); +} + +} // namespace atom diff --git a/atom/browser/atom_quota_permission_context.h b/atom/browser/atom_quota_permission_context.h new file mode 100644 index 000000000000..1246ea7bad58 --- /dev/null +++ b/atom/browser/atom_quota_permission_context.h @@ -0,0 +1,31 @@ +// 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_BROWSER_ATOM_QUOTA_PERMISSION_CONTEXT_H_ +#define ATOM_BROWSER_ATOM_QUOTA_PERMISSION_CONTEXT_H_ + +#include "content/public/browser/quota_permission_context.h" + +namespace atom { + +class AtomQuotaPermissionContext : public content::QuotaPermissionContext { + public: + typedef content::QuotaPermissionContext::QuotaPermissionResponse response; + + AtomQuotaPermissionContext(); + virtual ~AtomQuotaPermissionContext(); + + // content::QuotaPermissionContext: + void RequestQuotaPermission( + const content::StorageQuotaParams& params, + int render_process_id, + const PermissionCallback& callback) override; + + private: + DISALLOW_COPY_AND_ASSIGN(AtomQuotaPermissionContext); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_QUOTA_PERMISSION_CONTEXT_H_ diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.cc b/atom/browser/atom_resource_dispatcher_host_delegate.cc deleted file mode 100644 index 95335abcc576..000000000000 --- a/atom/browser/atom_resource_dispatcher_host_delegate.cc +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. All rights reserved. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/browser/atom_resource_dispatcher_host_delegate.h" - -#include - -#include "base/logging.h" -#include "content/public/browser/render_frame_host.h" -#include "content/public/browser/resource_request_info.h" -#include "net/http/http_response_headers.h" -#include "net/url_request/url_request.h" - -namespace atom { - -AtomResourceDispatcherHostDelegate::AtomResourceDispatcherHostDelegate() { -} - -void AtomResourceDispatcherHostDelegate::OnResponseStarted( - net::URLRequest* request, - content::ResourceContext* resource_context, - content::ResourceResponse* response, - IPC::Sender* sender) { - // Remove the "X-Frame-Options" from response headers for devtools. - if (request->url().SchemeIs("chrome-devtools")) { - net::HttpResponseHeaders* response_headers = request->response_headers(); - if (response_headers && response_headers->HasHeader("x-frame-options")) - response_headers->RemoveHeader("x-frame-options"); - } -} - -} // namespace atom diff --git a/atom/browser/atom_resource_dispatcher_host_delegate.h b/atom/browser/atom_resource_dispatcher_host_delegate.h deleted file mode 100644 index 12032444bce1..000000000000 --- a/atom/browser/atom_resource_dispatcher_host_delegate.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2014 GitHub, Inc. All rights reserved. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_ -#define ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_ - -#include "base/compiler_specific.h" -#include "content/public/browser/resource_dispatcher_host_delegate.h" - -namespace atom { - -class AtomResourceDispatcherHostDelegate - : public content::ResourceDispatcherHostDelegate { - public: - AtomResourceDispatcherHostDelegate(); - - // content::ResourceDispatcherHostDelegate: - void OnResponseStarted(net::URLRequest* request, - content::ResourceContext* resource_context, - content::ResourceResponse* response, - IPC::Sender* sender) override; - - private: - DISALLOW_COPY_AND_ASSIGN(AtomResourceDispatcherHostDelegate); -}; - -} // namespace atom - -#endif // ATOM_BROWSER_ATOM_RESOURCE_DISPATCHER_HOST_DELEGATE_H_ diff --git a/atom/browser/bridge_task_runner.cc b/atom/browser/bridge_task_runner.cc new file mode 100644 index 000000000000..24572f3990d8 --- /dev/null +++ b/atom/browser/bridge_task_runner.cc @@ -0,0 +1,42 @@ +// 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 "atom/browser/bridge_task_runner.h" + +#include "base/message_loop/message_loop.h" + +namespace atom { + +bool BridgeTaskRunner::PostDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + auto message_loop = base::MessageLoop::current(); + if (!message_loop) + return false; + + return message_loop->task_runner()->PostDelayedTask(from_here, task, delay); +} + +bool BridgeTaskRunner::RunsTasksOnCurrentThread() const { + auto message_loop = base::MessageLoop::current(); + if (!message_loop) + return false; + + return message_loop->task_runner()->RunsTasksOnCurrentThread(); +} + +bool BridgeTaskRunner::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + auto message_loop = base::MessageLoop::current(); + if (!message_loop) + return false; + + return message_loop->task_runner()->PostNonNestableDelayedTask( + from_here, task, delay); +} + +} // namespace atom diff --git a/atom/browser/bridge_task_runner.h b/atom/browser/bridge_task_runner.h new file mode 100644 index 000000000000..fb42aa3852f9 --- /dev/null +++ b/atom/browser/bridge_task_runner.h @@ -0,0 +1,35 @@ +// 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_BROWSER_BRIDGE_TASK_RUNNER_H_ +#define ATOM_BROWSER_BRIDGE_TASK_RUNNER_H_ + +#include "base/single_thread_task_runner.h" + +namespace atom { + +// Post all tasks to the current message loop's task runner if available, +// otherwise fail silently. +class BridgeTaskRunner : public base::SingleThreadTaskRunner { + public: + BridgeTaskRunner() {} + ~BridgeTaskRunner() override {} + + // base::SingleThreadTaskRunner: + bool PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) override; + bool RunsTasksOnCurrentThread() const override; + bool PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) override; + + private: + DISALLOW_COPY_AND_ASSIGN(BridgeTaskRunner); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_BRIDGE_TASK_RUNNER_H_ diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index 123cfc4f9844..9d2a9fc1effb 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -9,6 +9,8 @@ #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/window_list.h" #include "base/message_loop/message_loop.h" +#include "content/public/browser/client_certificate_delegate.h" +#include "net/ssl/ssl_cert_request_info.h" namespace atom { @@ -43,7 +45,8 @@ void Browser::Shutdown() { FOR_EACH_OBSERVER(BrowserObserver, observers_, OnQuit()); is_quiting_ = true; - base::MessageLoop::current()->Quit(); + base::MessageLoop::current()->PostTask( + FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); } std::string Browser::GetVersion() const { @@ -91,8 +94,10 @@ void Browser::OpenURL(const std::string& url) { FOR_EACH_OBSERVER(BrowserObserver, observers_, OnOpenURL(url)); } -void Browser::ActivateWithNoOpenWindows() { - FOR_EACH_OBSERVER(BrowserObserver, observers_, OnActivateWithNoOpenWindows()); +void Browser::Activate(bool has_visible_windows) { + FOR_EACH_OBSERVER(BrowserObserver, + observers_, + OnActivate(has_visible_windows)); } void Browser::WillFinishLaunching() { @@ -104,6 +109,17 @@ void Browser::DidFinishLaunching() { FOR_EACH_OBSERVER(BrowserObserver, observers_, OnFinishLaunching()); } +void Browser::ClientCertificateSelector( + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + scoped_ptr delegate) { + FOR_EACH_OBSERVER(BrowserObserver, + observers_, + OnSelectCertificate(web_contents, + cert_request_info, + delegate.Pass())); +} + void Browser::NotifyAndShutdown() { bool prevent_default = false; FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWillQuit(&prevent_default)); diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 8648d8853384..d135556b8760 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -108,13 +108,20 @@ class Browser : public WindowListObserver { // Tell the application to open a url. void OpenURL(const std::string& url); - // Tell the application that application is activated with no open windows. - void ActivateWithNoOpenWindows(); + // Tell the application that application is activated with visible/invisible + // windows. + void Activate(bool has_visible_windows); // Tell the application the loading has been done. void WillFinishLaunching(); void DidFinishLaunching(); + // Called when client certificate is required. + void ClientCertificateSelector( + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + scoped_ptr delegate); + void AddObserver(BrowserObserver* obs) { observers_.AddObserver(obs); } @@ -147,7 +154,7 @@ class Browser : public WindowListObserver { void OnWindowAllClosed() override; // Observers of the browser. - ObserverList observers_; + base::ObserverList observers_; // Whether "ready" event has been emitted. bool is_ready_; diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index 4f9f436712d4..45e86e620f84 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -7,6 +7,17 @@ #include +#include "base/memory/scoped_ptr.h" +#include "content/public/browser/client_certificate_delegate.h" + +namespace content { +class WebContents; +} + +namespace net { +class SSLCertRequestInfo; +} + namespace atom { class BrowserObserver { @@ -32,14 +43,20 @@ class BrowserObserver { // Browser is used to open a url. virtual void OnOpenURL(const std::string& url) {} - // The browser is activated with no open windows (usually by clicking on the - // dock icon). - virtual void OnActivateWithNoOpenWindows() {} + // The browser is activated with visible/invisible windows (usually by + // clicking on the dock icon). + virtual void OnActivate(bool has_visible_windows) {} // The browser has finished loading. virtual void OnWillFinishLaunching() {} virtual void OnFinishLaunching() {} + // The browser requires client certificate. + virtual void OnSelectCertificate( + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + scoped_ptr delegate) {} + protected: virtual ~BrowserObserver() {} }; diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc new file mode 100644 index 000000000000..3cef7c6d68cc --- /dev/null +++ b/atom/browser/common_web_contents_delegate.cc @@ -0,0 +1,377 @@ +// 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 "atom/browser/common_web_contents_delegate.h" + +#include +#include + +#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 "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 "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 "storage/browser/fileapi/isolated_context.h" + +using content::BrowserThread; + +namespace atom { + +namespace { + +struct FileSystem { + FileSystem() { + } + FileSystem(const std::string& file_system_name, + const std::string& root_url, + const std::string& file_system_path) + : file_system_name(file_system_name), + root_url(root_url), + file_system_path(file_system_path) { + } + + std::string file_system_name; + std::string root_url; + std::string file_system_path; +}; + +std::string RegisterFileSystem(content::WebContents* web_contents, + const base::FilePath& path, + std::string* registered_name) { + auto isolated_context = storage::IsolatedContext::GetInstance(); + std::string file_system_id = isolated_context->RegisterFileSystemForPath( + storage::kFileSystemTypeNativeLocal, + std::string(), + path, + registered_name); + + content::ChildProcessSecurityPolicy* policy = + content::ChildProcessSecurityPolicy::GetInstance(); + content::RenderViewHost* render_view_host = web_contents->GetRenderViewHost(); + int renderer_id = render_view_host->GetProcess()->GetID(); + policy->GrantReadFileSystem(renderer_id, file_system_id); + policy->GrantWriteFileSystem(renderer_id, file_system_id); + policy->GrantCreateFileForFileSystem(renderer_id, file_system_id); + policy->GrantDeleteFromFileSystem(renderer_id, file_system_id); + + if (!policy->CanReadFile(renderer_id, path)) + policy->GrantReadFile(renderer_id, path); + + return file_system_id; +} + +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); + return FileSystem(file_system_name, root_url, file_system_path); +} + +base::DictionaryValue* CreateFileSystemValue(const FileSystem& file_system) { + base::DictionaryValue* file_system_value = new base::DictionaryValue(); + file_system_value->SetString("fileSystemName", file_system.file_system_name); + file_system_value->SetString("rootURL", file_system.root_url); + file_system_value->SetString("fileSystemPath", file_system.file_system_path); + return file_system_value; +} + +void WriteToFile(const base::FilePath& path, + const std::string& content) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + DCHECK(!path.empty()); + + base::WriteFile(path, content.data(), content.size()); +} + +void AppendToFile(const base::FilePath& path, + const std::string& content) { + DCHECK_CURRENTLY_ON(BrowserThread::FILE); + DCHECK(!path.empty()); + + base::AppendToFile(path, content.data(), content.size()); +} + +} // namespace + +CommonWebContentsDelegate::CommonWebContentsDelegate() + : html_fullscreen_(false), + native_fullscreen_(false) { +} + +CommonWebContentsDelegate::~CommonWebContentsDelegate() { +} + +void CommonWebContentsDelegate::InitWithWebContents( + content::WebContents* web_contents) { + web_contents->SetDelegate(this); + + printing::PrintViewManagerBasic::CreateForWebContents(web_contents); + printing::PrintPreviewMessageHandler::CreateForWebContents(web_contents); + + // Create InspectableWebContents. + web_contents_.reset(brightray::InspectableWebContents::Create(web_contents)); + web_contents_->SetDelegate(this); +} + +void CommonWebContentsDelegate::SetOwnerWindow(NativeWindow* owner_window) { + content::WebContents* web_contents = GetWebContents(); + owner_window_ = owner_window->GetWeakPtr(); + NativeWindowRelay* relay = new NativeWindowRelay(owner_window_); + web_contents->SetUserData(relay->key, relay); +} + +void CommonWebContentsDelegate::DestroyWebContents() { + web_contents_.reset(); +} + +content::WebContents* CommonWebContentsDelegate::GetWebContents() const { + if (!web_contents_) + return nullptr; + return web_contents_->GetWebContents(); +} + +content::WebContents* +CommonWebContentsDelegate::GetDevToolsWebContents() const { + if (!web_contents_) + return nullptr; + return web_contents_->GetDevToolsWebContents(); +} + +content::WebContents* CommonWebContentsDelegate::OpenURLFromTab( + content::WebContents* source, + const content::OpenURLParams& params) { + content::NavigationController::LoadURLParams load_url_params(params.url); + load_url_params.referrer = params.referrer; + load_url_params.transition_type = params.transition; + load_url_params.extra_headers = params.extra_headers; + 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; +} + +content::JavaScriptDialogManager* +CommonWebContentsDelegate::GetJavaScriptDialogManager( + content::WebContents* source) { + if (!dialog_manager_) + dialog_manager_.reset(new AtomJavaScriptDialogManager); + + return dialog_manager_.get(); +} + +content::ColorChooser* CommonWebContentsDelegate::OpenColorChooser( + content::WebContents* web_contents, + SkColor color, + const std::vector& suggestions) { + return chrome::ShowColorChooser(web_contents, color); +} + +void CommonWebContentsDelegate::RunFileChooser( + content::WebContents* guest, + const content::FileChooserParams& params) { + if (!web_dialog_helper_) + web_dialog_helper_.reset(new WebDialogHelper(owner_window())); + web_dialog_helper_->RunFileChooser(guest, params); +} + +void CommonWebContentsDelegate::EnumerateDirectory(content::WebContents* guest, + int request_id, + const base::FilePath& path) { + if (!web_dialog_helper_) + web_dialog_helper_.reset(new WebDialogHelper(owner_window())); + web_dialog_helper_->EnumerateDirectory(guest, request_id, path); +} + +void CommonWebContentsDelegate::EnterFullscreenModeForTab( + content::WebContents* source, const GURL& origin) { + if (!owner_window_) + return; + SetHtmlApiFullscreen(true); + owner_window_->NotifyWindowEnterHtmlFullScreen(); + source->GetRenderViewHost()->WasResized(); +} + +void CommonWebContentsDelegate::ExitFullscreenModeForTab( + content::WebContents* source) { + if (!owner_window_) + return; + SetHtmlApiFullscreen(false); + owner_window_->NotifyWindowLeaveHtmlFullScreen(); + source->GetRenderViewHost()->WasResized(); +} + +bool CommonWebContentsDelegate::IsFullscreenForTabOrPending( + const content::WebContents* source) const { + return html_fullscreen_; +} + +void CommonWebContentsDelegate::DevToolsSaveToFile( + const std::string& url, const std::string& content, bool save_as) { + base::FilePath path; + PathsMap::iterator it = saved_files_.find(url); + if (it != saved_files_.end() && !save_as) { + path = it->second; + } else { + file_dialog::Filters filters; + base::FilePath default_path(base::FilePath::FromUTF8Unsafe(url)); + if (!file_dialog::ShowSaveDialog(owner_window(), url, default_path, + filters, &path)) { + base::StringValue url_value(url); + web_contents_->CallClientFunction( + "DevToolsAPI.canceledSaveURL", &url_value, nullptr, nullptr); + return; + } + } + + saved_files_[url] = path; + BrowserThread::PostTaskAndReply( + BrowserThread::FILE, FROM_HERE, + base::Bind(&WriteToFile, path, content), + base::Bind(&CommonWebContentsDelegate::OnDevToolsSaveToFile, + base::Unretained(this), url)); +} + +void CommonWebContentsDelegate::DevToolsAppendToFile( + const std::string& url, const std::string& content) { + PathsMap::iterator it = saved_files_.find(url); + if (it == saved_files_.end()) + return; + + BrowserThread::PostTaskAndReply( + BrowserThread::FILE, FROM_HERE, + base::Bind(&AppendToFile, it->second, content), + base::Bind(&CommonWebContentsDelegate::OnDevToolsAppendToFile, + base::Unretained(this), url)); +} + +void CommonWebContentsDelegate::DevToolsAddFileSystem( + const base::FilePath& file_system_path) { + base::FilePath path = file_system_path; + if (path.empty()) { + file_dialog::Filters filters; + base::FilePath default_path; + std::vector paths; + int flag = file_dialog::FILE_DIALOG_OPEN_DIRECTORY; + if (!file_dialog::ShowOpenDialog(owner_window(), "", default_path, + filters, flag, &paths)) + return; + + 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()) + return; + + saved_paths_[file_system_id] = path; + + FileSystem file_system = CreateFileSystemStruct(GetDevToolsWebContents(), + file_system_id, + registered_name, + path.AsUTF8Unsafe()); + + 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); +} + +void CommonWebContentsDelegate::DevToolsRemoveFileSystem( + const base::FilePath& file_system_path) { + if (!web_contents_) + return; + + 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; + } + + base::StringValue file_system_path_value(file_system_path.AsUTF8Unsafe()); + web_contents_->CallClientFunction( + "DevToolsAPI.fileSystemRemoved", + &file_system_path_value, + nullptr, + nullptr); +} + +void CommonWebContentsDelegate::OnDevToolsSaveToFile( + const std::string& url) { + // Notify DevTools. + base::StringValue url_value(url); + web_contents_->CallClientFunction( + "DevToolsAPI.savedURL", &url_value, nullptr, nullptr); +} + +void CommonWebContentsDelegate::OnDevToolsAppendToFile( + const std::string& url) { + // Notify DevTools. + base::StringValue url_value(url); + web_contents_->CallClientFunction( + "DevToolsAPI.appendedToURL", &url_value, nullptr, nullptr); +} + +void CommonWebContentsDelegate::SetHtmlApiFullscreen(bool enter_fullscreen) { + // Window is already in fullscreen mode, save the state. + if (enter_fullscreen && owner_window_->IsFullscreen()) { + native_fullscreen_ = true; + html_fullscreen_ = true; + return; + } + + // Exit html fullscreen state but not window's fullscreen mode. + if (!enter_fullscreen && native_fullscreen_) { + html_fullscreen_ = false; + return; + } + + owner_window_->SetFullScreen(enter_fullscreen); + html_fullscreen_ = enter_fullscreen; + native_fullscreen_ = false; +} + +} // namespace atom diff --git a/atom/browser/common_web_contents_delegate.h b/atom/browser/common_web_contents_delegate.h new file mode 100644 index 000000000000..495b5501a0d1 --- /dev/null +++ b/atom/browser/common_web_contents_delegate.h @@ -0,0 +1,131 @@ +// 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_BROWSER_COMMON_WEB_CONTENTS_DELEGATE_H_ +#define ATOM_BROWSER_COMMON_WEB_CONTENTS_DELEGATE_H_ + +#include +#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" + +namespace atom { + +class AtomJavaScriptDialogManager; +class NativeWindow; +class WebDialogHelper; + +class CommonWebContentsDelegate + : public brightray::DefaultWebContentsDelegate, + public brightray::InspectableWebContentsDelegate { + public: + CommonWebContentsDelegate(); + virtual ~CommonWebContentsDelegate(); + + // Creates a InspectableWebContents object and takes onwership of + // |web_contents|. + void InitWithWebContents(content::WebContents* web_contents); + + // Set the window as owner window. + void SetOwnerWindow(NativeWindow* owner_window); + + // Destroy the managed InspectableWebContents object. + void DestroyWebContents(); + + // Returns the WebContents managed by this delegate. + content::WebContents* GetWebContents() const; + + // Returns the WebContents of devtools. + content::WebContents* GetDevToolsWebContents() const; + + brightray::InspectableWebContents* managed_web_contents() const { + return web_contents_.get(); + } + + NativeWindow* owner_window() const { return owner_window_.get(); } + + bool is_html_fullscreen() const { return html_fullscreen_; } + + protected: + // content::WebContentsDelegate: + 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; + content::ColorChooser* OpenColorChooser( + content::WebContents* web_contents, + SkColor color, + const std::vector& suggestions) override; + void RunFileChooser(content::WebContents* web_contents, + const content::FileChooserParams& params) override; + void EnumerateDirectory(content::WebContents* web_contents, + int request_id, + const base::FilePath& path) override; + void EnterFullscreenModeForTab(content::WebContents* source, + const GURL& origin) override; + void ExitFullscreenModeForTab(content::WebContents* source) override; + bool IsFullscreenForTabOrPending( + const content::WebContents* source) const override; + + // brightray::InspectableWebContentsDelegate: + void DevToolsSaveToFile(const std::string& url, + const std::string& content, + bool save_as) override; + void DevToolsAppendToFile(const std::string& url, + const std::string& content) override; + void DevToolsAddFileSystem(const base::FilePath& path) override; + void DevToolsRemoveFileSystem( + const base::FilePath& file_system_path) override; + + private: + // Callback for when DevToolsSaveToFile has completed. + void OnDevToolsSaveToFile(const std::string& url); + + // Callback for when DevToolsAppendToFile has completed. + void OnDevToolsAppendToFile(const std::string& url); + + // Set fullscreen mode triggered by html api. + void SetHtmlApiFullscreen(bool enter_fullscreen); + + // The window that this WebContents belongs to. + base::WeakPtr owner_window_; + + // Whether window is fullscreened by HTML5 api. + bool html_fullscreen_; + + // Whether window is fullscreened by window api. + bool native_fullscreen_; + + scoped_ptr web_dialog_helper_; + scoped_ptr dialog_manager_; + + // The stored InspectableWebContents object. + // Notice that web_contents_ must be placed after dialog_manager_, so we can + // make sure web_contents_ is destroyed before dialog_manager_, otherwise a + // crash would happen. + scoped_ptr web_contents_; + + // Maps url to file path, used by the file requests sent from devtools. + 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); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_COMMON_WEB_CONTENTS_DELEGATE_H_ diff --git a/atom/browser/default_app/default_app.js b/atom/browser/default_app/default_app.js index e471b23ae621..2378902b44ec 100644 --- a/atom/browser/default_app/default_app.js +++ b/atom/browser/default_app/default_app.js @@ -1,10 +1,7 @@ var app = require('app'); -var Menu = require('menu'); -var MenuItem = require('menu-item'); var BrowserWindow = require('browser-window'); var mainWindow = null; -var menu = null; // Quit when all windows are closed. app.on('window-all-closed', function() { @@ -15,221 +12,9 @@ app.on('ready', function() { mainWindow = new BrowserWindow({ width: 800, height: 600, - resizable: false, 'auto-hide-menu-bar': true, 'use-content-size': true, }); mainWindow.loadUrl('file://' + __dirname + '/index.html'); mainWindow.focus(); - - if (process.platform == 'darwin') { - var template = [ - { - label: 'Electron', - submenu: [ - { - label: 'About Electron', - selector: 'orderFrontStandardAboutPanel:' - }, - { - type: 'separator' - }, - { - label: 'Services', - submenu: [] - }, - { - type: 'separator' - }, - { - label: 'Hide Electron', - accelerator: 'Command+H', - selector: 'hide:' - }, - { - label: 'Hide Others', - accelerator: 'Command+Shift+H', - selector: 'hideOtherApplications:' - }, - { - label: 'Show All', - selector: 'unhideAllApplications:' - }, - { - type: 'separator' - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: function() { app.quit(); } - }, - ] - }, - { - label: 'Edit', - submenu: [ - { - label: 'Undo', - accelerator: 'Command+Z', - selector: 'undo:' - }, - { - label: 'Redo', - accelerator: 'Shift+Command+Z', - selector: 'redo:' - }, - { - type: 'separator' - }, - { - label: 'Cut', - accelerator: 'Command+X', - selector: 'cut:' - }, - { - label: 'Copy', - accelerator: 'Command+C', - selector: 'copy:' - }, - { - label: 'Paste', - accelerator: 'Command+V', - selector: 'paste:' - }, - { - label: 'Select All', - accelerator: 'Command+A', - selector: 'selectAll:' - }, - ] - }, - { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'Command+R', - click: function() { mainWindow.restart(); } - }, - { - label: 'Toggle Full Screen', - accelerator: 'Ctrl+Command+F', - click: function() { mainWindow.setFullScreen(!mainWindow.isFullScreen()); } - }, - { - label: 'Toggle Developer Tools', - accelerator: 'Alt+Command+I', - click: function() { mainWindow.toggleDevTools(); } - }, - ] - }, - { - label: 'Window', - submenu: [ - { - label: 'Minimize', - accelerator: 'Command+M', - selector: 'performMiniaturize:' - }, - { - label: 'Close', - accelerator: 'Command+W', - selector: 'performClose:' - }, - { - type: 'separator' - }, - { - label: 'Bring All to Front', - selector: 'arrangeInFront:' - }, - ] - }, - { - label: 'Help', - submenu: [ - { - label: 'Learn More', - click: function() { require('shell').openExternal('http://electron.atom.io') } - }, - { - label: 'Documentation', - click: function() { require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme') } - }, - { - label: 'Community Discussions', - click: function() { require('shell').openExternal('https://discuss.atom.io/c/electron') } - }, - { - label: 'Search Issues', - click: function() { require('shell').openExternal('https://github.com/atom/electron/issues') } - } - ] - } - ]; - - menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); - } else { - var template = [ - { - label: '&File', - submenu: [ - { - label: '&Open', - accelerator: 'Ctrl+O', - }, - { - label: '&Close', - accelerator: 'Ctrl+W', - click: function() { mainWindow.close(); } - }, - ] - }, - { - label: '&View', - submenu: [ - { - label: '&Reload', - accelerator: 'Ctrl+R', - click: function() { mainWindow.restart(); } - }, - { - label: 'Toggle &Full Screen', - accelerator: 'F11', - click: function() { mainWindow.setFullScreen(!mainWindow.isFullScreen()); } - }, - { - label: 'Toggle &Developer Tools', - accelerator: 'Alt+Ctrl+I', - click: function() { mainWindow.toggleDevTools(); } - }, - ] - }, - { - label: 'Help', - submenu: [ - { - label: 'Learn More', - click: function() { require('shell').openExternal('http://electron.atom.io') } - }, - { - label: 'Documentation', - click: function() { require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme') } - }, - { - label: 'Community Discussions', - click: function() { require('shell').openExternal('https://discuss.atom.io/c/electron') } - }, - { - label: 'Search Issues', - click: function() { require('shell').openExternal('https://github.com/atom/electron/issues') } - } - ] - } - ]; - - menu = Menu.buildFromTemplate(template); - mainWindow.setMenu(menu); - } }); diff --git a/atom/browser/default_app/main.js b/atom/browser/default_app/main.js index 5664fac4cd21..4d854dc35cbc 100644 --- a/atom/browser/default_app/main.js +++ b/atom/browser/default_app/main.js @@ -2,6 +2,8 @@ var app = require('app'); var dialog = require('dialog'); var fs = require('fs'); var path = require('path'); +var Menu = require('menu'); +var BrowserWindow = require('browser-window'); // Quit when all windows are closed and no other one is listening to this. app.on('window-all-closed', function() { @@ -29,6 +31,187 @@ for (var i in argv) { } } +// 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() { require('shell').openExternal('http://electron.atom.io') } + }, + { + label: 'Documentation', + click: function() { require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme') } + }, + { + label: 'Community Discussions', + click: function() { require('shell').openExternal('https://discuss.atom.io/c/electron') } + }, + { + label: 'Search Issues', + click: function() { require('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); +}); + // Start the specified app if there is one specified in command line, otherwise // start the default app. if (option.file && !option.webdriver) { @@ -46,6 +229,7 @@ if (option.file && !option.webdriver) { 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. @@ -53,7 +237,7 @@ if (option.file && !option.webdriver) { } 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:\nhttps://github.com/atom/electron/tree/master/docs'); + dialog.showErrorBox('Error opening app', 'The app provided is not a valid electron app, please read the docs on how to write one:\nhttps://github.com/atom/electron/tree/master/docs\n\n' + e.toString()); process.exit(1); } else { console.error('App threw an error when running', e); diff --git a/atom/browser/javascript_environment.cc b/atom/browser/javascript_environment.cc index f8dd71f471eb..3788fcad8b68 100644 --- a/atom/browser/javascript_environment.cc +++ b/atom/browser/javascript_environment.cc @@ -4,10 +4,14 @@ #include "atom/browser/javascript_environment.h" +#include "gin/array_buffer.h" +#include "gin/v8_initializer.h" + namespace atom { JavascriptEnvironment::JavascriptEnvironment() - : isolate_(isolate_holder_.isolate()), + : initialized_(Initialize()), + isolate_(isolate_holder_.isolate()), isolate_scope_(isolate_), locker_(isolate_), handle_scope_(isolate_), @@ -15,4 +19,11 @@ JavascriptEnvironment::JavascriptEnvironment() context_scope_(v8::Local::New(isolate_, context_)) { } +bool JavascriptEnvironment::Initialize() { + gin::V8Initializer::LoadV8Snapshot(); + gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode, + gin::ArrayBufferAllocator::SharedInstance()); + return true; +} + } // namespace atom diff --git a/atom/browser/javascript_environment.h b/atom/browser/javascript_environment.h index 8baec7e36815..20f1667c3b8e 100644 --- a/atom/browser/javascript_environment.h +++ b/atom/browser/javascript_environment.h @@ -20,6 +20,9 @@ class JavascriptEnvironment { } private: + bool Initialize(); + + bool initialized_; gin::IsolateHolder isolate_holder_; v8::Isolate* isolate_; v8::Isolate::Scope isolate_scope_; diff --git a/atom/browser/lib/chrome-extension.coffee b/atom/browser/lib/chrome-extension.coffee index 884a9e25d6eb..15f7bfd54c56 100644 --- a/atom/browser/lib/chrome-extension.coffee +++ b/atom/browser/lib/chrome-extension.coffee @@ -32,6 +32,7 @@ getExtensionInfoFromPath = (srcDirectory) -> startPage: page name: manifest.name srcDirectory: srcDirectory + exposeExperimentalAPIs: true extensionInfoMap[manifest.name] # The loaded extensions cache and its persistent path. @@ -64,14 +65,16 @@ app.once 'ready', -> catch e # The chrome-extension: can map a extension URL request to real file path. - protocol.registerProtocol 'chrome-extension', (request) -> + chromeExtensionHandler = (request, callback) -> parsed = url.parse request.url - return unless parsed.hostname and parsed.path? - return unless /extension-\d+/.test parsed.hostname + return callback() unless parsed.hostname and parsed.path? + return callback() unless /extension-\d+/.test parsed.hostname directory = getPathForHost parsed.hostname - return unless directory? - return new protocol.RequestFileJob(path.join(directory, parsed.path)) + return callback() unless directory? + callback path.join(directory, parsed.path) + protocol.registerFileProtocol 'chrome-extension', chromeExtensionHandler, (error) -> + console.error 'Unable to register chrome-extension protocol' if error BrowserWindow::_loadDevToolsExtensions = (extensionInfoArray) -> @devToolsWebContents?.executeJavaScript "DevToolsAPI.addExtensions(#{JSON.stringify(extensionInfoArray)});" diff --git a/atom/browser/lib/guest-view-manager.coffee b/atom/browser/lib/guest-view-manager.coffee index 43f4d7c98c5f..1c05b65b2dc0 100644 --- a/atom/browser/lib/guest-view-manager.coffee +++ b/atom/browser/lib/guest-view-manager.coffee @@ -3,6 +3,7 @@ webContents = require 'web-contents' webViewManager = null # Doesn't exist in early initialization. supportedWebViewEvents = [ + 'load-commit' 'did-finish-load' 'did-fail-load' 'did-frame-finish-load' @@ -15,9 +16,13 @@ supportedWebViewEvents = [ 'new-window' 'close' 'crashed' + 'gpu-crashed' + 'plugin-crashed' 'destroyed' 'page-title-set' 'page-favicon-updated' + 'enter-html-full-screen' + 'leave-html-full-screen' ] nextInstanceId = 0 @@ -25,6 +30,10 @@ guestInstances = {} embedderElementsMap = {} reverseEmbedderElementsMap = {} +# Moves the last element of array to the first one. +moveLastToFirst = (list) -> + list.unshift list.pop() + # Generate guestInstanceId. getNextInstanceId = (webContents) -> ++nextInstanceId @@ -34,17 +43,20 @@ createGuest = (embedder, params) -> webViewManager ?= process.atomBinding 'web_view_manager' id = getNextInstanceId embedder - guest = webContents.create - isGuest: true - guestInstanceId: id - storagePartitionId: params.storagePartitionId + guest = webContents.create {isGuest: true, partition: params.partition, embedder} guestInstances[id] = {guest, embedder} # Destroy guest when the embedder is gone or navigated. destroyEvents = ['destroyed', 'crashed', 'did-navigate-to-different-page'] destroy = -> destroyGuest embedder, id if guestInstances[id]? - embedder.once event, destroy for event in destroyEvents + for event in destroyEvents + 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] + moveLastToFirst listeners if Array.isArray listeners guest.once 'destroyed', -> embedder.removeListener event, destroy for event in destroyEvents @@ -54,14 +66,21 @@ createGuest = (embedder, params) -> delete @attachParams @viewInstanceId = params.instanceId - min = width: params.minwidth, height: params.minheight - max = width: params.maxwidth, height: params.maxheight - @setAutoSize params.autosize, min, max + @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 - if params.httpreferrer - @loadUrl params.src, {httpreferrer: params.httpreferrer} - else - @loadUrl params.src + opts = {} + opts.httpReferrer = params.httpreferrer if params.httpreferrer + opts.userAgent = params.useragent if params.useragent + @loadUrl params.src, opts + if params.allowtransparency? @setAllowTransparency params.allowtransparency @@ -96,11 +115,13 @@ attachGuest = (embedder, elementInstanceId, guestInstanceId, params) -> return unless guestInstances[oldGuestInstanceId]? destroyGuest embedder, oldGuestInstanceId - webViewManager.addGuest guestInstanceId, elementInstanceId, embedder, guest, - nodeIntegration: params.nodeintegration - plugins: params.plugins - disableWebSecurity: params.disablewebsecurity - preloadUrl: params.preload ? '' + webPreferences = + 'guest-instance-id': guestInstanceId + 'node-integration': params.nodeintegration ? false + 'plugins': params.plugins + 'web-security': !params.disablewebsecurity + webPreferences['preload-url'] = params.preload if params.preload + webViewManager.addGuest guestInstanceId, elementInstanceId, embedder, guest, webPreferences guest.attachParams = params embedderElementsMap[key] = guestInstanceId @@ -117,7 +138,7 @@ destroyGuest = (embedder, id) -> delete reverseEmbedderElementsMap[id] delete embedderElementsMap[key] -ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', (event, type, params, requestId) -> +ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', (event, params, requestId) -> event.sender.send "ATOM_SHELL_RESPONSE_#{requestId}", createGuest(event.sender, params) ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', (event, elementInstanceId, guestInstanceId, params) -> @@ -126,8 +147,8 @@ ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_ATTACH_GUEST', (event, elementInstanceId, ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', (event, id) -> destroyGuest event.sender, id -ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_AUTO_SIZE', (event, id, params) -> - guestInstances[id]?.guest.setAutoSize params.enableAutoSize, params.min, params.max +ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', (event, id, params) -> + guestInstances[id]?.guest.setSize params ipc.on 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', (event, id, allowtransparency) -> guestInstances[id]?.guest.setAllowTransparency allowtransparency diff --git a/atom/browser/lib/guest-window-manager.coffee b/atom/browser/lib/guest-window-manager.coffee index 14b0cafe8f53..861b0d10aa31 100644 --- a/atom/browser/lib/guest-window-manager.coffee +++ b/atom/browser/lib/guest-window-manager.coffee @@ -21,9 +21,8 @@ createGuest = (embedder, url, frameName, options) -> # guest is closed by user then we should prevent |embedder| from double # closing guest. closedByEmbedder = -> - embedder.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', guest.id guest.removeListener 'closed', closedByUser - guest.destroy() unless guest.isClosed() + guest.destroy() closedByUser = -> embedder.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', guest.id embedder.removeListener 'render-view-deleted', closedByEmbedder @@ -41,31 +40,36 @@ createGuest = (embedder, url, frameName, options) -> # Routed window.open messages. ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, args...) -> [url, frameName, options] = args - event.sender.emit '-new-window', event, url, frameName, 7 + event.sender.emit 'new-window', event, url, frameName, 'new-window' if event.sender.isGuest() or event.defaultPrevented event.returnValue = null else event.returnValue = createGuest event.sender, args... ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', (event, guestId) -> - return unless BrowserWindow.windows.has guestId - BrowserWindow.windows.get(guestId).destroy() + BrowserWindow.fromId(guestId)?.destroy() ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestId, method, args...) -> - return unless BrowserWindow.windows.has guestId - BrowserWindow.windows.get(guestId)[method] args... + BrowserWindow.fromId(guestId)?[method] args... ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestId, message, targetOrigin) -> - return unless BrowserWindow.windows.has guestId - guestContents = BrowserWindow.windows.get(guestId).webContents - if guestContents.getUrl().indexOf(targetOrigin) is 0 or targetOrigin is '*' + guestContents = BrowserWindow.fromId(guestId)?.webContents + if guestContents?.getUrl().indexOf(targetOrigin) is 0 or targetOrigin is '*' guestContents.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', message, targetOrigin -ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, message, targetOrigin) -> +ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', (event, guestId, message, targetOrigin, sourceOrigin) -> embedder = v8Util.getHiddenValue event.sender, 'embedder' if embedder?.getUrl().indexOf(targetOrigin) is 0 or targetOrigin is '*' - embedder.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', message, targetOrigin + embedder.send 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', guestId, message, sourceOrigin ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) -> - return unless BrowserWindow.windows.has guestId - BrowserWindow.windows.get(guestId).webContents?[method] args... + BrowserWindow.fromId(guestId)?.webContents?[method] args... + +ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_GUEST_ID', (event) -> + embedder = v8Util.getHiddenValue event.sender, 'embedder' + if embedder? + guest = BrowserWindow.fromWebContents event.sender + if guest? + event.returnValue = guest.id + return + event.returnValue = null diff --git a/atom/browser/lib/init.coffee b/atom/browser/lib/init.coffee index cbee13655416..1299364d2fa6 100644 --- a/atom/browser/lib/init.coffee +++ b/atom/browser/lib/init.coffee @@ -38,7 +38,7 @@ process.on 'uncaughtException', (error) -> # Show error in GUI. stack = error.stack ? "#{error.name}: #{error.message}" message = "Uncaught Exception:\n#{stack}" - require('dialog').showErrorBox 'A JavaScript error occured in the browser process', message + require('dialog').showErrorBox 'A JavaScript error occurred in the main process', message # Emit 'exit' event on quit. app = require 'app' @@ -87,9 +87,13 @@ 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' +# Set main startup script of the app. +mainStartupScript = packageJson.main or 'index.js' + # Finally load app's main.js and transfer control to C++. -Module._load path.join(packagePath, packageJson.main), Module, true +Module._load path.join(packagePath, mainStartupScript), Module, true diff --git a/atom/browser/lib/objects-registry.coffee b/atom/browser/lib/objects-registry.coffee index e159730330e2..ccfe2dbe0ad2 100644 --- a/atom/browser/lib/objects-registry.coffee +++ b/atom/browser/lib/objects-registry.coffee @@ -1,82 +1,65 @@ EventEmitter = require('events').EventEmitter -IDWeakMap = require 'id-weak-map' v8Util = process.atomBinding 'v8_util' -# Class to reference all objects. -class ObjectsStore - @stores = {} - - constructor: -> - @nextId = 0 - @objects = [] - - getNextId: -> - ++@nextId - - add: (obj) -> - id = @getNextId() - @objects[id] = obj - id - - has: (id) -> - @objects[id]? - - remove: (id) -> - throw new Error("Invalid key #{id} for ObjectsStore") unless @has id - delete @objects[id] - - get: (id) -> - throw new Error("Invalid key #{id} for ObjectsStore") unless @has id - @objects[id] - - @forRenderView: (key) -> - @stores[key] = new ObjectsStore unless @stores[key]? - @stores[key] - - @releaseForRenderView: (key) -> - delete @stores[key] - class ObjectsRegistry extends EventEmitter constructor: -> @setMaxListeners Number.MAX_VALUE + @nextId = 0 - # Objects in weak map will be not referenced (so we won't leak memory), and - # every object created in browser will have a unique id in weak map. - @objectsWeakMap = new IDWeakMap - @objectsWeakMap.add = (obj) -> - id = IDWeakMap::add.call this, obj - v8Util.setHiddenValue obj, 'atomId', id - id + # Stores all objects by ref-counting. + # (id) => {object, count} + @storage = {} + + # Stores the IDs of objects referenced by WebContents. + # (webContentsId) => {(id) => (count)} + @owners = {} # Register a new object, the object would be kept referenced until you release # it explicitly. - add: (key, obj) -> - # Some native objects may already been added to objectsWeakMap, be care not - # to add it twice. - @objectsWeakMap.add obj unless v8Util.getHiddenValue obj, 'atomId' - id = v8Util.getHiddenValue obj, 'atomId' + add: (webContentsId, obj) -> + id = @saveToStorage obj + # Remember the owner. + @owners[webContentsId] ?= {} + @owners[webContentsId][id] ?= 0 + @owners[webContentsId][id]++ + # Returns object's id + id - # Store and reference the object, then return the storeId which points to - # where the object is stored. The caller can later dereference the object - # with the storeId. - # We use a difference key because the same object can be referenced for - # multiple times by the same renderer view. - store = ObjectsStore.forRenderView key - storeId = store.add obj - - [id, storeId] - - # Get an object according to its id. + # Get an object according to its ID. get: (id) -> - @objectsWeakMap.get id + @storage[id]?.object - # Remove an object according to its storeId. - remove: (key, storeId) -> - ObjectsStore.forRenderView(key).remove storeId + # Dereference an object according to its ID. + remove: (webContentsId, id) -> + @dereference id, 1 + # Also reduce the count in owner. + pointer = @owners[webContentsId] + --pointer[id] + delete pointer[id] if pointer[id] is 0 - # Clear all references to objects from renderer view. - clear: (key) -> - @emit "clear-#{key}" - ObjectsStore.releaseForRenderView key + # Clear all references to objects refrenced by the WebContents. + clear: (webContentsId) -> + @emit "clear-#{webContentsId}" + return unless @owners[webContentsId]? + @dereference id, count for id, count of @owners[webContentsId] + delete @owners[webContentsId] + + # Private: Saves the object into storage and assigns an ID for it. + saveToStorage: (object) -> + id = v8Util.getHiddenValue object, 'atomId' + unless id + id = ++@nextId + @storage[id] = {count: 0, object} + v8Util.setHiddenValue object, 'atomId', id + ++@storage[id].count + id + + # Private: Dereference the object from store. + dereference: (id, count) -> + pointer = @storage[id] + pointer.count -= count + if pointer.count is 0 + v8Util.deleteHiddenValue pointer.object, 'atomId' + delete @storage[id] module.exports = new ObjectsRegistry diff --git a/atom/browser/lib/rpc-server.coffee b/atom/browser/lib/rpc-server.coffee index 48d00e9bd23b..0a28d350e8a9 100644 --- a/atom/browser/lib/rpc-server.coffee +++ b/atom/browser/lib/rpc-server.coffee @@ -4,11 +4,17 @@ objectsRegistry = require './objects-registry.js' v8Util = process.atomBinding 'v8_util' # Convert a real value into meta data. -valueToMeta = (sender, value) -> +valueToMeta = (sender, value, optimizeSimpleObject=false) -> meta = type: typeof value + meta.type = 'buffer' if Buffer.isBuffer value meta.type = 'value' if value is null meta.type = 'array' if Array.isArray value + meta.type = 'promise' if value? and value.constructor.name is 'Promise' + + # Treat simple objects as value. + if optimizeSimpleObject and meta.type is 'object' and v8Util.getHiddenValue value, 'simple' + meta.type = 'value' # Treat the arguments object as array. meta.type = 'array' if meta.type is 'object' and value.callee? and value.length? @@ -22,10 +28,14 @@ valueToMeta = (sender, value) -> # 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, meta.storeId] = objectsRegistry.add sender.getId(), value + meta.id = objectsRegistry.add sender.getId(), value meta.members = [] meta.members.push {name: prop, type: typeof field} for prop, field of value + else if meta.type is 'buffer' + meta.value = Array::slice.call value, 0 + else if meta.type is 'promise' + meta.then = valueToMeta(sender, value.then.bind(value)) else meta.type = 'value' meta.value = value @@ -43,6 +53,8 @@ unwrapArgs = (sender, args) -> when 'value' then meta.value when 'remote-object' then objectsRegistry.get meta.id when 'array' then unwrapArgs sender, meta.value + when 'buffer' then new Buffer(meta.value) + when 'promise' then Promise.resolve(then: metaToValue(meta.then)) when 'object' ret = v8Util.createObjectWithName meta.name for member in meta.members @@ -72,11 +84,11 @@ unwrapArgs = (sender, args) -> callFunction = (event, func, caller, args) -> if v8Util.getHiddenValue(func, 'asynchronous') and typeof args[args.length - 1] isnt 'function' args.push (ret) -> - event.returnValue = valueToMeta event.sender, ret + event.returnValue = valueToMeta event.sender, ret, true func.apply caller, args else ret = func.apply caller, args - event.returnValue = valueToMeta event.sender, ret + event.returnValue = valueToMeta event.sender, ret, true # Send by BrowserWindow when its render view is deleted. process.on 'ATOM_BROWSER_RELEASE_RENDER_VIEW', (id) -> @@ -162,8 +174,8 @@ ipc.on 'ATOM_BROWSER_MEMBER_GET', (event, id, name) -> catch e event.returnValue = errorToMeta e -ipc.on 'ATOM_BROWSER_DEREFERENCE', (event, storeId) -> - objectsRegistry.remove event.sender.getId(), storeId +ipc.on 'ATOM_BROWSER_DEREFERENCE', (event, id) -> + objectsRegistry.remove event.sender.getId(), id ipc.on 'ATOM_BROWSER_GUEST_WEB_CONTENTS', (event, guestInstanceId) -> try diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index 16dcf6fd9523..a18d2fe40fcd 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -52,12 +52,8 @@ - (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication hasVisibleWindows:(BOOL)flag { atom::Browser* browser = atom::Browser::Get(); - if (flag) { - return YES; - } else { - browser->ActivateWithNoOpenWindows(); - return NO; - } + browser->Activate(static_cast(flag)); + return flag; } @end diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 5bef1acfb872..4d5f273340ab 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -9,45 +9,26 @@ #include #include "atom/browser/atom_browser_context.h" -#include "atom/browser/atom_javascript_dialog_manager.h" -#include "atom/browser/browser.h" -#include "atom/browser/ui/file_dialog.h" -#include "atom/browser/web_dialog_helper.h" +#include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/window_list.h" #include "atom/common/api/api_messages.h" -#include "atom/common/atom_version.h" -#include "atom/common/chrome_version.h" #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/options_switches.h" -#include "base/command_line.h" #include "base/files/file_util.h" #include "base/json/json_writer.h" #include "base/prefs/pref_service.h" #include "base/message_loop/message_loop.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "brightray/browser/inspectable_web_contents.h" #include "brightray/browser/inspectable_web_contents_view.h" -#include "chrome/browser/printing/print_view_manager_basic.h" -#include "chrome/browser/ui/browser_dialogs.h" #include "content/browser/renderer_host/render_widget_host_impl.h" -#include "content/public/browser/devtools_agent_host.h" -#include "content/public/browser/invalidate_type.h" #include "content/public/browser/navigation_entry.h" -#include "content/public/browser/notification_details.h" -#include "content/public/browser/notification_source.h" -#include "content/public/browser/notification_types.h" #include "content/public/browser/plugin_service.h" -#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/common/content_switches.h" -#include "content/public/common/renderer_preferences.h" -#include "content/public/common/user_agent.h" -#include "content/public/common/web_preferences.h" #include "ipc/ipc_message_macros.h" #include "native_mate/dictionary.h" #include "ui/gfx/codec/png_codec.h" @@ -58,57 +39,47 @@ #include "ui/gfx/screen.h" #include "ui/gl/gpu_switching_manager.h" -#if defined(OS_WIN) -#include "ui/gfx/switches.h" -#endif - -using content::NavigationEntry; -using content::RenderWidgetHostView; -using content::RenderWidgetHost; +DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::NativeWindowRelay); namespace atom { namespace { -// Array of available web runtime features. -const char* kWebRuntimeFeatures[] = { - switches::kExperimentalFeatures, - switches::kExperimentalCanvasFeatures, - switches::kSubpixelFontScaling, - switches::kOverlayScrollbars, - switches::kOverlayFullscreenVideo, - switches::kSharedWorker, -}; - -std::string RemoveWhitespace(const std::string& str) { - std::string trimmed; - if (base::RemoveChars(str, " ", &trimmed)) - return trimmed; - else - return str; +// Convert draggable regions in raw format to SkRegion format. Caller is +// responsible for deleting the returned SkRegion instance. +scoped_ptr DraggableRegionsToSkRegion( + const std::vector& regions) { + scoped_ptr sk_region(new SkRegion); + for (const DraggableRegion& region : regions) { + sk_region->op( + region.bounds.x(), + region.bounds.y(), + region.bounds.right(), + region.bounds.bottom(), + region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); + } + return sk_region.Pass(); } } // namespace -NativeWindow::NativeWindow(content::WebContents* web_contents, - const mate::Dictionary& options) - : content::WebContentsObserver(web_contents), +NativeWindow::NativeWindow( + brightray::InspectableWebContents* inspectable_web_contents, + const mate::Dictionary& options) + : content::WebContentsObserver(inspectable_web_contents->GetWebContents()), has_frame_(true), transparent_(false), enable_larger_than_screen_(false), is_closed_(false), - node_integration_(true), has_dialog_attached_(false), - zoom_factor_(1.0), - weak_factory_(this), - inspectable_web_contents_( - brightray::InspectableWebContents::Create(web_contents)) { - printing::PrintViewManagerBasic::CreateForWebContents(web_contents); + aspect_ratio_(0.0), + inspectable_web_contents_(inspectable_web_contents), + weak_factory_(this) { + inspectable_web_contents->GetView()->SetDelegate(this); options.Get(switches::kFrame, &has_frame_); options.Get(switches::kTransparent, &transparent_); options.Get(switches::kEnableLargerThanScreen, &enable_larger_than_screen_); - options.Get(switches::kNodeIntegration, &node_integration_); // Tell the content module to initialize renderer widget with transparent // mode. @@ -117,43 +88,7 @@ NativeWindow::NativeWindow(content::WebContents* web_contents, // Read icon before window is created. options.Get(switches::kIcon, &icon_); - // The "preload" option must be absolute path. - if (options.Get(switches::kPreloadScript, &preload_script_) && - !preload_script_.IsAbsolute()) { - LOG(ERROR) << "Path of \"preload\" script must be absolute."; - preload_script_.clear(); - } - - // Be compatible with old API of "node-integration" option. - std::string old_string_token; - if (options.Get(switches::kNodeIntegration, &old_string_token) && - old_string_token != "disable") - node_integration_ = true; - - // Read the web preferences. - options.Get(switches::kWebPreferences, &web_preferences_); - - // Read the zoom factor before any navigation. - options.Get(switches::kZoomFactor, &zoom_factor_); - - web_contents->SetDelegate(this); - inspectable_web_contents()->SetDelegate(this); - WindowList::AddWindow(this); - - // Override the user agent to contain application and atom-shell's version. - Browser* browser = Browser::Get(); - std::string product_name = base::StringPrintf( - "%s/%s Chrome/%s " ATOM_PRODUCT_NAME "/" ATOM_VERSION_STRING, - RemoveWhitespace(browser->GetName()).c_str(), - browser->GetVersion().c_str(), - CHROME_VERSION_STRING); - web_contents->GetMutableRendererPrefs()->user_agent_override = - content::BuildUserAgentFromProduct(product_name); - - // Get notified of title updated message. - registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED, - content::Source(web_contents)); } NativeWindow::~NativeWindow() { @@ -163,24 +98,13 @@ NativeWindow::~NativeWindow() { } // static -NativeWindow* NativeWindow::Create(const mate::Dictionary& options) { - content::WebContents::CreateParams create_params(AtomBrowserContext::Get()); - return Create(content::WebContents::Create(create_params), options); -} - -// static -NativeWindow* NativeWindow::FromRenderView(int process_id, int routing_id) { - // Stupid iterating. +NativeWindow* NativeWindow::FromWebContents( + content::WebContents* web_contents) { WindowList& window_list = *WindowList::GetInstance(); - for (auto w = window_list.begin(); w != window_list.end(); ++w) { - auto& window = *w; - content::WebContents* web_contents = window->GetWebContents(); - int window_process_id = web_contents->GetRenderProcessHost()->GetID(); - int window_routing_id = web_contents->GetRoutingID(); - if (window_routing_id == routing_id && window_process_id == process_id) + for (NativeWindow* window : window_list) { + if (window->web_contents() == web_contents) return window; } - return nullptr; } @@ -214,10 +138,12 @@ void NativeWindow::InitFromOptions(const mate::Dictionary& options) { if (options.Get(switches::kAlwaysOnTop, &top) && top) { SetAlwaysOnTop(true); } +#if defined(OS_MACOSX) || defined(OS_WIN) bool fullscreen; if (options.Get(switches::kFullscreen, &fullscreen) && fullscreen) { SetFullScreen(true); } +#endif bool skip; if (options.Get(switches::kSkipTaskbar, &skip) && skip) { SetSkipTaskbar(skip); @@ -270,72 +196,31 @@ bool NativeWindow::IsDocumentEdited() { void NativeWindow::SetMenu(ui::MenuModel* menu) { } -void NativeWindow::Print(bool silent, bool print_background) { - printing::PrintViewManagerBasic::FromWebContents(GetWebContents())-> - PrintNow(silent, print_background); -} - -void NativeWindow::ShowDefinitionForSelection() { - NOTIMPLEMENTED(); -} - -void NativeWindow::SetAutoHideMenuBar(bool auto_hide) { -} - -bool NativeWindow::IsMenuBarAutoHide() { - return false; -} - -void NativeWindow::SetMenuBarVisibility(bool visible) { -} - -bool NativeWindow::IsMenuBarVisible() { - return true; -} - bool NativeWindow::HasModalDialog() { return has_dialog_attached_; } -void NativeWindow::OpenDevTools(bool can_dock) { - inspectable_web_contents()->SetCanDock(can_dock); - inspectable_web_contents()->ShowDevTools(); -} - -void NativeWindow::CloseDevTools() { - inspectable_web_contents()->CloseDevTools(); -} - -bool NativeWindow::IsDevToolsOpened() { - return inspectable_web_contents()->IsDevToolsViewShowing(); -} - -void NativeWindow::InspectElement(int x, int y) { - OpenDevTools(true); - scoped_refptr agent( - content::DevToolsAgentHost::GetOrCreateFor(GetWebContents())); - agent->InspectElement(x, y); -} - void NativeWindow::FocusOnWebView() { - GetWebContents()->GetRenderViewHost()->Focus(); + web_contents()->GetRenderViewHost()->Focus(); } void NativeWindow::BlurWebView() { - GetWebContents()->GetRenderViewHost()->Blur(); + web_contents()->GetRenderViewHost()->Blur(); } bool NativeWindow::IsWebViewFocused() { - RenderWidgetHostView* host_view = - GetWebContents()->GetRenderViewHost()->GetView(); + auto host_view = web_contents()->GetRenderViewHost()->GetView(); return host_view && host_view->HasFocus(); } +bool NativeWindow::IsDevToolsFocused() { + return inspectable_web_contents_->GetView()->IsDevToolsViewFocused(); +} + void NativeWindow::CapturePage(const gfx::Rect& rect, const CapturePageCallback& callback) { - content::WebContents* contents = GetWebContents(); - RenderWidgetHostView* const view = contents->GetRenderWidgetHostView(); - RenderWidgetHost* const host = view ? view->GetRenderWidgetHost() : nullptr; + const auto view = web_contents()->GetRenderWidgetHostView(); + const auto host = view ? view->GetRenderWidgetHost() : nullptr; if (!view || !host) { callback.Run(SkBitmap()); return; @@ -365,14 +250,39 @@ void NativeWindow::CapturePage(const gfx::Rect& rect, kBGRA_8888_SkColorType); } -void NativeWindow::DestroyWebContents() { - if (!inspectable_web_contents_) - return; - - inspectable_web_contents_.reset(); +void NativeWindow::ShowDefinitionForSelection() { + NOTIMPLEMENTED(); } -void NativeWindow::CloseWebContents() { +void NativeWindow::SetAutoHideMenuBar(bool auto_hide) { +} + +bool NativeWindow::IsMenuBarAutoHide() { + return false; +} + +void NativeWindow::SetMenuBarVisibility(bool visible) { +} + +bool NativeWindow::IsMenuBarVisible() { + return true; +} + +double NativeWindow::GetAspectRatio() { + return aspect_ratio_; +} + +gfx::Size NativeWindow::GetAspectRatioExtraSize() { + return aspect_ratio_extraSize_; +} + +void NativeWindow::SetAspectRatio(double aspect_ratio, + const gfx::Size& extra_size) { + aspect_ratio_ = aspect_ratio; + aspect_ratio_extraSize_ = extra_size; +} + +void NativeWindow::RequestToClosePage() { bool prevent_default = false; FOR_EACH_OBSERVER(NativeWindowObserver, observers_, @@ -382,12 +292,6 @@ void NativeWindow::CloseWebContents() { return; } - content::WebContents* web_contents(GetWebContents()); - if (!web_contents) { - CloseImmediately(); - return; - } - // Assume the window is not responding if it doesn't cancel the close and is // not closed in 5s, in this way we can quickly show the unresponsive // dialog when the window is busy executing some script withouth waiting for @@ -395,104 +299,55 @@ void NativeWindow::CloseWebContents() { if (window_unresposive_closure_.IsCancelled()) ScheduleUnresponsiveEvent(5000); - if (web_contents->NeedToFireBeforeUnload()) - web_contents->DispatchBeforeUnload(false); + if (web_contents()->NeedToFireBeforeUnload()) + web_contents()->DispatchBeforeUnload(false); else - web_contents->Close(); + web_contents()->Close(); } -content::WebContents* NativeWindow::GetWebContents() const { +void NativeWindow::CloseContents(content::WebContents* source) { if (!inspectable_web_contents_) - return nullptr; - return inspectable_web_contents()->GetWebContents(); -} - -content::WebContents* NativeWindow::GetDevToolsWebContents() const { - if (!inspectable_web_contents_) - return nullptr; - return inspectable_web_contents()->devtools_web_contents(); -} - -void NativeWindow::AppendExtraCommandLineSwitches( - base::CommandLine* command_line, int child_process_id) { - // Append --node-integration to renderer process. - command_line->AppendSwitchASCII(switches::kNodeIntegration, - node_integration_ ? "true" : "false"); - - // Append --preload. - if (!preload_script_.empty()) - command_line->AppendSwitchPath(switches::kPreloadScript, preload_script_); - - // Append --zoom-factor. - if (zoom_factor_ != 1.0) - command_line->AppendSwitchASCII(switches::kZoomFactor, - base::DoubleToString(zoom_factor_)); - - if (web_preferences_.IsEmpty()) return; - bool b; -#if defined(OS_WIN) - // Check if DirectWrite is disabled. - if (web_preferences_.Get(switches::kDirectWrite, &b) && !b) - command_line->AppendSwitch(::switches::kDisableDirectWrite); -#endif + inspectable_web_contents_->GetView()->SetDelegate(nullptr); + inspectable_web_contents_ = nullptr; + Observe(nullptr); - // Check if plugins are enabled. - if (web_preferences_.Get("plugins", &b) && b) - command_line->AppendSwitch(switches::kEnablePlugins); + // When the web contents is gone, close the window immediately, but the + // memory will not be freed until you call delete. + // In this way, it would be safe to manage windows via smart pointers. If you + // want to free memory when the window is closed, you can do deleting by + // overriding the OnWindowClosed method in the observer. + CloseImmediately(); - // This set of options are not availabe in WebPreferences, so we have to pass - // them via command line and enable them in renderer procss. - for (size_t i = 0; i < arraysize(kWebRuntimeFeatures); ++i) { - const char* feature = kWebRuntimeFeatures[i]; - if (web_preferences_.Get(feature, &b)) - command_line->AppendSwitchASCII(feature, b ? "true" : "false"); - } + // Do not sent "unresponsive" event after window is closed. + window_unresposive_closure_.Cancel(); } -void NativeWindow::OverrideWebkitPrefs(content::WebPreferences* prefs) { - if (web_preferences_.IsEmpty()) - return; +void NativeWindow::RendererUnresponsive(content::WebContents* source) { + // Schedule the unresponsive shortly later, since we may receive the + // responsive event soon. This could happen after the whole application had + // blocked for a while. + // Also notice that when closing this event would be ignored because we have + // explicity started a close timeout counter. This is on purpose because we + // don't want the unresponsive event to be sent too early when user is closing + // the window. + ScheduleUnresponsiveEvent(50); +} - bool b; - std::vector list; - if (web_preferences_.Get("javascript", &b)) - prefs->javascript_enabled = b; - if (web_preferences_.Get("web-security", &b)) - prefs->web_security_enabled = b; - if (web_preferences_.Get("images", &b)) - prefs->images_enabled = b; - if (web_preferences_.Get("java", &b)) - prefs->java_enabled = b; - if (web_preferences_.Get("text-areas-are-resizable", &b)) - prefs->text_areas_are_resizable = b; - if (web_preferences_.Get("webgl", &b)) - prefs->experimental_webgl_enabled = b; - if (web_preferences_.Get("webaudio", &b)) - prefs->webaudio_enabled = b; - if (web_preferences_.Get("extra-plugin-dirs", &list)) { - if (content::PluginService::GetInstance()->NPAPIPluginsSupported()) { - for (size_t i = 0; i < list.size(); ++i) - content::PluginService::GetInstance()->AddExtraPluginDir(list[i]); - } else { - LOG(WARNING) << "NPAPI plugins not supported on this platform"; - } - } +void NativeWindow::RendererResponsive(content::WebContents* source) { + window_unresposive_closure_.Cancel(); + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnRendererResponsive()); } void NativeWindow::NotifyWindowClosed() { if (is_closed_) return; + WindowList::RemoveWindow(this); + is_closed_ = true; FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowClosed()); - - // Do not receive any notification after window has been closed, there is a - // crash that seems to be caused by this: http://git.io/YqMG5g. - registrar_.RemoveAll(); - - WindowList::RemoveWindow(this); } void NativeWindow::NotifyWindowBlur() { @@ -519,6 +374,18 @@ void NativeWindow::NotifyWindowRestore() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowRestore()); } +void NativeWindow::NotifyWindowResize() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowResize()); +} + +void NativeWindow::NotifyWindowMove() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowMove()); +} + +void NativeWindow::NotifyWindowMoved() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowMoved()); +} + void NativeWindow::NotifyWindowEnterFullScreen() { FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowEnterFullScreen()); @@ -529,70 +396,32 @@ void NativeWindow::NotifyWindowLeaveFullScreen() { OnWindowLeaveFullScreen()); } -bool NativeWindow::ShouldCreateWebContents( - content::WebContents* web_contents, - int route_id, - int main_frame_route_id, - WindowContainerType window_container_type, - const base::string16& frame_name, - const GURL& target_url, - const std::string& partition_id, - content::SessionStorageNamespace* session_storage_namespace) { - FOR_EACH_OBSERVER(NativeWindowObserver, - observers_, - WillCreatePopupWindow(frame_name, - target_url, - partition_id, - NEW_FOREGROUND_TAB)); - return false; +void NativeWindow::NotifyWindowEnterHtmlFullScreen() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowEnterHtmlFullScreen()); } -// In atom-shell all reloads and navigations started by renderer process would -// be redirected to this method, so we can have precise control of how we -// would open the url (in our case, is to restart the renderer process). See -// AtomRendererClient::ShouldFork for how this is done. -content::WebContents* NativeWindow::OpenURLFromTab( - content::WebContents* source, - const content::OpenURLParams& params) { - if (params.disposition != CURRENT_TAB) { - FOR_EACH_OBSERVER(NativeWindowObserver, - observers_, - WillCreatePopupWindow(base::string16(), - params.url, - "", - params.disposition)); - return nullptr; - } - - // Give user a chance to prevent navigation. - bool prevent_default = false; - FOR_EACH_OBSERVER(NativeWindowObserver, - observers_, - WillNavigate(&prevent_default, params.url)); - if (prevent_default) - return nullptr; - - content::NavigationController::LoadURLParams load_url_params(params.url); - load_url_params.referrer = params.referrer; - load_url_params.transition_type = params.transition; - load_url_params.extra_headers = params.extra_headers; - 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 NativeWindow::NotifyWindowLeaveHtmlFullScreen() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnWindowLeaveHtmlFullScreen()); } -content::JavaScriptDialogManager* NativeWindow::GetJavaScriptDialogManager( - content::WebContents* source) { - if (!dialog_manager_) - dialog_manager_.reset(new AtomJavaScriptDialogManager); +void NativeWindow::NotifyWindowExecuteWindowsCommand( + const std::string& command) { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, + OnExecuteWindowsCommand(command)); +} - return dialog_manager_.get(); +void NativeWindow::DevToolsFocused() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnDevToolsFocus()); +} + +void NativeWindow::DevToolsOpened() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnDevToolsOpened()); +} + +void NativeWindow::DevToolsClosed() { + FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnDevToolsClosed()); } void NativeWindow::RenderViewCreated( @@ -607,117 +436,22 @@ void NativeWindow::RenderViewCreated( impl->SetBackgroundOpaque(false); } -void NativeWindow::BeforeUnloadFired(content::WebContents* tab, - bool proceed, - bool* proceed_to_fire_unload) { - *proceed_to_fire_unload = proceed; +void NativeWindow::BeforeUnloadDialogCancelled() { + WindowList::WindowCloseCancelled(this); - if (!proceed) { - WindowList::WindowCloseCancelled(this); - - // Cancel unresponsive event when window close is cancelled. - window_unresposive_closure_.Cancel(); - } -} - -content::ColorChooser* NativeWindow::OpenColorChooser( - content::WebContents* web_contents, - SkColor color, - const std::vector& suggestions) { - return chrome::ShowColorChooser(web_contents, color); -} - -void NativeWindow::RunFileChooser(content::WebContents* web_contents, - const content::FileChooserParams& params) { - if (!web_dialog_helper_) - web_dialog_helper_.reset(new WebDialogHelper(this)); - web_dialog_helper_->RunFileChooser(web_contents, params); -} - -void NativeWindow::EnumerateDirectory(content::WebContents* web_contents, - int request_id, - const base::FilePath& path) { - if (!web_dialog_helper_) - web_dialog_helper_.reset(new WebDialogHelper(this)); - web_dialog_helper_->EnumerateDirectory(web_contents, request_id, path); -} - -void NativeWindow::RequestToLockMouse(content::WebContents* web_contents, - bool user_gesture, - bool last_unlocked_by_target) { - GetWebContents()->GotResponseToLockMouseRequest(true); -} - -bool NativeWindow::CanOverscrollContent() const { - return false; -} - -void NativeWindow::ActivateContents(content::WebContents* contents) { - FocusOnWebView(); -} - -void NativeWindow::DeactivateContents(content::WebContents* contents) { - BlurWebView(); -} - -void NativeWindow::MoveContents(content::WebContents* source, - const gfx::Rect& pos) { - SetBounds(pos); -} - -void NativeWindow::CloseContents(content::WebContents* source) { - // Destroy the WebContents before we close the window. - DestroyWebContents(); - - // When the web contents is gone, close the window immediately, but the - // memory will not be freed until you call delete. - // In this way, it would be safe to manage windows via smart pointers. If you - // want to free memory when the window is closed, you can do deleting by - // overriding the OnWindowClosed method in the observer. - CloseImmediately(); - - // Do not sent "unresponsive" event after window is closed. + // Cancel unresponsive event when window close is cancelled. window_unresposive_closure_.Cancel(); } -bool NativeWindow::IsPopupOrPanel(const content::WebContents* source) const { - // Only popup window can use things like window.moveTo. - return true; -} - -void NativeWindow::RendererUnresponsive(content::WebContents* source) { - // Schedule the unresponsive shortly later, since we may receive the - // responsive event soon. This could happen after the whole application had - // blocked for a while. - // Also notice that when closing this event would be ignored because we have - // explicity started a close timeout counter. This is on purpose because we - // don't want the unresponsive event to be sent too early when user is closing - // the window. - ScheduleUnresponsiveEvent(50); -} - -void NativeWindow::RendererResponsive(content::WebContents* source) { - window_unresposive_closure_.Cancel(); - FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnRendererResponsive()); -} - -void NativeWindow::EnterFullscreenModeForTab(content::WebContents* source, - const GURL& origin) { - SetFullScreen(true); -} - -void NativeWindow::ExitFullscreenModeForTab(content::WebContents* source) { - SetFullScreen(false); -} - -bool NativeWindow::IsFullscreenForTabOrPending( - const content::WebContents* source) const { - return IsFullscreen(); -} - -void NativeWindow::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 NativeWindow::TitleWasSet(content::NavigationEntry* entry, + bool explicit_set) { + bool prevent_default = false; + std::string text = entry ? base::UTF16ToUTF8(entry->GetTitle()) : ""; + FOR_EACH_OBSERVER(NativeWindowObserver, + observers_, + OnPageTitleUpdated(&prevent_default, text)); + if (!prevent_default) + SetTitle(text); } bool NativeWindow::OnMessageReceived(const IPC::Message& message) { @@ -731,65 +465,12 @@ bool NativeWindow::OnMessageReceived(const IPC::Message& message) { return handled; } -void NativeWindow::Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) { - if (type == content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED) { - std::pair* title = - content::Details>(details).ptr(); - - if (title->first) { - bool prevent_default = false; - std::string text = base::UTF16ToUTF8(title->first->GetTitle()); - FOR_EACH_OBSERVER(NativeWindowObserver, - observers_, - OnPageTitleUpdated(&prevent_default, text)); - - if (!prevent_default) - SetTitle(text); - } - } -} - -void NativeWindow::DevToolsSaveToFile(const std::string& url, - const std::string& content, - bool save_as) { - base::FilePath path; - PathsMap::iterator it = saved_files_.find(url); - if (it != saved_files_.end() && !save_as) { - path = it->second; - } else { - file_dialog::Filters filters; - base::FilePath default_path(base::FilePath::FromUTF8Unsafe(url)); - if (!file_dialog::ShowSaveDialog(this, url, default_path, filters, &path)) { - base::StringValue url_value(url); - CallDevToolsFunction("DevToolsAPI.canceledSaveURL", &url_value); - return; - } - } - - saved_files_[url] = path; - base::WriteFile(path, content.data(), content.size()); - - // Notify devtools. - base::StringValue url_value(url); - CallDevToolsFunction("DevToolsAPI.savedURL", &url_value); -} - -void NativeWindow::DevToolsAppendToFile(const std::string& url, - const std::string& content) { - PathsMap::iterator it = saved_files_.find(url); - if (it == saved_files_.end()) +void NativeWindow::UpdateDraggableRegions( + const std::vector& regions) { + // Draggable region is not supported for non-frameless window. + if (has_frame_) return; - base::AppendToFile(it->second, content.data(), content.size()); - - // Notify devtools. - base::StringValue url_value(url); - CallDevToolsFunction("DevToolsAPI.appendedToURL", &url_value); -} - -void NativeWindow::DevToolsFocused() { - FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnDevToolsFocus()); + draggable_region_ = DraggableRegionsToSkRegion(regions); } void NativeWindow::ScheduleUnresponsiveEvent(int ms) { @@ -820,27 +501,4 @@ void NativeWindow::OnCapturePageDone(const CapturePageCallback& callback, callback.Run(bitmap); } -void NativeWindow::CallDevToolsFunction(const std::string& function_name, - const base::Value* arg1, - const base::Value* arg2, - const base::Value* arg3) { - std::string params; - if (arg1) { - std::string json; - base::JSONWriter::Write(arg1, &json); - params.append(json); - if (arg2) { - base::JSONWriter::Write(arg2, &json); - params.append(", " + json); - if (arg3) { - base::JSONWriter::Write(arg3, &json); - params.append(", " + json); - } - } - } - base::string16 javascript = - base::UTF8ToUTF16(function_name + "(" + params + ");"); - GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(javascript); -} - } // namespace atom diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index 32c3493cdbf8..e9a2b9433d13 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -15,23 +15,21 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" -#include "brightray/browser/default_web_contents_delegate.h" -#include "brightray/browser/inspectable_web_contents_delegate.h" -#include "brightray/browser/inspectable_web_contents_impl.h" -#include "content/public/browser/notification_registrar.h" -#include "content/public/browser/notification_observer.h" +#include "brightray/browser/inspectable_web_contents_view_delegate.h" #include "content/public/browser/readback_types.h" -#include "native_mate/persistent_dictionary.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" #include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" -namespace base { -class CommandLine; +class SkRegion; + +namespace brightray { +class InspectableWebContents; } namespace content { -class BrowserContext; -class WebContents; -struct WebPreferences; +struct NativeWebKeyboardEvent; } namespace gfx { @@ -50,16 +48,12 @@ class MenuModel; namespace atom { -class AtomJavaScriptDialogManager; struct DraggableRegion; -class WebDialogHelper; -class NativeWindow : public brightray::DefaultWebContentsDelegate, - public brightray::InspectableWebContentsDelegate, - public content::WebContentsObserver, - public content::NotificationObserver { +class NativeWindow : public content::WebContentsObserver, + public brightray::InspectableWebContentsViewDelegate { public: - typedef base::Callback CapturePageCallback; + using CapturePageCallback = base::Callback; class DialogScope { public: @@ -84,20 +78,18 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, // Create window with existing WebContents, the caller is responsible for // managing the window's live. - static NativeWindow* Create(content::WebContents* web_contents, - const mate::Dictionary& options); + static NativeWindow* Create( + brightray::InspectableWebContents* inspectable_web_contents, + const mate::Dictionary& options); - // Create window with new WebContents, the caller is responsible for - // managing the window's live. - static NativeWindow* Create(const mate::Dictionary& options); - - // Find a window from its process id and routing id. - static NativeWindow* FromRenderView(int process_id, int routing_id); + // Find a window from its WebContents + static NativeWindow* FromWebContents(content::WebContents* web_contents); void InitFromOptions(const mate::Dictionary& options); virtual void Close() = 0; virtual void CloseImmediately() = 0; + virtual bool IsClosed() const { return is_closed_; } virtual void Focus(bool focus) = 0; virtual bool IsFocused() = 0; virtual void Show() = 0; @@ -142,30 +134,27 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, virtual void SetMenu(ui::MenuModel* menu); virtual bool HasModalDialog(); virtual gfx::NativeWindow GetNativeWindow() = 0; + + // Taskbar/Dock APIs. virtual void SetProgressBar(double progress) = 0; virtual void SetOverlayIcon(const gfx::Image& overlay, const std::string& description) = 0; + + // Workspace APIs. virtual void SetVisibleOnAllWorkspaces(bool visible) = 0; virtual bool IsVisibleOnAllWorkspaces() = 0; - virtual bool IsClosed() const { return is_closed_; } - virtual void OpenDevTools(bool can_dock); - virtual void CloseDevTools(); - virtual bool IsDevToolsOpened(); - virtual void InspectElement(int x, int y); - + // Webview APIs. virtual void FocusOnWebView(); virtual void BlurWebView(); virtual bool IsWebViewFocused(); + virtual bool IsDevToolsFocused(); // Captures the page with |rect|, |callback| would be called when capturing is // done. virtual void CapturePage(const gfx::Rect& rect, const CapturePageCallback& callback); - // Print current page. - virtual void Print(bool silent, bool print_background); - // Show popup dictionary. virtual void ShowDefinitionForSelection(); @@ -175,25 +164,25 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, virtual void SetMenuBarVisibility(bool visible); virtual bool IsMenuBarVisible(); - // The same with closing a tab in a real browser. - // - // Should be called by platform code when user want to close the window. - virtual void CloseWebContents(); - - // Destroy the WebContents immediately. - virtual void DestroyWebContents(); + // Set the aspect ratio when resizing window. + double GetAspectRatio(); + gfx::Size GetAspectRatioExtraSize(); + void SetAspectRatio(double aspect_ratio, const gfx::Size& extra_size); base::WeakPtr GetWeakPtr() { return weak_factory_.GetWeakPtr(); } - content::WebContents* GetWebContents() const; - content::WebContents* GetDevToolsWebContents() const; + // Requests the WebContents to close, can be cancelled by the page. + virtual void RequestToClosePage(); - // Called when renderer process is going to be started. - void AppendExtraCommandLineSwitches(base::CommandLine* command_line, - int child_process_id); - void OverrideWebkitPrefs(content::WebPreferences* prefs); + // Methods called by the WebContents. + virtual void CloseContents(content::WebContents* source); + virtual void RendererUnresponsive(content::WebContents* source); + virtual void RendererResponsive(content::WebContents* source); + virtual void HandleKeyboardEvent( + content::WebContents*, + const content::NativeWebKeyboardEvent& event) {} // Public API used by platform-dependent delegates and observers to send UI // related notifications. @@ -204,99 +193,66 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, void NotifyWindowUnmaximize(); void NotifyWindowMinimize(); void NotifyWindowRestore(); + void NotifyWindowMove(); + void NotifyWindowResize(); + void NotifyWindowMoved(); void NotifyWindowEnterFullScreen(); void NotifyWindowLeaveFullScreen(); + void NotifyWindowEnterHtmlFullScreen(); + void NotifyWindowLeaveHtmlFullScreen(); + void NotifyWindowExecuteWindowsCommand(const std::string& command); void AddObserver(NativeWindowObserver* obs) { observers_.AddObserver(obs); } - void RemoveObserver(NativeWindowObserver* obs) { observers_.RemoveObserver(obs); } + brightray::InspectableWebContents* inspectable_web_contents() const { + return inspectable_web_contents_; + } + bool has_frame() const { return has_frame_; } + bool transparent() const { return transparent_; } + SkRegion* draggable_region() const { return draggable_region_.get(); } + bool enable_larger_than_screen() const { return enable_larger_than_screen_; } + gfx::ImageSkia icon() const { return icon_; } void set_has_dialog_attached(bool has_dialog_attached) { has_dialog_attached_ = has_dialog_attached; } protected: - explicit NativeWindow(content::WebContents* web_contents, - const mate::Dictionary& options); + NativeWindow(brightray::InspectableWebContents* inspectable_web_contents, + const mate::Dictionary& options); - brightray::InspectableWebContentsImpl* inspectable_web_contents() const { - return static_cast( - inspectable_web_contents_.get()); - } + // brightray::InspectableWebContentsViewDelegate: + void DevToolsFocused() override; + void DevToolsOpened() override; + void DevToolsClosed() override; - // Called when the window needs to update its draggable region. - virtual void UpdateDraggableRegions( - const std::vector& regions) = 0; - - // Implementations of content::WebContentsDelegate. - bool ShouldCreateWebContents( - content::WebContents* web_contents, - int route_id, - int main_frame_route_id, - WindowContainerType window_container_type, - const base::string16& 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; - content::JavaScriptDialogManager* GetJavaScriptDialogManager( - content::WebContents* source) override; - void BeforeUnloadFired(content::WebContents* tab, - bool proceed, - bool* proceed_to_fire_unload) override; - content::ColorChooser* OpenColorChooser( - content::WebContents* web_contents, - SkColor color, - const std::vector& suggestions) override; - void RunFileChooser(content::WebContents* web_contents, - const content::FileChooserParams& params) override; - void EnumerateDirectory(content::WebContents* web_contents, - int request_id, - const base::FilePath& path) override; - void RequestToLockMouse(content::WebContents* web_contents, - bool user_gesture, - bool last_unlocked_by_target) override; - bool CanOverscrollContent() const override; - void ActivateContents(content::WebContents* contents) override; - void DeactivateContents(content::WebContents* contents) override; - void MoveContents(content::WebContents* source, - const gfx::Rect& pos) override; - void CloseContents(content::WebContents* source) override; - bool IsPopupOrPanel( - const content::WebContents* source) const override; - void RendererUnresponsive(content::WebContents* source) override; - void RendererResponsive(content::WebContents* source) override; - void EnterFullscreenModeForTab(content::WebContents* source, - const GURL& origin) override; - void ExitFullscreenModeForTab(content::WebContents* source) override; - bool IsFullscreenForTabOrPending( - const content::WebContents* source) const override; - - // Implementations of content::WebContentsObserver. + // content::WebContentsObserver: void RenderViewCreated(content::RenderViewHost* render_view_host) override; - void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; + void BeforeUnloadDialogCancelled() override; + void TitleWasSet(content::NavigationEntry* entry, bool explicit_set) override; bool OnMessageReceived(const IPC::Message& message) override; - // Implementations of content::NotificationObserver. - void Observe(int type, - const content::NotificationSource& source, - const content::NotificationDetails& details) override; + private: + // Called when the window needs to update its draggable region. + void UpdateDraggableRegions( + const std::vector& regions); - // Implementations of brightray::InspectableWebContentsDelegate. - void DevToolsSaveToFile(const std::string& url, - const std::string& content, - bool save_as) override; - void DevToolsAppendToFile(const std::string& url, - const std::string& content) override; - void DevToolsFocused() override; + // Schedule a notification unresponsive event. + void ScheduleUnresponsiveEvent(int ms); + + // Dispatch unresponsive event to observers. + void NotifyWindowUnresponsive(); + + // Called when CapturePage has done. + void OnCapturePageDone(const CapturePageCallback& callback, + const SkBitmap& bitmap, + content::ReadbackResponse response); // Whether window has standard frame. bool has_frame_; @@ -304,42 +260,19 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, // Whether window is transparent. bool transparent_; + // For custom drag, the whole window is non-draggable and the draggable region + // has to been explicitly provided. + scoped_ptr draggable_region_; // used in custom drag. + // Whether window can be resized larger than screen. bool enable_larger_than_screen_; // Window icon. gfx::ImageSkia icon_; - private: - // Schedule a notification unresponsive event. - void ScheduleUnresponsiveEvent(int ms); - - // Dispatch unresponsive event to observers. - void NotifyWindowUnresponsive(); - - // Call a function in devtools. - void CallDevToolsFunction(const std::string& function_name, - const base::Value* arg1 = NULL, - const base::Value* arg2 = NULL, - const base::Value* arg3 = NULL); - - // Called when CapturePage has done. - void OnCapturePageDone(const CapturePageCallback& callback, - const SkBitmap& bitmap, - content::ReadbackResponse response); - - // Notification manager. - content::NotificationRegistrar registrar_; - - // Observers of this window. - ObserverList observers_; - // The windows has been closed. bool is_closed_; - // Whether node integration is enabled. - bool node_integration_; - // There is a dialog that has been attached to window. bool has_dialog_attached_; @@ -347,32 +280,36 @@ class NativeWindow : public brightray::DefaultWebContentsDelegate, // it should be cancelled when we can prove that the window is responsive. base::CancelableClosure window_unresposive_closure_; - // Web preferences. - mate::PersistentDictionary web_preferences_; + // Used to maintain the aspect ratio of a view which is inside of the + // content view. + double aspect_ratio_; + gfx::Size aspect_ratio_extraSize_; - // The script to load before page's JavaScript starts to run. - base::FilePath preload_script_; + // The page this window is viewing. + brightray::InspectableWebContents* inspectable_web_contents_; - // Page's default zoom factor. - double zoom_factor_; + // Observers of this window. + base::ObserverList observers_; base::WeakPtrFactory weak_factory_; - scoped_ptr web_dialog_helper_; - scoped_ptr dialog_manager_; - - // Notice that inspectable_web_contents_ must be placed after dialog_manager_, - // so we can make sure inspectable_web_contents_ is destroyed before - // dialog_manager_, otherwise a crash would happen. - scoped_ptr inspectable_web_contents_; - - // Maps url to file path, used by the file requests sent from devtools. - typedef std::map PathsMap; - PathsMap saved_files_; - DISALLOW_COPY_AND_ASSIGN(NativeWindow); }; +// This class provides a hook to get a NativeWindow from a WebContents. +class NativeWindowRelay : + public content::WebContentsUserData { + public: + explicit NativeWindowRelay(base::WeakPtr window) + : key(UserDataKey()), window(window) {} + + void* key; + base::WeakPtr window; + + private: + friend class content::WebContentsUserData; +}; + } // namespace atom #endif // ATOM_BROWSER_NATIVE_WINDOW_H_ diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 8a6d141c34bf..20ad60531498 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -11,23 +11,21 @@ #include #include "base/mac/scoped_nsobject.h" -#include "base/memory/scoped_ptr.h" #include "atom/browser/native_window.h" @class AtomNSWindow; @class AtomNSWindowDelegate; @class FullSizeContentView; -class SkRegion; namespace atom { class NativeWindowMac : public NativeWindow { public: - explicit NativeWindowMac(content::WebContents* web_contents, - const mate::Dictionary& options); - virtual ~NativeWindowMac(); + NativeWindowMac(brightray::InspectableWebContents* inspectable_web_contents, + const mate::Dictionary& options); + ~NativeWindowMac() override; - // NativeWindow implementation. + // NativeWindow: void Close() override; void CloseImmediately() override; void Focus(bool focus) override; @@ -84,14 +82,8 @@ class NativeWindowMac : public NativeWindow { // Called to handle a mouse event. void HandleMouseEvent(NSEvent* event); - // Clip web view to rounded corner. - void ClipWebView(); - protected: - void UpdateDraggableRegions( - const std::vector& regions) override; - - // Implementations of content::WebContentsDelegate. + // NativeWindow: void HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent&) override; @@ -117,10 +109,6 @@ class NativeWindowMac : public NativeWindow { // The presentation options before entering kiosk mode. NSApplicationPresentationOptions kiosk_options_; - // For custom drag, the whole window is non-draggable and the draggable region - // has to been explicitly provided. - scoped_ptr draggable_region_; // used in custom drag. - // Mouse location since the last mouse event, in screen coordinates. This is // used in custom drag to compute the window movement. NSPoint last_mouse_offset_; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 8dd6bcbacbe1..9555914c899d 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -20,11 +20,23 @@ #include "content/public/browser/render_widget_host_view.h" #include "native_mate/dictionary.h" -static const CGFloat kAtomWindowCornerRadius = 4.0; +namespace { -@interface NSView (PrivateMethods) -- (CGFloat)roundedCornerRadius; -@end +// Prevents window from resizing during the scope. +class ScopedDisableResize { + public: + ScopedDisableResize() { disable_resize_ = true; } + ~ScopedDisableResize() { disable_resize_ = false; } + + static bool IsResizeDisabled() { return disable_resize_; } + + private: + static bool disable_resize_; +}; + +bool ScopedDisableResize::disable_resize_ = false; + +} // namespace // This view always takes the size of its superview. It is intended to be used // as a NSWindow's contentView. It is needed because NSWindow's implementation @@ -68,7 +80,7 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; } - (void)windowDidBecomeMain:(NSNotification*)notification { - content::WebContents* web_contents = shell_->GetWebContents(); + content::WebContents* web_contents = shell_->web_contents(); if (!web_contents) return; @@ -82,7 +94,7 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; } - (void)windowDidResignMain:(NSNotification*)notification { - content::WebContents* web_contents = shell_->GetWebContents(); + content::WebContents* web_contents = shell_->web_contents(); if (!web_contents) return; @@ -95,9 +107,53 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; shell_->NotifyWindowBlur(); } +- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize { + NSSize newSize = frameSize; + double aspectRatio = shell_->GetAspectRatio(); + + if (aspectRatio > 0.0) { + gfx::Size windowSize = shell_->GetSize(); + gfx::Size contentSize = shell_->GetContentSize(); + gfx::Size extraSize = shell_->GetAspectRatioExtraSize(); + + double extraWidthPlusFrame = + windowSize.width() - contentSize.width() + extraSize.width(); + double extraHeightPlusFrame = + windowSize.height() - contentSize.height() + extraSize.height(); + + newSize.width = + roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio + + extraWidthPlusFrame); + + // If the new width is less than the frame size use it as the primary + // constraint. This ensures that the value returned by this method will + // never be larger than the users requested window size. + if (newSize.width <= frameSize.width) { + newSize.height = + roundf((newSize.width - extraWidthPlusFrame) / aspectRatio + + extraHeightPlusFrame); + } else { + newSize.height = + roundf((frameSize.width - extraWidthPlusFrame) / aspectRatio + + extraHeightPlusFrame); + newSize.width = + roundf((newSize.height - extraHeightPlusFrame) * aspectRatio + + extraWidthPlusFrame); + } + } + + return newSize; +} + - (void)windowDidResize:(NSNotification*)notification { - if (!shell_->has_frame()) - shell_->ClipWebView(); + shell_->NotifyWindowResize(); +} + +- (void)windowDidMove:(NSNotification*)notification { + // TODO(zcbenz): Remove the alias after figuring out a proper + // way to disptach move. + shell_->NotifyWindowMove(); + shell_->NotifyWindowMoved(); } - (void)windowDidMiniaturize:(NSNotification*)notification { @@ -140,7 +196,7 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; // When user tries to close the window by clicking the close button, we do // not close the window immediately, instead we try to close the web page // fisrt, and when the web page is closed the window will also be closed. - shell_->CloseWebContents(); + shell_->RequestToClosePage(); return NO; } @@ -167,8 +223,12 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; enable_larger_than_screen_ = enable; } -// Enable the window to be larger than screen. - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen { + // Resizing is disabled. + if (ScopedDisableResize::IsResizeDisabled()) + return [self frame]; + + // Enable the window to be larger than screen. if (enable_larger_than_screen_) return frameRect; else @@ -271,31 +331,9 @@ static const CGFloat kAtomWindowCornerRadius = 4.0; namespace atom { -namespace { - -// Convert draggable regions in raw format to SkRegion format. Caller is -// responsible for deleting the returned SkRegion instance. -SkRegion* DraggableRegionsToSkRegion( - const std::vector& regions) { - SkRegion* sk_region = new SkRegion; - for (std::vector::const_iterator iter = regions.begin(); - iter != regions.end(); - ++iter) { - const DraggableRegion& region = *iter; - sk_region->op( - region.bounds.x(), - region.bounds.y(), - region.bounds.right(), - region.bounds.bottom(), - region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); - } - return sk_region; -} - -} // namespace - -NativeWindowMac::NativeWindowMac(content::WebContents* web_contents, - const mate::Dictionary& options) +NativeWindowMac::NativeWindowMac( + brightray::InspectableWebContents* web_contents, + const mate::Dictionary& options) : NativeWindow(web_contents, options), is_kiosk_(false), attention_request_id_(0) { @@ -315,22 +353,33 @@ NativeWindowMac::NativeWindowMac(content::WebContents* web_contents, NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask; - if (!useStandardWindow) { + if (!useStandardWindow || transparent() || !has_frame()) { styleMask |= NSTexturedBackgroundWindowMask; } + std::string titleBarStyle = "default"; + options.Get(switches::kTitleBarStyle, &titleBarStyle); + + if (base::mac::IsOSYosemiteOrLater()) { + // New title bar styles are available in Yosemite or newer + if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { + styleMask |= NSFullSizeContentViewWindowMask; + styleMask |= NSUnifiedTitleAndToolbarWindowMask; + } + } + window_.reset([[AtomNSWindow alloc] initWithContentRect:cocoa_bounds styleMask:styleMask backing:NSBackingStoreBuffered defer:YES]); [window_ setShell:this]; - [window_ setEnableLargerThanScreen:enable_larger_than_screen_]; + [window_ setEnableLargerThanScreen:enable_larger_than_screen()]; window_delegate_.reset([[AtomNSWindowDelegate alloc] initWithShell:this]); [window_ setDelegate:window_delegate_]; - if (transparent_) { + if (transparent()) { // Make window has transparent background. [window_ setOpaque:NO]; [window_ setHasShadow:NO]; @@ -338,16 +387,30 @@ NativeWindowMac::NativeWindowMac(content::WebContents* web_contents, } // Remove non-transparent corners, see http://git.io/vfonD. - if (!has_frame_) + if (!has_frame()) [window_ setOpaque:NO]; // We will manage window's lifetime ourselves. [window_ setReleasedWhenClosed:NO]; + // Configure title bar look on Yosemite or newer + if (base::mac::IsOSYosemiteOrLater()) { + if ((titleBarStyle == "hidden") || (titleBarStyle == "hidden-inset")) { + [window_ setTitlebarAppearsTransparent:YES]; + [window_ setTitleVisibility:NSWindowTitleHidden]; + if (titleBarStyle == "hidden-inset") { + NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]; + toolbar.showsBaselineSeparator = NO; + [window_ setToolbar:toolbar]; + [toolbar release]; + } + } + } + // On OS X the initial window size doesn't include window frame. bool use_content_size = false; options.Get(switches::kUseContentSize, &use_content_size); - if (has_frame_ && !use_content_size) + if (!has_frame() || !use_content_size) SetSize(gfx::Size(width, height)); // Enable the NSView to accept first mouse event. @@ -376,9 +439,7 @@ NativeWindowMac::NativeWindowMac(content::WebContents* web_contents, } NativeWindowMac::~NativeWindowMac() { - // Force InspectableWebContents to be destroyed before we destroy window, - // because it may still be observing the window at this time. - DestroyWebContents(); + Observe(nullptr); } void NativeWindowMac::Close() { @@ -486,6 +547,11 @@ gfx::Rect NativeWindowMac::GetBounds() { } void NativeWindowMac::SetContentSize(const gfx::Size& size) { + if (!has_frame()) { + SetSize(size); + return; + } + NSRect frame_nsrect = [window_ frame]; NSSize frame = frame_nsrect.size; NSSize content = [window_ contentRectForFrameRect:frame_nsrect].size; @@ -499,6 +565,9 @@ void NativeWindowMac::SetContentSize(const gfx::Size& size) { } gfx::Size NativeWindowMac::GetContentSize() { + if (!has_frame()) + return GetSize(); + NSRect bounds = [[window_ contentView] bounds]; return gfx::Size(bounds.size.width, bounds.size.height); } @@ -530,12 +599,15 @@ gfx::Size NativeWindowMac::GetMaximumSize() { } 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]; + [window_ setStyleMask:[window_ styleMask] & (~NSResizableWindowMask)]; } } @@ -556,8 +628,8 @@ void NativeWindowMac::Center() { } void NativeWindowMac::SetTitle(const std::string& title) { - // We don't want the title to show in transparent window. - if (transparent_) + // We don't want the title to show in transparent or frameless window. + if (transparent() || !has_frame()) return; [window_ setTitle:base::SysUTF8ToNSString(title)]; @@ -669,10 +741,9 @@ void NativeWindowMac::SetOverlayIcon(const gfx::Image& overlay, } void NativeWindowMac::ShowDefinitionForSelection() { - content::WebContents* web_contents = GetWebContents(); - if (!web_contents) + if (!web_contents()) return; - content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView(); + auto rwhv = web_contents()->GetRenderWidgetHostView(); if (!rwhv) return; rwhv->ShowDefinitionForSelection(); @@ -694,17 +765,16 @@ bool NativeWindowMac::IsVisibleOnAllWorkspaces() { } bool NativeWindowMac::IsWithinDraggableRegion(NSPoint point) const { - if (!draggable_region_) + if (!draggable_region()) return false; - content::WebContents* web_contents = GetWebContents(); - if (!web_contents) + if (!web_contents()) return false; - NSView* webView = web_contents->GetNativeView(); + NSView* webView = web_contents()->GetNativeView(); NSInteger webViewHeight = NSHeight([webView bounds]); // |draggable_region_| is stored in local platform-indepdent coordiate system // while |point| is in local Cocoa coordinate system. Do the conversion // to match these two. - return draggable_region_->contains(point.x, webViewHeight - point.y); + return draggable_region()->contains(point.x, webViewHeight - point.y); } void NativeWindowMac::HandleMouseEvent(NSEvent* event) { @@ -724,15 +794,6 @@ void NativeWindowMac::HandleMouseEvent(NSEvent* event) { } } -void NativeWindowMac::UpdateDraggableRegions( - const std::vector& regions) { - // Draggable region is not supported for non-frameless window. - if (has_frame_) - return; - - draggable_region_.reset(DraggableRegionsToSkRegion(regions)); -} - void NativeWindowMac::HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) { @@ -758,35 +819,27 @@ void NativeWindowMac::HandleKeyboardEvent( } void NativeWindowMac::InstallView() { + // Make sure the bottom corner is rounded: http://crbug.com/396264. + [[window_ contentView] setWantsLayer:YES]; + NSView* view = inspectable_web_contents()->GetView()->GetNativeView(); - if (has_frame_) { - // Add layer with white background for the contents view. - base::scoped_nsobject layer([[CALayer alloc] init]); - [layer setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)]; - [view setLayer:layer]; + if (has_frame()) { [view setFrame:[[window_ contentView] bounds]]; [[window_ contentView] addSubview:view]; } else { - if (base::mac::IsOSYosemiteOrLater()) { - // In OSX 10.10, adding subviews to the root view for the NSView hierarchy - // produces warnings. To eliminate the warnings, we resize the contentView - // to fill the window, and add subviews to that. - // http://crbug.com/380412 - content_view_.reset([[FullSizeContentView alloc] init]); - [content_view_ - setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - [content_view_ setFrame:[[[window_ contentView] superview] bounds]]; - [window_ setContentView:content_view_]; + // In OSX 10.10, adding subviews to the root view for the NSView hierarchy + // produces warnings. To eliminate the warnings, we resize the contentView + // to fill the window, and add subviews to that. + // http://crbug.com/380412 + content_view_.reset([[FullSizeContentView alloc] init]); + [content_view_ + setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + [content_view_ setFrame:[[[window_ contentView] superview] bounds]]; + [window_ setContentView:content_view_]; - [view setFrame:[content_view_ bounds]]; - [content_view_ addSubview:view]; - } else { - NSView* frameView = [[window_ contentView] superview]; - [view setFrame:[frameView bounds]]; - [frameView addSubview:view]; - } + [view setFrame:[content_view_ bounds]]; + [content_view_ addSubview:view]; - ClipWebView(); InstallDraggableRegionView(); [[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES]; @@ -805,17 +858,8 @@ void NativeWindowMac::UninstallView() { [view removeFromSuperview]; } -void NativeWindowMac::ClipWebView() { - content::WebContents* web_contents = GetWebContents(); - if (!web_contents) - return; - NSView* webView = web_contents->GetNativeView(); - webView.layer.masksToBounds = YES; - webView.layer.cornerRadius = kAtomWindowCornerRadius; -} - void NativeWindowMac::InstallDraggableRegionView() { - NSView* webView = GetWebContents()->GetNativeView(); + NSView* webView = web_contents()->GetNativeView(); base::scoped_nsobject controlRegion( [[ControlRegionView alloc] initWithShellWindow:this]); [controlRegion setFrame:NSMakeRect(0, 0, @@ -825,9 +869,10 @@ void NativeWindowMac::InstallDraggableRegionView() { } // static -NativeWindow* NativeWindow::Create(content::WebContents* web_contents, - const mate::Dictionary& options) { - return new NativeWindowMac(web_contents, options); +NativeWindow* NativeWindow::Create( + brightray::InspectableWebContents* inspectable_web_contents, + const mate::Dictionary& options) { + return new NativeWindowMac(inspectable_web_contents, options); } } // namespace atom diff --git a/atom/browser/native_window_observer.h b/atom/browser/native_window_observer.h index aa5824946731..5b0a0c56b3de 100644 --- a/atom/browser/native_window_observer.h +++ b/atom/browser/native_window_observer.h @@ -47,17 +47,27 @@ class NativeWindowObserver { virtual void OnWindowUnmaximize() {} virtual void OnWindowMinimize() {} virtual void OnWindowRestore() {} + virtual void OnWindowResize() {} + virtual void OnWindowMove() {} + virtual void OnWindowMoved() {} virtual void OnWindowEnterFullScreen() {} virtual void OnWindowLeaveFullScreen() {} + virtual void OnWindowEnterHtmlFullScreen() {} + virtual void OnWindowLeaveHtmlFullScreen() {} - // Called when devtools window gets focused. + // Redirect devtools events. virtual void OnDevToolsFocus() {} + virtual void OnDevToolsOpened() {} + virtual void OnDevToolsClosed() {} // Called when renderer is hung. virtual void OnRendererUnresponsive() {} // Called when renderer recovers. virtual void OnRendererResponsive() {} + + // Called on Windows when App Commands arrive (WM_APPCOMMAND) + virtual void OnExecuteWindowsCommand(const std::string& command_name) {} }; } // namespace atom diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 4c99f88969ba..45697c96683c 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -4,10 +4,6 @@ #include "atom/browser/native_window_views.h" -#if defined(OS_WIN) -#include -#endif - #include #include @@ -16,10 +12,10 @@ #include "atom/common/draggable_region.h" #include "atom/common/options_switches.h" #include "base/strings/utf_string_conversions.h" -#include "browser/inspectable_web_contents_view.h" +#include "brightray/browser/inspectable_web_contents.h" +#include "brightray/browser/inspectable_web_contents_view.h" #include "content/public/browser/native_web_keyboard_event.h" #include "native_mate/dictionary.h" -#include "ui/aura/window.h" #include "ui/aura/window_tree_host.h" #include "ui/base/hit_test.h" #include "ui/gfx/image/image.h" @@ -35,26 +31,20 @@ #include "atom/browser/browser.h" #include "atom/browser/ui/views/global_menu_bar_x11.h" #include "atom/browser/ui/views/frameless_view.h" +#include "atom/browser/ui/views/native_frame_view.h" #include "atom/browser/ui/x/window_state_watcher.h" #include "atom/browser/ui/x/x_window_utils.h" -#include "base/environment.h" -#include "base/nix/xdg_util.h" #include "base/strings/string_util.h" #include "chrome/browser/ui/libgtk2ui/unity_service.h" -#include "dbus/bus.h" -#include "dbus/object_proxy.h" -#include "dbus/message.h" #include "ui/base/x/x11_util.h" #include "ui/gfx/x/x11_types.h" #include "ui/views/window/native_frame_view.h" #elif defined(OS_WIN) #include "atom/browser/ui/views/win_frame_view.h" -#include "base/win/scoped_comptr.h" -#include "base/win/windows_version.h" +#include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h" #include "ui/base/win/shell.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/win/dpi.h" -#include "ui/views/win/hwnd_util.h" +#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" #endif namespace atom { @@ -68,42 +58,6 @@ const int kMenuBarHeight = 20; const int kMenuBarHeight = 25; #endif -#if defined(USE_X11) -// Returns true if the bus name "com.canonical.AppMenu.Registrar" is available. -bool ShouldUseGlobalMenuBar() { - dbus::Bus::Options options; - scoped_refptr bus(new dbus::Bus(options)); - - dbus::ObjectProxy* object_proxy = - bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS)); - dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, "ListNames"); - scoped_ptr response(object_proxy->CallMethodAndBlock( - &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); - if (!response) { - bus->ShutdownAndBlock(); - return false; - } - - dbus::MessageReader reader(response.get()); - dbus::MessageReader array_reader(NULL); - if (!reader.PopArray(&array_reader)) { - bus->ShutdownAndBlock(); - return false; - } - while (array_reader.HasMoreData()) { - std::string name; - if (array_reader.PopString(&name) && - name == "com.canonical.AppMenu.Registrar") { - bus->ShutdownAndBlock(); - return true; - } - } - - bus->ShutdownAndBlock(); - return false; -} -#endif - bool IsAltKey(const content::NativeWebKeyboardEvent& event) { #if defined(USE_X11) // 164 and 165 represent VK_LALT and VK_RALT. @@ -123,6 +77,70 @@ bool IsAltModifier(const content::NativeWebKeyboardEvent& event) { (modifiers == (Modifiers::AltKey | Modifiers::IsRight)); } +#if defined(OS_WIN) +// Convert Win32 WM_APPCOMMANDS to strings. +const char* AppCommandToString(int command_id) { + switch (command_id) { + case APPCOMMAND_BROWSER_BACKWARD : return "browser-backward"; + case APPCOMMAND_BROWSER_FORWARD : return "browser-forward"; + case APPCOMMAND_BROWSER_REFRESH : return "browser-refresh"; + case APPCOMMAND_BROWSER_STOP : return "browser-stop"; + case APPCOMMAND_BROWSER_SEARCH : return "browser-search"; + case APPCOMMAND_BROWSER_FAVORITES : return "browser-favorites"; + case APPCOMMAND_BROWSER_HOME : return "browser-home"; + case APPCOMMAND_VOLUME_MUTE : return "volume-mute"; + case APPCOMMAND_VOLUME_DOWN : return "volume-down"; + case APPCOMMAND_VOLUME_UP : return "volume-up"; + case APPCOMMAND_MEDIA_NEXTTRACK : return "media-nexttrack"; + case APPCOMMAND_MEDIA_PREVIOUSTRACK : return "media-previoustrack"; + case APPCOMMAND_MEDIA_STOP : return "media-stop"; + case APPCOMMAND_MEDIA_PLAY_PAUSE : return "media-play_pause"; + case APPCOMMAND_LAUNCH_MAIL : return "launch-mail"; + case APPCOMMAND_LAUNCH_MEDIA_SELECT : return "launch-media-select"; + case APPCOMMAND_LAUNCH_APP1 : return "launch-app1"; + case APPCOMMAND_LAUNCH_APP2 : return "launch-app2"; + case APPCOMMAND_BASS_DOWN : return "bass-down"; + case APPCOMMAND_BASS_BOOST : return "bass-boost"; + case APPCOMMAND_BASS_UP : return "bass-up"; + case APPCOMMAND_TREBLE_DOWN : return "treble-down"; + case APPCOMMAND_TREBLE_UP : return "treble-up"; + case APPCOMMAND_MICROPHONE_VOLUME_MUTE : return "microphone-volume-mute"; + case APPCOMMAND_MICROPHONE_VOLUME_DOWN : return "microphone-volume-down"; + case APPCOMMAND_MICROPHONE_VOLUME_UP : return "microphone-volume-up"; + case APPCOMMAND_HELP : return "help"; + case APPCOMMAND_FIND : return "find"; + case APPCOMMAND_NEW : return "new"; + case APPCOMMAND_OPEN : return "open"; + case APPCOMMAND_CLOSE : return "close"; + case APPCOMMAND_SAVE : return "save"; + case APPCOMMAND_PRINT : return "print"; + case APPCOMMAND_UNDO : return "undo"; + case APPCOMMAND_REDO : return "redo"; + case APPCOMMAND_COPY : return "copy"; + case APPCOMMAND_CUT : return "cut"; + case APPCOMMAND_PASTE : return "paste"; + case APPCOMMAND_REPLY_TO_MAIL : return "reply-to-mail"; + case APPCOMMAND_FORWARD_MAIL : return "forward-mail"; + case APPCOMMAND_SEND_MAIL : return "send-mail"; + case APPCOMMAND_SPELL_CHECK : return "spell-check"; + case APPCOMMAND_MIC_ON_OFF_TOGGLE : return "mic-on-off-toggle"; + case APPCOMMAND_CORRECTION_LIST : return "correction-list"; + case APPCOMMAND_MEDIA_PLAY : return "media-play"; + case APPCOMMAND_MEDIA_PAUSE : return "media-pause"; + case APPCOMMAND_MEDIA_RECORD : return "media-record"; + case APPCOMMAND_MEDIA_FAST_FORWARD : return "media-fast-forward"; + case APPCOMMAND_MEDIA_REWIND : return "media-rewind"; + case APPCOMMAND_MEDIA_CHANNEL_UP : return "media-channel-up"; + case APPCOMMAND_MEDIA_CHANNEL_DOWN : return "media-channel-down"; + case APPCOMMAND_DELETE : return "delete"; + case APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE: + return "dictate-or-command-control-toggle"; + default: + return "unknown"; + } +} +#endif + class NativeWindowClientView : public views::ClientView { public: NativeWindowClientView(views::Widget* widget, @@ -132,7 +150,7 @@ class NativeWindowClientView : public views::ClientView { virtual ~NativeWindowClientView() {} bool CanClose() override { - static_cast(contents_view())->CloseWebContents(); + static_cast(contents_view())->RequestToClosePage(); return false; } @@ -142,8 +160,9 @@ class NativeWindowClientView : public views::ClientView { } // namespace -NativeWindowViews::NativeWindowViews(content::WebContents* web_contents, - const mate::Dictionary& options) +NativeWindowViews::NativeWindowViews( + brightray::InspectableWebContents* web_contents, + const mate::Dictionary& options) : NativeWindow(web_contents, options), window_(new views::Widget), web_view_(inspectable_web_contents()->GetView()->GetView()), @@ -165,7 +184,7 @@ NativeWindowViews::NativeWindowViews(content::WebContents* web_contents, options.Get(switches::kResizable, &resizable_); #endif - if (enable_larger_than_screen_) + if (enable_larger_than_screen()) // We need to set a default maximum window size here otherwise Windows // will not allow us to resize the window larger than scree. // Setting directly to INT_MAX somehow doesn't work, so we just devide @@ -176,6 +195,7 @@ NativeWindowViews::NativeWindowViews(content::WebContents* web_contents, options.Get(switches::kWidth, &width); options.Get(switches::kHeight, &height); gfx::Rect bounds(0, 0, width, height); + widget_size_ = bounds.size(); window_->AddObserver(this); @@ -184,12 +204,20 @@ NativeWindowViews::NativeWindowViews(content::WebContents* web_contents, params.bounds = bounds; params.delegate = this; params.type = views::Widget::InitParams::TYPE_WINDOW; - params.remove_standard_frame = !has_frame_; + params.remove_standard_frame = !has_frame(); - if (transparent_) + if (transparent()) params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; -#if defined(USE_X11) +#if defined(OS_WIN) + params.native_widget = + new views::DesktopNativeWidgetAura(window_.get()); + atom_desktop_window_tree_host_win_ = new AtomDesktopWindowTreeHostWin( + this, + window_.get(), + static_cast(params.native_widget)); + params.desktop_window_tree_host = atom_desktop_window_tree_host_win_; +#elif defined(USE_X11) std::string name = Browser::Get()->GetName(); // Set WM_WINDOW_ROLE. params.wm_role_name = "browser-window"; @@ -218,14 +246,21 @@ NativeWindowViews::NativeWindowViews(content::WebContents* web_contents, // Before the window is mapped the SetWMSpecState can not work, so we have // to manually set the _NET_WM_STATE. + std::vector<::Atom> state_atom_list; bool skip_taskbar = false; if (options.Get(switches::kSkipTaskbar, &skip_taskbar) && skip_taskbar) { - std::vector<::Atom> state_atom_list; state_atom_list.push_back(GetAtom("_NET_WM_STATE_SKIP_TASKBAR")); - ui::SetAtomArrayProperty(GetAcceleratedWidget(), "_NET_WM_STATE", "ATOM", - state_atom_list); } + // Before the window is mapped, there is no SHOW_FULLSCREEN_STATE. + bool fullscreen = false; + if (options.Get(switches::kFullscreen, & fullscreen) && fullscreen) { + state_atom_list.push_back(GetAtom("_NET_WM_STATE_FULLSCREEN")); + } + + ui::SetAtomArrayProperty(GetAcceleratedWidget(), "_NET_WM_STATE", "ATOM", + state_atom_list); + // Set the _NET_WM_WINDOW_TYPE. std::string window_type; if (options.Get(switches::kType, &window_type)) @@ -237,24 +272,24 @@ NativeWindowViews::NativeWindowViews(content::WebContents* web_contents, set_background(views::Background::CreateStandardPanelBackground()); AddChildView(web_view_); - if (has_frame_ && + if (has_frame() && options.Get(switches::kUseContentSize, &use_content_size_) && use_content_size_) bounds = ContentBoundsToWindowBounds(bounds); #if defined(OS_WIN) - if (!has_frame_) { + 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; // We should not show a frame for transparent window. - if (transparent_) + if (transparent()) frame_style &= ~(WS_THICKFRAME | WS_CAPTION); ::SetWindowLong(GetAcceleratedWidget(), GWL_STYLE, frame_style); } - if (transparent_) { + if (transparent()) { // Transparent window on Windows has to have WS_EX_COMPOSITED style. LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE); ex_style |= WS_EX_COMPOSITED; @@ -264,14 +299,14 @@ NativeWindowViews::NativeWindowViews(content::WebContents* web_contents, // TODO(zcbenz): This was used to force using native frame on Windows 2003, we // should check whether setting it in InitParams can work. - if (has_frame_) { + if (has_frame()) { window_->set_frame_type(views::Widget::FrameType::FRAME_TYPE_FORCE_NATIVE); 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_) + if (transparent() && !has_frame()) wm::SetShadowType(GetNativeWindow(), wm::SHADOW_TYPE_NONE); window_->UpdateWindowIcon(); @@ -351,17 +386,19 @@ bool NativeWindowViews::IsMinimized() { } void NativeWindowViews::SetFullScreen(bool fullscreen) { +#if defined(OS_WIN) + // There is no native fullscreen state on Windows. + window_->SetFullscreen(fullscreen); + if (fullscreen) + NotifyWindowEnterFullScreen(); + else + NotifyWindowLeaveFullScreen(); +#else if (IsVisible()) window_->SetFullscreen(fullscreen); else window_->native_widget_private()->ShowWithWindowState( ui::SHOW_STATE_FULLSCREEN); -#if defined(OS_WIN) - // There is no native fullscreen state on Windows. - if (fullscreen) - NotifyWindowEnterFullScreen(); - else - NotifyWindowLeaveFullScreen(); #endif } @@ -392,7 +429,7 @@ gfx::Rect NativeWindowViews::GetBounds() { } void NativeWindowViews::SetContentSize(const gfx::Size& size) { - if (!has_frame_) { + if (!has_frame()) { NativeWindow::SetSize(size); return; } @@ -403,7 +440,7 @@ void NativeWindowViews::SetContentSize(const gfx::Size& size) { } gfx::Size NativeWindowViews::GetContentSize() { - if (!has_frame_) + if (!has_frame()) return GetSize(); gfx::Size content_size = @@ -415,14 +452,6 @@ gfx::Size NativeWindowViews::GetContentSize() { void NativeWindowViews::SetMinimumSize(const gfx::Size& size) { minimum_size_ = size; - -#if defined(USE_X11) - XSizeHints size_hints; - size_hints.flags = PMinSize; - size_hints.min_width = size.width(); - size_hints.min_height = size.height(); - XSetWMNormalHints(gfx::GetXDisplay(), GetAcceleratedWidget(), &size_hints); -#endif } gfx::Size NativeWindowViews::GetMinimumSize() { @@ -431,14 +460,6 @@ gfx::Size NativeWindowViews::GetMinimumSize() { void NativeWindowViews::SetMaximumSize(const gfx::Size& size) { maximum_size_ = size; - -#if defined(USE_X11) - XSizeHints size_hints; - size_hints.flags = PMaxSize; - size_hints.max_width = size.width(); - size_hints.max_height = size.height(); - XSetWMNormalHints(gfx::GetXDisplay(), GetAcceleratedWidget(), &size_hints); -#endif } gfx::Size NativeWindowViews::GetMaximumSize() { @@ -540,6 +561,19 @@ bool NativeWindowViews::IsKiosk() { } void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) { + if (menu_model == nullptr) { + // Remove accelerators + accelerator_table_.clear(); + GetFocusManager()->UnregisterAccelerators(this); + // and menu bar. +#if defined(USE_X11) + global_menu_bar_.reset(); +#endif + SetMenuBarVisibility(false); + menu_bar_.reset(); + return; + } + RegisterAccelerators(menu_model); #if defined(USE_X11) @@ -554,7 +588,7 @@ void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) { #endif // Do not show menu bar in frameless window. - if (!has_frame_) + if (!has_frame()) return; if (!menu_bar_) { @@ -579,24 +613,7 @@ gfx::NativeWindow NativeWindowViews::GetNativeWindow() { void NativeWindowViews::SetProgressBar(double progress) { #if defined(OS_WIN) - if (base::win::GetVersion() < base::win::VERSION_WIN7) - return; - base::win::ScopedComPtr taskbar; - if (FAILED(taskbar.CreateInstance(CLSID_TaskbarList, NULL, - CLSCTX_INPROC_SERVER) || - FAILED(taskbar->HrInit()))) { - return; - } - HWND frame = views::HWNDForNativeWindow(GetNativeWindow()); - if (progress > 1.0) { - taskbar->SetProgressState(frame, TBPF_INDETERMINATE); - } else if (progress < 0) { - taskbar->SetProgressState(frame, TBPF_NOPROGRESS); - } else if (progress >= 0) { - taskbar->SetProgressValue(frame, - static_cast(progress * 100), - 100); - } + taskbar_host_.SetProgressBar(GetAcceleratedWidget(), progress); #elif defined(USE_X11) if (unity::IsRunning()) { unity::SetProgressFraction(progress); @@ -607,22 +624,7 @@ void NativeWindowViews::SetProgressBar(double progress) { void NativeWindowViews::SetOverlayIcon(const gfx::Image& overlay, const std::string& description) { #if defined(OS_WIN) - if (base::win::GetVersion() < base::win::VERSION_WIN7) - return; - - base::win::ScopedComPtr taskbar; - if (FAILED(taskbar.CreateInstance(CLSID_TaskbarList, NULL, - CLSCTX_INPROC_SERVER) || - FAILED(taskbar->HrInit()))) { - return; - } - - HWND frame = views::HWNDForNativeWindow(GetNativeWindow()); - - std::wstring wstr = std::wstring(description.begin(), description.end()); - taskbar->SetOverlayIcon(frame, - IconUtil::CreateHICONFromSkBitmap(overlay.AsBitmap()), - wstr.c_str()); + taskbar_host_.SetOverlayIcon(GetAcceleratedWidget(), overlay, description); #endif } @@ -679,29 +681,6 @@ gfx::AcceleratedWidget NativeWindowViews::GetAcceleratedWidget() { return GetNativeWindow()->GetHost()->GetAcceleratedWidget(); } -void NativeWindowViews::UpdateDraggableRegions( - const std::vector& regions) { - if (has_frame_) - return; - - SkRegion* draggable_region = new SkRegion; - - // By default, the whole window is non-draggable. We need to explicitly - // include those draggable regions. - for (std::vector::const_iterator iter = regions.begin(); - iter != regions.end(); ++iter) { - const DraggableRegion& region = *iter; - draggable_region->op( - region.bounds.x(), - region.bounds.y(), - region.bounds.right(), - region.bounds.bottom(), - region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op); - } - - draggable_region_.reset(draggable_region); -} - void NativeWindowViews::OnWidgetActivationChanged( views::Widget* widget, bool active) { if (widget != window_.get()) @@ -712,14 +691,26 @@ void NativeWindowViews::OnWidgetActivationChanged( else NotifyWindowBlur(); - if (active && GetWebContents() && !IsDevToolsOpened()) - GetWebContents()->Focus(); + if (active && inspectable_web_contents() && + !inspectable_web_contents()->IsDevToolsViewShowing()) + web_contents()->Focus(); // Hide menu bar when window is blured. if (!active && menu_bar_autohide_ && menu_bar_visible_) SetMenuBarVisibility(false); } +void NativeWindowViews::OnWidgetBoundsChanged( + views::Widget* widget, const gfx::Rect& bounds) { + if (widget != window_.get()) + return; + + if (widget_size_ != bounds.size()) { + NotifyWindowResize(); + widget_size_ = bounds.size(); + } +} + void NativeWindowViews::DeleteDelegate() { NotifyWindowClosed(); } @@ -749,7 +740,7 @@ bool NativeWindowViews::ShouldHandleSystemCommands() const { } gfx::ImageSkia NativeWindowViews::GetWindowAppIcon() { - return icon_; + return icon(); } gfx::ImageSkia NativeWindowViews::GetWindowIcon() { @@ -772,12 +763,12 @@ bool NativeWindowViews::ShouldDescendIntoChildForEventHandling( gfx::NativeView child, const gfx::Point& location) { // App window should claim mouse events that fall within the draggable region. - if (draggable_region_ && - draggable_region_->contains(location.x(), location.y())) + if (draggable_region() && + draggable_region()->contains(location.x(), location.y())) return false; // And the events on border for dragging resizable frameless window. - if (!has_frame_ && CanResize()) { + if (!has_frame() && CanResize()) { FramelessView* frame = static_cast( window_->non_client_view()->frame_view()); return frame->ResizingBorderHitTest(location) == HTNOWHERE; @@ -797,8 +788,8 @@ views::NonClientFrameView* NativeWindowViews::CreateNonClientFrameView( frame_view->Init(this, widget); return frame_view; #else - if (has_frame_) { - return new views::NativeFrameView(widget); + if (has_frame()) { + return new NativeFrameView(this, widget); } else { FramelessView* frame_view = new FramelessView; frame_view->Init(this, widget); @@ -807,6 +798,10 @@ views::NonClientFrameView* NativeWindowViews::CreateNonClientFrameView( #endif } +void NativeWindowViews::OnWidgetMove() { + NotifyWindowMove(); +} + #if defined(OS_WIN) bool NativeWindowViews::ExecuteWindowsCommand(int command_id) { // Windows uses the 4 lower order bits of |command_id| for type-specific @@ -823,6 +818,9 @@ bool NativeWindowViews::ExecuteWindowsCommand(int command_id) { is_minimized_ = false; } else if ((command_id & sc_mask) == SC_MAXIMIZE) { NotifyWindowMaximize(); + } else { + std::string command = AppCommandToString(command_id); + NotifyWindowExecuteWindowsCommand(command); } return false; } @@ -840,11 +838,16 @@ void NativeWindowViews::GetDevToolsWindowWMClass( } #endif -void NativeWindowViews::HandleMouseDown() { - // Hide menu bar when web view is clicked. - if (menu_bar_autohide_ && menu_bar_visible_) - SetMenuBarVisibility(false); +#if defined(OS_WIN) +bool NativeWindowViews::PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) { + // Handle thumbar button click message. + if (message == WM_COMMAND && HIWORD(w_param) == THBN_CLICKED) + return taskbar_host_.HandleThumbarButtonEvent(LOWORD(w_param)); + else + return false; } +#endif void NativeWindowViews::HandleKeyboardEvent( content::WebContents*, @@ -914,6 +917,7 @@ void NativeWindowViews::RegisterAccelerators(ui::MenuModel* menu_model) { gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds( const gfx::Rect& bounds) { + gfx::Point origin = bounds.origin(); #if defined(OS_WIN) gfx::Rect dpi_bounds = gfx::win::DIPToScreenRect(bounds); gfx::Rect window_bounds = gfx::win::ScreenToDIPRect( @@ -922,6 +926,9 @@ gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds( gfx::Rect window_bounds = window_->non_client_view()->GetWindowBoundsForClientBounds(bounds); #endif + // The window's position would also be changed, but we only want to change + // the size. + window_bounds.set_origin(origin); if (menu_bar_ && menu_bar_visible_) window_bounds.set_height(window_bounds.height() + kMenuBarHeight); @@ -938,9 +945,10 @@ ui::WindowShowState NativeWindowViews::GetRestoredState() { } // static -NativeWindow* NativeWindow::Create(content::WebContents* web_contents, - const mate::Dictionary& options) { - return new NativeWindowViews(web_contents, options); +NativeWindow* NativeWindow::Create( + brightray::InspectableWebContents* inspectable_web_contents, + const mate::Dictionary& options) { + return new NativeWindowViews(inspectable_web_contents, options); } } // namespace atom diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 15f073d8d8c1..355f5bd38ef3 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -14,6 +14,11 @@ #include "ui/views/widget/widget_delegate.h" #include "ui/views/widget/widget_observer.h" +#if defined(OS_WIN) +#include "atom/browser/ui/win/message_handler_delegate.h" +#include "atom/browser/ui/win/taskbar_host.h" +#endif + namespace views { class UnhandledKeyboardEventHandler; } @@ -24,13 +29,20 @@ class GlobalMenuBarX11; class MenuBar; class WindowStateWatcher; +#if defined(OS_WIN) +class AtomDesktopWindowTreeHostWin; +#endif + class NativeWindowViews : public NativeWindow, +#if defined(OS_WIN) + public MessageHandlerDelegate, +#endif public views::WidgetDelegateView, public views::WidgetObserver { public: - explicit NativeWindowViews(content::WebContents* web_contents, - const mate::Dictionary& options); - virtual ~NativeWindowViews(); + NativeWindowViews(brightray::InspectableWebContents* inspectable_web_contents, + const mate::Dictionary& options); + ~NativeWindowViews() override; // NativeWindow: void Close() override; @@ -82,17 +94,18 @@ class NativeWindowViews : public NativeWindow, gfx::AcceleratedWidget GetAcceleratedWidget(); - SkRegion* draggable_region() const { return draggable_region_.get(); } views::Widget* widget() const { return window_.get(); } - private: - // NativeWindow: - void UpdateDraggableRegions( - const std::vector& regions) override; +#if defined(OS_WIN) + TaskbarHost& taskbar_host() { return taskbar_host_; } +#endif + private: // views::WidgetObserver: void OnWidgetActivationChanged( views::Widget* widget, bool active) override; + void OnWidgetBoundsChanged( + views::Widget* widget, const gfx::Rect& bounds) override; // views::WidgetDelegate: void DeleteDelegate() override; @@ -113,19 +126,25 @@ class NativeWindowViews : public NativeWindow, views::ClientView* CreateClientView(views::Widget* widget) override; views::NonClientFrameView* CreateNonClientFrameView( views::Widget* widget) override; + void OnWidgetMove() override; #if defined(OS_WIN) bool ExecuteWindowsCommand(int command_id) override; #endif - // brightray::InspectableWebContentsDelegate: + // brightray::InspectableWebContentsViewDelegate: gfx::ImageSkia GetDevToolsWindowIcon() override; #if defined(USE_X11) void GetDevToolsWindowWMClass( std::string* name, std::string* class_name) override; #endif - // content::WebContentsDelegate: - void HandleMouseDown() override; +#if defined(OS_WIN) + // MessageHandlerDelegate: + bool PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override; +#endif + + // NativeWindow: void HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) override; @@ -157,9 +176,13 @@ class NativeWindowViews : public NativeWindow, // Handles window state events. scoped_ptr window_state_watcher_; #elif defined(OS_WIN) + // Weak ref. + AtomDesktopWindowTreeHostWin* atom_desktop_window_tree_host_win_; // Records window was whether restored from minimized state or maximized // state. bool is_minimized_; + // In charge of running taskbar related APIs. + TaskbarHost taskbar_host_; #endif // Handles unhandled keyboard messages coming back from the renderer process. @@ -173,8 +196,7 @@ class NativeWindowViews : public NativeWindow, std::string title_; gfx::Size minimum_size_; gfx::Size maximum_size_; - - scoped_ptr draggable_region_; + gfx::Size widget_size_; DISALLOW_COPY_AND_ASSIGN(NativeWindowViews); }; diff --git a/atom/browser/net/adapter_request_job.cc b/atom/browser/net/adapter_request_job.cc deleted file mode 100644 index 8b562bcbf248..000000000000 --- a/atom/browser/net/adapter_request_job.cc +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/browser/net/adapter_request_job.h" - -#include "base/threading/sequenced_worker_pool.h" -#include "atom/browser/net/url_request_buffer_job.h" -#include "atom/browser/net/url_request_string_job.h" -#include "atom/browser/net/asar/url_request_asar_job.h" -#include "atom/common/asar/asar_util.h" -#include "content/public/browser/browser_thread.h" -#include "net/base/net_errors.h" -#include "net/url_request/url_request_error_job.h" -#include "net/url_request/url_request_file_job.h" - -namespace atom { - -AdapterRequestJob::AdapterRequestJob(ProtocolHandler* protocol_handler, - net::URLRequest* request, - net::NetworkDelegate* network_delegate) - : URLRequestJob(request, network_delegate), - protocol_handler_(protocol_handler), - weak_factory_(this) { -} - -void AdapterRequestJob::Start() { - DCHECK(!real_job_.get()); - content::BrowserThread::PostTask( - content::BrowserThread::UI, - FROM_HERE, - base::Bind(&AdapterRequestJob::GetJobTypeInUI, - weak_factory_.GetWeakPtr())); -} - -void AdapterRequestJob::Kill() { - if (real_job_.get()) // Kill could happen when real_job_ is created. - real_job_->Kill(); -} - -bool AdapterRequestJob::ReadRawData(net::IOBuffer* buf, - int buf_size, - int *bytes_read) { - DCHECK(!real_job_.get()); - return real_job_->ReadRawData(buf, buf_size, bytes_read); -} - -bool AdapterRequestJob::IsRedirectResponse(GURL* location, - int* http_status_code) { - DCHECK(!real_job_.get()); - return real_job_->IsRedirectResponse(location, http_status_code); -} - -net::Filter* AdapterRequestJob::SetupFilter() const { - DCHECK(!real_job_.get()); - return real_job_->SetupFilter(); -} - -bool AdapterRequestJob::GetMimeType(std::string* mime_type) const { - DCHECK(!real_job_.get()); - return real_job_->GetMimeType(mime_type); -} - -bool AdapterRequestJob::GetCharset(std::string* charset) { - DCHECK(!real_job_.get()); - return real_job_->GetCharset(charset); -} - -base::WeakPtr AdapterRequestJob::GetWeakPtr() { - return weak_factory_.GetWeakPtr(); -} - -void AdapterRequestJob::CreateErrorJobAndStart(int error_code) { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); - - real_job_ = new net::URLRequestErrorJob( - request(), network_delegate(), error_code); - real_job_->Start(); -} - -void AdapterRequestJob::CreateStringJobAndStart(const std::string& mime_type, - const std::string& charset, - const std::string& data) { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); - - real_job_ = new URLRequestStringJob( - request(), network_delegate(), mime_type, charset, data); - real_job_->Start(); -} - -void AdapterRequestJob::CreateBufferJobAndStart(const std::string& mime_type, - const std::string& charset, - v8::Local buffer) { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); - - real_job_ = new URLRequestBufferJob( - request(), network_delegate(), mime_type, charset, buffer); - real_job_->Start(); -} - -void AdapterRequestJob::CreateFileJobAndStart(const base::FilePath& path) { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); - real_job_ = asar::CreateJobFromPath( - path, - request(), - network_delegate(), - content::BrowserThread::GetBlockingPool()-> - GetTaskRunnerWithShutdownBehavior( - base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); - real_job_->Start(); -} - -void AdapterRequestJob::CreateJobFromProtocolHandlerAndStart() { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); - DCHECK(protocol_handler_); - real_job_ = protocol_handler_->MaybeCreateJob(request(), - network_delegate()); - if (!real_job_.get()) - CreateErrorJobAndStart(net::ERR_NOT_IMPLEMENTED); - else - real_job_->Start(); -} - -} // namespace atom diff --git a/atom/browser/net/adapter_request_job.h b/atom/browser/net/adapter_request_job.h deleted file mode 100644 index 406214e3c162..000000000000 --- a/atom/browser/net/adapter_request_job.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_BROWSER_NET_ADAPTER_REQUEST_JOB_H_ -#define ATOM_BROWSER_NET_ADAPTER_REQUEST_JOB_H_ - -#include - -#include "base/memory/weak_ptr.h" -#include "net/url_request/url_request_job.h" -#include "net/url_request/url_request_job_factory.h" -#include "v8/include/v8.h" - -namespace base { -class FilePath; -} - -namespace atom { - -// Ask JS which type of job it wants, and then delegate corresponding methods. -class AdapterRequestJob : public net::URLRequestJob { - public: - typedef net::URLRequestJobFactory::ProtocolHandler ProtocolHandler; - - AdapterRequestJob(ProtocolHandler* protocol_handler, - net::URLRequest* request, - net::NetworkDelegate* network_delegate); - - public: - // net::URLRequestJob: - void Start() override; - void Kill() override; - bool ReadRawData(net::IOBuffer* buf, - int buf_size, - int *bytes_read) override; - bool IsRedirectResponse(GURL* location, - int* http_status_code) override; - net::Filter* SetupFilter() const override; - bool GetMimeType(std::string* mime_type) const override; - bool GetCharset(std::string* charset) override; - - base::WeakPtr GetWeakPtr(); - - ProtocolHandler* default_protocol_handler() { return protocol_handler_; } - - // Override this function to determine which job should be started. - virtual void GetJobTypeInUI() = 0; - - void CreateErrorJobAndStart(int error_code); - void CreateStringJobAndStart(const std::string& mime_type, - const std::string& charset, - const std::string& data); - void CreateBufferJobAndStart(const std::string& mime_type, - const std::string& charset, - v8::Local buffer); - void CreateFileJobAndStart(const base::FilePath& path); - void CreateJobFromProtocolHandlerAndStart(); - - private: - // The delegated request job. - scoped_refptr real_job_; - - // Default protocol handler. - ProtocolHandler* protocol_handler_; - - base::WeakPtrFactory weak_factory_; - - DISALLOW_COPY_AND_ASSIGN(AdapterRequestJob); -}; - -} // namespace atom - -#endif // ATOM_BROWSER_NET_ADAPTER_REQUEST_JOB_H_ diff --git a/atom/browser/net/asar/asar_protocol_handler.cc b/atom/browser/net/asar/asar_protocol_handler.cc index 6d2a2cd5bf0a..324f8339c8c9 100644 --- a/atom/browser/net/asar/asar_protocol_handler.cc +++ b/atom/browser/net/asar/asar_protocol_handler.cc @@ -5,45 +5,11 @@ #include "atom/browser/net/asar/asar_protocol_handler.h" #include "atom/browser/net/asar/url_request_asar_job.h" -#include "atom/common/asar/archive.h" -#include "atom/common/asar/asar_util.h" #include "net/base/filename_util.h" #include "net/base/net_errors.h" -#include "net/url_request/url_request_error_job.h" -#include "net/url_request/url_request_file_job.h" namespace asar { -// static -net::URLRequestJob* CreateJobFromPath( - const base::FilePath& full_path, - net::URLRequest* request, - net::NetworkDelegate* network_delegate, - const scoped_refptr file_task_runner) { - // Create asar:// job when the path contains "xxx.asar/", otherwise treat the - // URL request as file://. - base::FilePath asar_path, relative_path; - if (!GetAsarArchivePath(full_path, &asar_path, &relative_path)) - return new net::URLRequestFileJob(request, network_delegate, full_path, - file_task_runner); - - std::shared_ptr archive = GetOrCreateAsarArchive(asar_path); - Archive::FileInfo file_info; - if (!archive || !archive->GetFileInfo(relative_path, &file_info)) - return new net::URLRequestErrorJob(request, network_delegate, - net::ERR_FILE_NOT_FOUND); - - if (file_info.unpacked) { - base::FilePath real_path; - archive->CopyFileOut(relative_path, &real_path); - return new net::URLRequestFileJob(request, network_delegate, real_path, - file_task_runner); - } - - return new URLRequestAsarJob(request, network_delegate, archive, - relative_path, file_info, file_task_runner); -} - AsarProtocolHandler::AsarProtocolHandler( const scoped_refptr& file_task_runner) : file_task_runner_(file_task_runner) {} @@ -56,8 +22,9 @@ net::URLRequestJob* AsarProtocolHandler::MaybeCreateJob( net::NetworkDelegate* network_delegate) const { base::FilePath full_path; net::FileURLToFilePath(request->url(), &full_path); - return CreateJobFromPath(full_path, request, network_delegate, - file_task_runner_); + URLRequestAsarJob* job = new URLRequestAsarJob(request, network_delegate); + job->Initialize(file_task_runner_, full_path); + return job; } bool AsarProtocolHandler::IsSafeRedirectTarget(const GURL& location) const { diff --git a/atom/browser/net/asar/url_request_asar_job.cc b/atom/browser/net/asar/url_request_asar_job.cc index 477c6610b1b1..9b9a3c69d27f 100644 --- a/atom/browser/net/asar/url_request_asar_job.cc +++ b/atom/browser/net/asar/url_request_asar_job.cc @@ -5,48 +5,128 @@ #include "atom/browser/net/asar/url_request_asar_job.h" #include +#include +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/strings/string_util.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "atom/common/asar/archive.h" +#include "atom/common/asar/asar_util.h" #include "net/base/file_stream.h" +#include "net/base/filename_util.h" #include "net/base/io_buffer.h" +#include "net/base/load_flags.h" #include "net/base/mime_util.h" #include "net/base/net_errors.h" +#include "net/filter/filter.h" +#include "net/http/http_util.h" #include "net/url_request/url_request_status.h" +#if defined(OS_WIN) +#include "base/win/shortcut.h" +#endif + namespace asar { +URLRequestAsarJob::FileMetaInfo::FileMetaInfo() + : file_size(0), + mime_type_result(false), + file_exists(false), + is_directory(false) { +} + URLRequestAsarJob::URLRequestAsarJob( net::URLRequest* request, - net::NetworkDelegate* network_delegate, - std::shared_ptr archive, - const base::FilePath& file_path, - const Archive::FileInfo& file_info, - const scoped_refptr& file_task_runner) + net::NetworkDelegate* network_delegate) : net::URLRequestJob(request, network_delegate), - archive_(archive), - file_path_(file_path), - file_info_(file_info), - stream_(new net::FileStream(file_task_runner)), + type_(TYPE_ERROR), remaining_bytes_(0), - file_task_runner_(file_task_runner), weak_ptr_factory_(this) {} URLRequestAsarJob::~URLRequestAsarJob() {} -void URLRequestAsarJob::Start() { - remaining_bytes_ = static_cast(file_info_.size); +void URLRequestAsarJob::Initialize( + const scoped_refptr file_task_runner, + const base::FilePath& file_path) { + // Determine whether it is an asar file. + base::FilePath asar_path, relative_path; + if (!GetAsarArchivePath(file_path, &asar_path, &relative_path)) { + InitializeFileJob(file_task_runner, file_path); + return; + } - int flags = base::File::FLAG_OPEN | - base::File::FLAG_READ | - base::File::FLAG_ASYNC; - int rv = stream_->Open(archive_->path(), flags, - base::Bind(&URLRequestAsarJob::DidOpen, - weak_ptr_factory_.GetWeakPtr())); - if (rv != net::ERR_IO_PENDING) - DidOpen(rv); + std::shared_ptr archive = GetOrCreateAsarArchive(asar_path); + Archive::FileInfo file_info; + if (!archive || !archive->GetFileInfo(relative_path, &file_info)) { + type_ = TYPE_ERROR; + return; + } + + if (file_info.unpacked) { + base::FilePath real_path; + archive->CopyFileOut(relative_path, &real_path); + InitializeFileJob(file_task_runner, real_path); + return; + } + + InitializeAsarJob(file_task_runner, archive, relative_path, file_info); +} + +void URLRequestAsarJob::InitializeAsarJob( + const scoped_refptr file_task_runner, + std::shared_ptr archive, + const base::FilePath& file_path, + const Archive::FileInfo& file_info) { + type_ = TYPE_ASAR; + file_task_runner_ = file_task_runner; + stream_.reset(new net::FileStream(file_task_runner_)); + archive_ = archive; + file_path_ = file_path; + file_info_ = file_info; +} + +void URLRequestAsarJob::InitializeFileJob( + const scoped_refptr file_task_runner, + const base::FilePath& file_path) { + type_ = TYPE_FILE; + file_task_runner_ = file_task_runner; + stream_.reset(new net::FileStream(file_task_runner_)); + file_path_ = file_path; +} + +void URLRequestAsarJob::Start() { + if (type_ == TYPE_ASAR) { + remaining_bytes_ = static_cast(file_info_.size); + + int flags = base::File::FLAG_OPEN | + base::File::FLAG_READ | + base::File::FLAG_ASYNC; + int rv = stream_->Open(archive_->path(), flags, + base::Bind(&URLRequestAsarJob::DidOpen, + weak_ptr_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) + DidOpen(rv); + } else if (type_ == TYPE_FILE) { + FileMetaInfo* meta_info = new FileMetaInfo(); + file_task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&URLRequestAsarJob::FetchMetaInfo, file_path_, + base::Unretained(meta_info)), + base::Bind(&URLRequestAsarJob::DidFetchMetaInfo, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(meta_info))); + } else { + NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_FILE_NOT_FOUND)); + } } void URLRequestAsarJob::Kill() { + stream_.reset(); weak_ptr_factory_.InvalidateWeakPtrs(); + URLRequestJob::Kill(); } @@ -85,8 +165,97 @@ bool URLRequestAsarJob::ReadRawData(net::IOBuffer* dest, return false; } +bool URLRequestAsarJob::IsRedirectResponse(GURL* location, + int* http_status_code) { + if (type_ != TYPE_FILE) + return false; +#if defined(OS_WIN) + // Follow a Windows shortcut. + // We just resolve .lnk file, ignore others. + if (!base::LowerCaseEqualsASCII(file_path_.Extension(), ".lnk")) + return false; + + base::FilePath new_path = file_path_; + bool resolved; + resolved = base::win::ResolveShortcut(new_path, &new_path, NULL); + + // If shortcut is not resolved succesfully, do not redirect. + if (!resolved) + return false; + + *location = net::FilePathToFileURL(new_path); + *http_status_code = 301; + return true; +#else + return false; +#endif +} + +net::Filter* URLRequestAsarJob::SetupFilter() const { + // Bug 9936 - .svgz files needs to be decompressed. + return base::LowerCaseEqualsASCII(file_path_.Extension(), ".svgz") + ? net::Filter::GZipFactory() : NULL; +} + bool URLRequestAsarJob::GetMimeType(std::string* mime_type) const { - return net::GetMimeTypeFromFile(file_path_, mime_type); + if (type_ == TYPE_ASAR) { + return net::GetMimeTypeFromFile(file_path_, mime_type); + } else { + if (meta_info_.mime_type_result) { + *mime_type = meta_info_.mime_type; + return true; + } + return false; + } +} + +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. + 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)); + } + } + } +} + +void URLRequestAsarJob::FetchMetaInfo(const base::FilePath& file_path, + FileMetaInfo* meta_info) { + base::File::Info file_info; + meta_info->file_exists = base::GetFileInfo(file_path, &file_info); + if (meta_info->file_exists) { + meta_info->file_size = file_info.size; + meta_info->is_directory = file_info.is_directory; + } + // On Windows GetMimeTypeFromFile() goes to the registry. Thus it should be + // done in WorkerPool. + meta_info->mime_type_result = + net::GetMimeTypeFromFile(file_path, &meta_info->mime_type); +} + +void URLRequestAsarJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) { + meta_info_ = *meta_info; + if (!meta_info_.file_exists || meta_info_.is_directory) { + DidOpen(net::ERR_FILE_NOT_FOUND); + return; + } + + int flags = base::File::FLAG_OPEN | + base::File::FLAG_READ | + base::File::FLAG_ASYNC; + int rv = stream_->Open(file_path_, flags, + base::Bind(&URLRequestAsarJob::DidOpen, + weak_ptr_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) + DidOpen(rv); } void URLRequestAsarJob::DidOpen(int result) { @@ -95,24 +264,57 @@ void URLRequestAsarJob::DidOpen(int result) { return; } - int rv = stream_->Seek(base::File::FROM_BEGIN, - file_info_.offset, - base::Bind(&URLRequestAsarJob::DidSeek, - weak_ptr_factory_.GetWeakPtr())); - if (rv != net::ERR_IO_PENDING) { - // stream_->Seek() failed, so pass an intentionally erroneous value - // into DidSeek(). - DidSeek(-1); + if (type_ == TYPE_ASAR) { + int rv = stream_->Seek(file_info_.offset, + base::Bind(&URLRequestAsarJob::DidSeek, + weak_ptr_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) { + // stream_->Seek() failed, so pass an intentionally erroneous value + // into DidSeek(). + DidSeek(-1); + } + } else { + if (!byte_range_.ComputeBounds(meta_info_.file_size)) { + NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + return; + } + + remaining_bytes_ = byte_range_.last_byte_position() - + byte_range_.first_byte_position() + 1; + + if (remaining_bytes_ > 0 && byte_range_.first_byte_position() != 0) { + int rv = stream_->Seek(byte_range_.first_byte_position(), + base::Bind(&URLRequestAsarJob::DidSeek, + weak_ptr_factory_.GetWeakPtr())); + if (rv != net::ERR_IO_PENDING) { + // stream_->Seek() failed, so pass an intentionally erroneous value + // into DidSeek(). + DidSeek(-1); + } + } else { + // We didn't need to call stream_->Seek() at all, so we pass to DidSeek() + // the value that would mean seek success. This way we skip the code + // handling seek failure. + DidSeek(byte_range_.first_byte_position()); + } } } void URLRequestAsarJob::DidSeek(int64 result) { - if (result != static_cast(file_info_.offset)) { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); - return; + if (type_ == TYPE_ASAR) { + if (result != static_cast(file_info_.offset)) { + NotifyDone(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)); + return; + } } - set_expected_content_size(remaining_bytes_); NotifyHeadersComplete(); } diff --git a/atom/browser/net/asar/url_request_asar_job.h b/atom/browser/net/asar/url_request_asar_job.h index adcac85b37d7..15a723d79e89 100644 --- a/atom/browser/net/asar/url_request_asar_job.h +++ b/atom/browser/net/asar/url_request_asar_job.h @@ -8,10 +8,12 @@ #include #include +#include "atom/browser/net/js_asker.h" #include "atom/common/asar/archive.h" #include "base/files/file_path.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" +#include "net/http/http_byte_range.h" #include "net/url_request/url_request_job.h" namespace base { @@ -34,11 +36,20 @@ net::URLRequestJob* CreateJobFromPath( class URLRequestAsarJob : public net::URLRequestJob { public: URLRequestAsarJob(net::URLRequest* request, - net::NetworkDelegate* network_delegate, - std::shared_ptr archive, - const base::FilePath& file_path, - const Archive::FileInfo& file_info, - const scoped_refptr& file_task_runner); + net::NetworkDelegate* network_delegate); + + void Initialize(const scoped_refptr file_task_runner, + const base::FilePath& file_path); + + protected: + virtual ~URLRequestAsarJob(); + + void InitializeAsarJob(const scoped_refptr file_task_runner, + std::shared_ptr archive, + const base::FilePath& file_path, + const Archive::FileInfo& file_info); + void InitializeFileJob(const scoped_refptr file_task_runner, + const base::FilePath& file_path); // net::URLRequestJob: void Start() override; @@ -46,12 +57,39 @@ class URLRequestAsarJob : public net::URLRequestJob { bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read) override; + bool IsRedirectResponse(GURL* location, int* http_status_code) override; + net::Filter* SetupFilter() const override; bool GetMimeType(std::string* mime_type) const override; - - protected: - virtual ~URLRequestAsarJob(); + void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override; private: + // Meta information about the file. It's used as a member in the + // URLRequestFileJob and also passed between threads because disk access is + // necessary to obtain it. + struct FileMetaInfo { + FileMetaInfo(); + + // Size of the file. + int64 file_size; + // Mime type associated with the file. + std::string mime_type; + // Result returned from GetMimeTypeFromFile(), i.e. flag showing whether + // obtaining of the mime type was successful. + bool mime_type_result; + // Flag showing whether the file exists. + bool file_exists; + // Flag showing whether the file name actually refers to a directory. + bool is_directory; + }; + + // Fetches file info on a background thread. + static void FetchMetaInfo(const base::FilePath& file_path, + FileMetaInfo* meta_info); + + // Callback after fetching file info on a background thread. + void DidFetchMetaInfo(const FileMetaInfo* meta_info); + + // Callback after opening file on a background thread. void DidOpen(int result); @@ -62,14 +100,24 @@ class URLRequestAsarJob : public net::URLRequestJob { // Callback after data is asynchronously read from the file into |buf|. void DidRead(scoped_refptr buf, int result); + // The type of this job. + enum JobType { + TYPE_ERROR, + TYPE_ASAR, + TYPE_FILE, + }; + JobType type_; + std::shared_ptr archive_; base::FilePath file_path_; Archive::FileInfo file_info_; scoped_ptr stream_; - int64 remaining_bytes_; + FileMetaInfo meta_info_; + scoped_refptr file_task_runner_; - const scoped_refptr file_task_runner_; + net::HttpByteRange byte_range_; + int64 remaining_bytes_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/atom/browser/net/atom_url_request_job_factory.cc b/atom/browser/net/atom_url_request_job_factory.cc index ed1fb97e2c1c..dbd8b4160cfd 100644 --- a/atom/browser/net/atom_url_request_job_factory.cc +++ b/atom/browser/net/atom_url_request_job_factory.cc @@ -6,9 +6,12 @@ #include "atom/browser/net/atom_url_request_job_factory.h" #include "base/stl_util.h" +#include "content/public/browser/browser_thread.h" #include "net/base/load_flags.h" #include "net/url_request/url_request.h" +using content::BrowserThread; + namespace atom { typedef net::URLRequestJobFactory::ProtocolHandler ProtocolHandler; @@ -20,12 +23,7 @@ AtomURLRequestJobFactory::~AtomURLRequestJobFactory() { } bool AtomURLRequestJobFactory::SetProtocolHandler( - const std::string& scheme, - ProtocolHandler* protocol_handler) { - DCHECK(CalledOnValidThread()); - - base::AutoLock locked(lock_); - + const std::string& scheme, scoped_ptr protocol_handler) { if (!protocol_handler) { ProtocolHandlerMap::iterator it = protocol_handler_map_.find(scheme); if (it == protocol_handler_map_.end()) @@ -38,29 +36,23 @@ bool AtomURLRequestJobFactory::SetProtocolHandler( if (ContainsKey(protocol_handler_map_, scheme)) return false; - protocol_handler_map_[scheme] = protocol_handler; + protocol_handler_map_[scheme] = protocol_handler.release(); return true; } -ProtocolHandler* AtomURLRequestJobFactory::ReplaceProtocol( - const std::string& scheme, - ProtocolHandler* protocol_handler) { - DCHECK(CalledOnValidThread()); - DCHECK(protocol_handler); - - base::AutoLock locked(lock_); +scoped_ptr AtomURLRequestJobFactory::ReplaceProtocol( + const std::string& scheme, scoped_ptr protocol_handler) { if (!ContainsKey(protocol_handler_map_, scheme)) return nullptr; ProtocolHandler* original_protocol_handler = protocol_handler_map_[scheme]; - protocol_handler_map_[scheme] = protocol_handler; - return original_protocol_handler; + protocol_handler_map_[scheme] = protocol_handler.release(); + return make_scoped_ptr(original_protocol_handler); } ProtocolHandler* AtomURLRequestJobFactory::GetProtocolHandler( const std::string& scheme) const { - DCHECK(CalledOnValidThread()); + DCHECK_CURRENTLY_ON(BrowserThread::IO); - base::AutoLock locked(lock_); ProtocolHandlerMap::const_iterator it = protocol_handler_map_.find(scheme); if (it == protocol_handler_map_.end()) return nullptr; @@ -69,7 +61,6 @@ ProtocolHandler* AtomURLRequestJobFactory::GetProtocolHandler( bool AtomURLRequestJobFactory::HasProtocolHandler( const std::string& scheme) const { - base::AutoLock locked(lock_); return ContainsKey(protocol_handler_map_, scheme); } @@ -77,9 +68,8 @@ net::URLRequestJob* AtomURLRequestJobFactory::MaybeCreateJobWithProtocolHandler( const std::string& scheme, net::URLRequest* request, net::NetworkDelegate* network_delegate) const { - DCHECK(CalledOnValidThread()); + DCHECK_CURRENTLY_ON(BrowserThread::IO); - base::AutoLock locked(lock_); ProtocolHandlerMap::const_iterator it = protocol_handler_map_.find(scheme); if (it == protocol_handler_map_.end()) return nullptr; @@ -101,7 +91,8 @@ net::URLRequestJob* AtomURLRequestJobFactory::MaybeInterceptResponse( bool AtomURLRequestJobFactory::IsHandledProtocol( const std::string& scheme) const { - DCHECK(CalledOnValidThread()); + DCHECK_CURRENTLY_ON(BrowserThread::IO); + return HasProtocolHandler(scheme) || net::URLRequest::IsHandledProtocol(scheme); } diff --git a/atom/browser/net/atom_url_request_job_factory.h b/atom/browser/net/atom_url_request_job_factory.h index a083e51483c7..dde36225b7af 100644 --- a/atom/browser/net/atom_url_request_job_factory.h +++ b/atom/browser/net/atom_url_request_job_factory.h @@ -10,8 +10,7 @@ #include #include -#include "base/basictypes.h" -#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" #include "base/synchronization/lock.h" #include "net/url_request/url_request_job_factory.h" @@ -25,13 +24,13 @@ class AtomURLRequestJobFactory : public net::URLRequestJobFactory { // Sets the ProtocolHandler for a scheme. Returns true on success, false on // failure (a ProtocolHandler already exists for |scheme|). On success, // URLRequestJobFactory takes ownership of |protocol_handler|. - bool SetProtocolHandler(const std::string& scheme, - ProtocolHandler* protocol_handler); + bool SetProtocolHandler( + const std::string& scheme, scoped_ptr protocol_handler); // Intercepts the ProtocolHandler for a scheme. Returns the original protocol // handler on success, otherwise returns NULL. - ProtocolHandler* ReplaceProtocol(const std::string& scheme, - ProtocolHandler* protocol_handler); + scoped_ptr ReplaceProtocol( + const std::string& scheme, scoped_ptr protocol_handler); // Returns the protocol handler registered with scheme. ProtocolHandler* GetProtocolHandler(const std::string& scheme) const; @@ -56,12 +55,10 @@ class AtomURLRequestJobFactory : public net::URLRequestJobFactory { bool IsSafeRedirectTarget(const GURL& location) const override; private: - typedef std::map ProtocolHandlerMap; + using ProtocolHandlerMap = std::map; ProtocolHandlerMap protocol_handler_map_; - mutable base::Lock lock_; - DISALLOW_COPY_AND_ASSIGN(AtomURLRequestJobFactory); }; diff --git a/atom/browser/net/http_protocol_handler.cc b/atom/browser/net/http_protocol_handler.cc new file mode 100644 index 000000000000..cf5fc01c0884 --- /dev/null +++ b/atom/browser/net/http_protocol_handler.cc @@ -0,0 +1,26 @@ +// 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 "atom/browser/net/http_protocol_handler.h" + +#include "net/url_request/url_request_http_job.h" + +namespace atom { + +HttpProtocolHandler::HttpProtocolHandler(const std::string& scheme) + : scheme_(scheme) { +} + +HttpProtocolHandler::~HttpProtocolHandler() { +} + +net::URLRequestJob* HttpProtocolHandler::MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const { + return net::URLRequestHttpJob::Factory(request, + network_delegate, + scheme_); +} + +} // namespace atom diff --git a/atom/browser/net/http_protocol_handler.h b/atom/browser/net/http_protocol_handler.h new file mode 100644 index 000000000000..98085374175b --- /dev/null +++ b/atom/browser/net/http_protocol_handler.h @@ -0,0 +1,30 @@ +// 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_BROWSER_NET_HTTP_PROTOCOL_HANDLER_H_ +#define ATOM_BROWSER_NET_HTTP_PROTOCOL_HANDLER_H_ + +#include + +#include "net/url_request/url_request_job_factory.h" + +namespace atom { + +class HttpProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { + public: + explicit HttpProtocolHandler(const std::string&); + virtual ~HttpProtocolHandler(); + + // net::URLRequestJobFactory::ProtocolHandler: + net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const override; + + private: + std::string scheme_; +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_HTTP_PROTOCOL_HANDLER_H_ diff --git a/atom/browser/net/js_asker.cc b/atom/browser/net/js_asker.cc new file mode 100644 index 000000000000..d838ae39638f --- /dev/null +++ b/atom/browser/net/js_asker.cc @@ -0,0 +1,131 @@ +// 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 "atom/browser/net/js_asker.h" + +#include + +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/v8_value_converter.h" +#include "native_mate/function_template.h" + +namespace atom { + +namespace internal { + +namespace { + +struct CallbackHolder { + ResponseCallback callback; +}; + +// Cached JavaScript version of |HandlerCallback|. +v8::Persistent g_handler_callback_; + +// The callback which is passed to |handler|. +void HandlerCallback(v8::Isolate* isolate, + v8::Local external, + v8::Local state, + mate::Arguments* args) { + // Check if the callback has already been called. + v8::Local called_symbol = mate::StringToSymbol(isolate, "called"); + if (state->Has(called_symbol)) + return; // no nothing + else + state->Set(called_symbol, v8::Boolean::New(isolate, true)); + + // If there is no argument passed then we failed. + scoped_ptr holder( + static_cast(external->Value())); + CHECK(holder); + v8::Local value; + if (!args->GetNext(&value)) { + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(holder->callback, false, nullptr)); + return; + } + + // Pass whatever user passed to the actaul request job. + V8ValueConverter converter; + v8::Local context = args->isolate()->GetCurrentContext(); + scoped_ptr options(converter.FromV8Value(value, context)); + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(holder->callback, true, base::Passed(&options))); +} + +// func.bind(func, arg1, arg2). +// NB(zcbenz): Using C++11 version crashes VS. +v8::Local BindFunctionWith(v8::Isolate* isolate, + v8::Local context, + v8::Local func, + v8::Local arg1, + v8::Local arg2) { + v8::MaybeLocal bind = func->Get(mate::StringToV8(isolate, "bind")); + CHECK(!bind.IsEmpty()); + v8::Local bind_func = + v8::Local::Cast(bind.ToLocalChecked()); + v8::Local converted[] = { func, arg1, arg2 }; + return bind_func->Call( + context, func, arraysize(converted), converted).ToLocalChecked(); +} + +// Generate the callback that will be passed to |handler|. +v8::MaybeLocal GenerateCallback(v8::Isolate* isolate, + v8::Local context, + const ResponseCallback& callback) { + // The FunctionTemplate is cached. + if (g_handler_callback_.IsEmpty()) + g_handler_callback_.Reset( + isolate, + mate::CreateFunctionTemplate(isolate, base::Bind(&HandlerCallback))); + + v8::Local handler_callback = + v8::Local::New(isolate, g_handler_callback_); + CallbackHolder* holder = new CallbackHolder; + holder->callback = callback; + return BindFunctionWith(isolate, context, handler_callback->GetFunction(), + v8::External::New(isolate, holder), + v8::Object::New(isolate)); +} + +} // namespace + +void AskForOptions(v8::Isolate* isolate, + const JavaScriptHandler& handler, + net::URLRequest* request, + 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); + // We don't convert the callback to C++ directly because creating + // FunctionTemplate will cause memory leak since V8 never releases it. So we + // have to create the function object in JavaScript to work around it. + v8::MaybeLocal wrapped_callback = GenerateCallback( + isolate, context, callback); + if (wrapped_callback.IsEmpty()) { + callback.Run(false, nullptr); + return; + } + handler.Run(request, wrapped_callback.ToLocalChecked()); +} + +bool IsErrorOptions(base::Value* value, int* error) { + if (value->IsType(base::Value::TYPE_DICTIONARY)) { + base::DictionaryValue* dict = static_cast(value); + if (dict->GetInteger("error", error)) + return true; + } else if (value->IsType(base::Value::TYPE_INTEGER)) { + if (value->GetAsInteger(error)) + return true; + } + return false; +} + +} // namespace internal + +} // namespace atom diff --git a/atom/browser/net/js_asker.h b/atom/browser/net/js_asker.h new file mode 100644 index 000000000000..8ec245ee8c4f --- /dev/null +++ b/atom/browser/net/js_asker.h @@ -0,0 +1,102 @@ +// 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_BROWSER_NET_JS_ASKER_H_ +#define ATOM_BROWSER_NET_JS_ASKER_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_job.h" +#include "v8/include/v8.h" + +namespace atom { + +using JavaScriptHandler = + base::Callback)>; + +namespace internal { + +using ResponseCallback = + base::Callback options)>; + +// Ask handler for options in UI thread. +void AskForOptions(v8::Isolate* isolate, + const JavaScriptHandler& handler, + net::URLRequest* request, + const ResponseCallback& callback); + +// Test whether the |options| means an error. +bool IsErrorOptions(base::Value* value, int* error); + +} // namespace internal + +template +class JsAsker : public RequestJob { + public: + JsAsker(net::URLRequest* request, net::NetworkDelegate* network_delegate) + : RequestJob(request, network_delegate), weak_factory_(this) {} + + // Called by |CustomProtocolHandler| to store handler related information. + void SetHandlerInfo( + v8::Isolate* isolate, + net::URLRequestContextGetter* request_context_getter, + const JavaScriptHandler& handler) { + isolate_ = isolate; + request_context_getter_ = request_context_getter; + handler_ = handler; + } + + // Subclass should do initailze work here. + virtual void StartAsync(scoped_ptr options) = 0; + + net::URLRequestContextGetter* request_context_getter() const { + return request_context_getter_; + } + + private: + // RequestJob: + void Start() override { + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&internal::AskForOptions, + isolate_, + handler_, + RequestJob::request(), + base::Bind(&JsAsker::OnResponse, + weak_factory_.GetWeakPtr()))); + } + void GetResponseInfo(net::HttpResponseInfo* info) override { + info->headers = new net::HttpResponseHeaders(""); + } + + // Called when the JS handler has sent the response, we need to decide whether + // to start, or fail the job. + void OnResponse(bool success, scoped_ptr value) { + int error = net::ERR_NOT_IMPLEMENTED; + if (success && value && !internal::IsErrorOptions(value.get(), &error)) { + StartAsync(value.Pass()); + } else { + RequestJob::NotifyStartError( + net::URLRequestStatus(net::URLRequestStatus::FAILED, error)); + } + } + + v8::Isolate* isolate_; + net::URLRequestContextGetter* request_context_getter_; + JavaScriptHandler handler_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(JsAsker); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_JS_ASKER_H_ diff --git a/atom/browser/net/url_request_async_asar_job.cc b/atom/browser/net/url_request_async_asar_job.cc new file mode 100644 index 000000000000..ba0189e5f6e0 --- /dev/null +++ b/atom/browser/net/url_request_async_asar_job.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/net/url_request_async_asar_job.h" + +namespace atom { + +UrlRequestAsyncAsarJob::UrlRequestAsyncAsarJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) + : JsAsker(request, network_delegate) { +} + +void UrlRequestAsyncAsarJob::StartAsync(scoped_ptr options) { + base::FilePath::StringType file_path; + if (options->IsType(base::Value::TYPE_DICTIONARY)) { + static_cast(options.get())->GetString( + "path", &file_path); + } else if (options->IsType(base::Value::TYPE_STRING)) { + options->GetAsString(&file_path); + } + + if (file_path.empty()) { + NotifyStartError(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_NOT_IMPLEMENTED)); + } else { + asar::URLRequestAsarJob::Initialize( + content::BrowserThread::GetBlockingPool()-> + GetTaskRunnerWithShutdownBehavior( + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN), + base::FilePath(file_path)); + asar::URLRequestAsarJob::Start(); + } +} + +} // namespace atom diff --git a/atom/browser/net/url_request_async_asar_job.h b/atom/browser/net/url_request_async_asar_job.h new file mode 100644 index 000000000000..748b96d84d28 --- /dev/null +++ b/atom/browser/net/url_request_async_asar_job.h @@ -0,0 +1,27 @@ +// 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_BROWSER_NET_URL_REQUEST_ASYNC_ASAR_JOB_H_ +#define ATOM_BROWSER_NET_URL_REQUEST_ASYNC_ASAR_JOB_H_ + +#include "atom/browser/net/asar/url_request_asar_job.h" +#include "atom/browser/net/js_asker.h" + +namespace atom { + +// Like URLRequestAsarJob, but asks the JavaScript handler for file path. +class UrlRequestAsyncAsarJob : public JsAsker { + public: + UrlRequestAsyncAsarJob(net::URLRequest*, net::NetworkDelegate*); + + // JsAsker: + void StartAsync(scoped_ptr options) override; + + private: + DISALLOW_COPY_AND_ASSIGN(UrlRequestAsyncAsarJob); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_URL_REQUEST_ASYNC_ASAR_JOB_H_ diff --git a/atom/browser/net/url_request_buffer_job.cc b/atom/browser/net/url_request_buffer_job.cc index 4aeb3284a4e9..affc3dd37d25 100644 --- a/atom/browser/net/url_request_buffer_job.cc +++ b/atom/browser/net/url_request_buffer_job.cc @@ -6,22 +6,58 @@ #include +#include "base/strings/string_number_conversions.h" #include "net/base/net_errors.h" namespace atom { -URLRequestBufferJob::URLRequestBufferJob(net::URLRequest* request, - net::NetworkDelegate* network_delegate, - const std::string& mime_type, - const std::string& charset, - v8::Local data) - : net::URLRequestSimpleJob(request, network_delegate), - mime_type_(mime_type), - charset_(charset), - buffer_data_(new base::RefCountedBytes()) { - auto input = reinterpret_cast(node::Buffer::Data(data)); - size_t length = node::Buffer::Length(data); - buffer_data_->data().assign(input, input + length); +URLRequestBufferJob::URLRequestBufferJob( + net::URLRequest* request, net::NetworkDelegate* network_delegate) + : JsAsker(request, network_delegate), + status_code_(net::HTTP_NOT_IMPLEMENTED) { +} + +void URLRequestBufferJob::StartAsync(scoped_ptr options) { + const base::BinaryValue* binary = nullptr; + if (options->IsType(base::Value::TYPE_DICTIONARY)) { + base::DictionaryValue* dict = + static_cast(options.get()); + dict->GetString("mimeType", &mime_type_); + dict->GetString("charset", &charset_); + dict->GetBinary("data", &binary); + } else if (options->IsType(base::Value::TYPE_BINARY)) { + options->GetAsBinary(&binary); + } + + if (!binary) { + NotifyStartError(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_NOT_IMPLEMENTED)); + return; + } + + data_ = new base::RefCountedBytes( + reinterpret_cast(binary->GetBuffer()), + binary->GetSize()); + status_code_ = net::HTTP_OK; + net::URLRequestSimpleJob::Start(); +} + +void URLRequestBufferJob::GetResponseInfo(net::HttpResponseInfo* info) { + std::string status("HTTP/1.1 "); + status.append(base::IntToString(status_code_)); + status.append(" "); + status.append(net::GetHttpReasonPhrase(status_code_)); + status.append("\0\0", 2); + net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + + if (!mime_type_.empty()) { + std::string content_type_header(net::HttpRequestHeaders::kContentType); + content_type_header.append(": "); + content_type_header.append(mime_type_); + headers->AddHeader(content_type_header); + } + + info->headers = headers; } int URLRequestBufferJob::GetRefCountedData( @@ -31,7 +67,7 @@ int URLRequestBufferJob::GetRefCountedData( const net::CompletionCallback& callback) const { *mime_type = mime_type_; *charset = charset_; - *data = buffer_data_; + *data = data_; return net::OK; } diff --git a/atom/browser/net/url_request_buffer_job.h b/atom/browser/net/url_request_buffer_job.h index 936d96409428..ab8de7e8f030 100644 --- a/atom/browser/net/url_request_buffer_job.h +++ b/atom/browser/net/url_request_buffer_job.h @@ -7,19 +7,22 @@ #include +#include "atom/browser/net/js_asker.h" #include "base/memory/ref_counted_memory.h" +#include "net/http/http_status_code.h" #include "net/url_request/url_request_simple_job.h" -#include "atom/common/node_includes.h" namespace atom { -class URLRequestBufferJob : public net::URLRequestSimpleJob { +class URLRequestBufferJob : public JsAsker { public: - URLRequestBufferJob(net::URLRequest* request, - net::NetworkDelegate* network_delegate, - const std::string& mime_type, - const std::string& charset, - v8::Local buffer); + URLRequestBufferJob(net::URLRequest*, net::NetworkDelegate*); + + // JsAsker: + void StartAsync(scoped_ptr options) override; + + // URLRequestJob: + void GetResponseInfo(net::HttpResponseInfo* info) override; // URLRequestSimpleJob: int GetRefCountedData(std::string* mime_type, @@ -30,7 +33,8 @@ class URLRequestBufferJob : public net::URLRequestSimpleJob { private: std::string mime_type_; std::string charset_; - scoped_refptr buffer_data_; + scoped_refptr data_; + net::HttpStatusCode status_code_; DISALLOW_COPY_AND_ASSIGN(URLRequestBufferJob); }; diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc new file mode 100644 index 000000000000..a8a16e286b30 --- /dev/null +++ b/atom/browser/net/url_request_fetch_job.cc @@ -0,0 +1,217 @@ +// 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 "atom/browser/net/url_request_fetch_job.h" + +#include +#include + +#include "base/strings/string_util.h" +#include "base/thread_task_runner_handle.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_builder.h" +#include "net/url_request/url_request_status.h" + +namespace atom { + +namespace { + +// Convert string to RequestType. +net::URLFetcher::RequestType GetRequestType(const std::string& raw) { + std::string method = base::StringToUpperASCII(raw); + if (method.empty() || method == "GET") + return net::URLFetcher::GET; + else if (method == "POST") + return net::URLFetcher::POST; + else if (method == "HEAD") + return net::URLFetcher::HEAD; + else if (method == "DELETE") + return net::URLFetcher::DELETE_REQUEST; + else if (method == "PUT") + return net::URLFetcher::PUT; + else if (method == "PATCH") + return net::URLFetcher::PATCH; + else // Use "GET" as fallback. + return net::URLFetcher::GET; +} + +// Pipe the response writer back to URLRequestFetchJob. +class ResponsePiper : public net::URLFetcherResponseWriter { + public: + explicit ResponsePiper(URLRequestFetchJob* job) + : first_write_(true), job_(job) {} + + // net::URLFetcherResponseWriter: + int Initialize(const net::CompletionCallback& callback) override { + return net::OK; + } + int Write(net::IOBuffer* buffer, + int num_bytes, + const net::CompletionCallback& callback) override { + if (first_write_) { + // The URLFetcherResponseWriter doesn't have an event when headers have + // been read, so we have to emulate by hooking to first write event. + job_->HeadersCompleted(); + first_write_ = false; + } + return job_->DataAvailable(buffer, num_bytes); + } + int Finish(const net::CompletionCallback& callback) override { + return net::OK; + } + + private: + bool first_write_; + URLRequestFetchJob* job_; + + DISALLOW_COPY_AND_ASSIGN(ResponsePiper); +}; + +} // namespace + +URLRequestFetchJob::URLRequestFetchJob( + net::URLRequest* request, net::NetworkDelegate* network_delegate) + : JsAsker(request, network_delegate), + pending_buffer_size_(0) { +} + +void URLRequestFetchJob::StartAsync(scoped_ptr options) { + if (!options->IsType(base::Value::TYPE_DICTIONARY)) { + NotifyStartError(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_NOT_IMPLEMENTED)); + return; + } + + std::string url, method, referrer; + base::Value* session = nullptr; + base::DictionaryValue* dict = + static_cast(options.get()); + dict->GetString("url", &url); + dict->GetString("method", &method); + dict->GetString("referrer", &referrer); + dict->Get("session", &session); + + // Check if URL is valid. + GURL formated_url(url); + if (!formated_url.is_valid()) { + NotifyStartError(net::URLRequestStatus( + net::URLRequestStatus::FAILED, net::ERR_INVALID_URL)); + return; + } + + // Use |request|'s method if |method| is not specified. + net::URLFetcher::RequestType request_type; + if (method.empty()) + request_type = GetRequestType(request()->method()); + else + request_type = GetRequestType(method); + + 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()); + else + fetcher_->SetRequestContext(request_context_getter()); + + // Use |request|'s referrer if |referrer| is not specified. + if (referrer.empty()) + fetcher_->SetReferrer(request()->referrer()); + else + fetcher_->SetReferrer(referrer); + + // Use |request|'s headers. + fetcher_->SetExtraRequestHeaders( + request()->extra_request_headers().ToString()); + + 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()); + url_request_context_getter_ = + new net::TrivialURLRequestContextGetter(builder.Build(), task_runner); + } + return url_request_context_getter_.get(); +} + +void URLRequestFetchJob::HeadersCompleted() { + response_info_.reset(new net::HttpResponseInfo); + response_info_->headers = fetcher_->GetResponseHeaders(); + NotifyHeadersComplete(); +} + +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()) + return net::ERR_IO_PENDING; + + // 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); + + // Clear the buffers before notifying the read is complete, so that it is + // safe for the observer to read. + pending_buffer_ = nullptr; + pending_buffer_size_ = 0; + + NotifyReadComplete(bytes_read); + return bytes_read; +} + +void URLRequestFetchJob::Kill() { + JsAsker::Kill(); + fetcher_.reset(); +} + +bool URLRequestFetchJob::ReadRawData(net::IOBuffer* dest, + int dest_size, + int* bytes_read) { + pending_buffer_ = dest; + pending_buffer_size_ = dest_size; + SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); + return false; +} + +bool URLRequestFetchJob::GetMimeType(std::string* mime_type) const { + if (!response_info_) + return false; + + return response_info_->headers->GetMimeType(mime_type); +} + +void URLRequestFetchJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (response_info_) + *info = *response_info_; +} + +int URLRequestFetchJob::GetResponseCode() const { + if (!response_info_) + return -1; + + return response_info_->headers->response_code(); +} + +void URLRequestFetchJob::OnURLFetchComplete(const net::URLFetcher* source) { + pending_buffer_ = nullptr; + pending_buffer_size_ = 0; + NotifyDone(fetcher_->GetStatus()); + if (fetcher_->GetStatus().is_success()) + NotifyReadComplete(0); +} + +} // namespace atom diff --git a/atom/browser/net/url_request_fetch_job.h b/atom/browser/net/url_request_fetch_job.h new file mode 100644 index 000000000000..189cebf01b18 --- /dev/null +++ b/atom/browser/net/url_request_fetch_job.h @@ -0,0 +1,59 @@ +// 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_BROWSER_NET_URL_REQUEST_FETCH_JOB_H_ +#define ATOM_BROWSER_NET_URL_REQUEST_FETCH_JOB_H_ + +#include + +#include "atom/browser/net/js_asker.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" + +namespace atom { + +class AtomBrowserContext; + +class URLRequestFetchJob : public JsAsker, + public net::URLFetcherDelegate { + public: + URLRequestFetchJob(net::URLRequest*, net::NetworkDelegate*); + + // Called by response writer. + void HeadersCompleted(); + int DataAvailable(net::IOBuffer* buffer, int num_bytes); + + protected: + // JsAsker: + void StartAsync(scoped_ptr options) override; + + // net::URLRequestJob: + void Kill() override; + bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) override; + bool GetMimeType(std::string* mime_type) const override; + void GetResponseInfo(net::HttpResponseInfo* info) override; + int GetResponseCode() const override; + + // net::URLFetcherDelegate: + void OnURLFetchComplete(const net::URLFetcher* source) override; + + private: + // Create a independent request context. + net::URLRequestContextGetter* CreateRequestContext(); + + scoped_refptr url_request_context_getter_; + scoped_ptr fetcher_; + scoped_refptr pending_buffer_; + int pending_buffer_size_; + scoped_ptr response_info_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestFetchJob); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_URL_REQUEST_FETCH_JOB_H_ diff --git a/atom/browser/net/url_request_string_job.cc b/atom/browser/net/url_request_string_job.cc index d0f205789a81..4a631b813eff 100644 --- a/atom/browser/net/url_request_string_job.cc +++ b/atom/browser/net/url_request_string_job.cc @@ -10,15 +10,36 @@ namespace atom { -URLRequestStringJob::URLRequestStringJob(net::URLRequest* request, - net::NetworkDelegate* network_delegate, - const std::string& mime_type, - const std::string& charset, - const std::string& data) - : net::URLRequestSimpleJob(request, network_delegate), - mime_type_(mime_type), - charset_(charset), - data_(data) { +URLRequestStringJob::URLRequestStringJob( + net::URLRequest* request, net::NetworkDelegate* network_delegate) + : JsAsker(request, network_delegate) { +} + +void URLRequestStringJob::StartAsync(scoped_ptr options) { + if (options->IsType(base::Value::TYPE_DICTIONARY)) { + base::DictionaryValue* dict = + static_cast(options.get()); + dict->GetString("mimeType", &mime_type_); + dict->GetString("charset", &charset_); + dict->GetString("data", &data_); + } else if (options->IsType(base::Value::TYPE_STRING)) { + options->GetAsString(&data_); + } + net::URLRequestSimpleJob::Start(); +} + +void URLRequestStringJob::GetResponseInfo(net::HttpResponseInfo* info) { + std::string status("HTTP/1.1 200 OK"); + net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + + if (!mime_type_.empty()) { + std::string content_type_header(net::HttpRequestHeaders::kContentType); + content_type_header.append(": "); + content_type_header.append(mime_type_); + headers->AddHeader(content_type_header); + } + + info->headers = headers; } int URLRequestStringJob::GetData( diff --git a/atom/browser/net/url_request_string_job.h b/atom/browser/net/url_request_string_job.h index 7ad250466aba..e40f0d93dab7 100644 --- a/atom/browser/net/url_request_string_job.h +++ b/atom/browser/net/url_request_string_job.h @@ -5,19 +5,22 @@ #ifndef ATOM_BROWSER_NET_URL_REQUEST_STRING_JOB_H_ #define ATOM_BROWSER_NET_URL_REQUEST_STRING_JOB_H_ -#include "net/url_request/url_request_simple_job.h" - #include +#include "atom/browser/net/js_asker.h" +#include "net/url_request/url_request_simple_job.h" + namespace atom { -class URLRequestStringJob : public net::URLRequestSimpleJob { +class URLRequestStringJob : public JsAsker { public: - URLRequestStringJob(net::URLRequest* request, - net::NetworkDelegate* network_delegate, - const std::string& mime_type, - const std::string& charset, - const std::string& data); + URLRequestStringJob(net::URLRequest*, net::NetworkDelegate*); + + // JsAsker: + void StartAsync(scoped_ptr options) override; + + // URLRequestJob: + void GetResponseInfo(net::HttpResponseInfo* info) override; // URLRequestSimpleJob: int GetData(std::string* mime_type, diff --git a/atom/browser/node_debugger.cc b/atom/browser/node_debugger.cc new file mode 100644 index 000000000000..da5602a88137 --- /dev/null +++ b/atom/browser/node_debugger.cc @@ -0,0 +1,209 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/node_debugger.h" + +#include + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/browser/browser_thread.h" +#include "net/test/embedded_test_server/tcp_listen_socket.h" + +#include "atom/common/node_includes.h" + +namespace atom { + +namespace { + +// NodeDebugger is stored in Isolate's data, slots 0, 1, 3 have already been +// taken by gin, blink and node, using 2 is a safe option for now. +const int kIsolateSlot = 2; + +const char* kContentLength = "Content-Length"; + +} // namespace + +NodeDebugger::NodeDebugger(v8::Isolate* isolate) + : isolate_(isolate), + thread_("NodeDebugger"), + content_length_(-1), + weak_factory_(this) { + bool use_debug_agent = false; + int port = 5858; + bool wait_for_connection = false; + + std::string port_str; + base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); + if (cmd->HasSwitch("debug")) { + use_debug_agent = true; + port_str = cmd->GetSwitchValueASCII("debug"); + } + if (cmd->HasSwitch("debug-brk")) { + use_debug_agent = true; + wait_for_connection = true; + port_str = cmd->GetSwitchValueASCII("debug-brk"); + } + + if (use_debug_agent) { + if (!port_str.empty()) + base::StringToInt(port_str, &port); + + isolate_->SetData(kIsolateSlot, this); + v8::Debug::SetMessageHandler(DebugMessageHandler); + + if (wait_for_connection) + v8::Debug::DebugBreak(isolate_); + + uv_async_init(uv_default_loop(), &weak_up_ui_handle_, ProcessMessageInUI); + + // Start a new IO thread. + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + if (!thread_.StartWithOptions(options)) { + LOG(ERROR) << "Unable to start debugger thread"; + return; + } + + // Start the server in new IO thread. + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&NodeDebugger::StartServer, weak_factory_.GetWeakPtr(), + port)); + } +} + +NodeDebugger::~NodeDebugger() { + thread_.Stop(); +} + +bool NodeDebugger::IsRunning() const { + return thread_.IsRunning(); +} + +void NodeDebugger::StartServer(int port) { + server_ = net::test_server::TCPListenSocket::CreateAndListen( + "127.0.0.1", port, this); + if (!server_) { + LOG(ERROR) << "Cannot start debugger server"; + return; + } +} + +void NodeDebugger::CloseSession() { + accepted_socket_.reset(); +} + +void NodeDebugger::OnMessage(const std::string& message) { + if (message.find("\"type\":\"request\",\"command\":\"disconnect\"}") != + std::string::npos) + CloseSession(); + + base::string16 message16 = base::UTF8ToUTF16(message); + v8::Debug::SendCommand( + isolate_, + reinterpret_cast(message16.data()), message16.size()); + + uv_async_send(&weak_up_ui_handle_); +} + +void NodeDebugger::SendMessage(const std::string& message) { + if (accepted_socket_) { + std::string header = base::StringPrintf( + "%s: %d\r\n\r\n", kContentLength, static_cast(message.size())); + accepted_socket_->Send(header); + accepted_socket_->Send(message); + } +} + +void NodeDebugger::SendConnectMessage() { + accepted_socket_->Send(base::StringPrintf( + "Type: connect\r\n" + "V8-Version: %s\r\n" + "Protocol-Version: 1\r\n" + "Embedding-Host: %s\r\n" + "%s: 0\r\n", + v8::V8::GetVersion(), ATOM_PRODUCT_NAME, kContentLength), true); +} + +// static +void NodeDebugger::ProcessMessageInUI(uv_async_t* handle) { + v8::Debug::ProcessDebugMessages(); +} + +// static +void NodeDebugger::DebugMessageHandler(const v8::Debug::Message& message) { + NodeDebugger* self = static_cast( + message.GetIsolate()->GetData(kIsolateSlot)); + + if (self) { + std::string message8(*v8::String::Utf8Value(message.GetJSON())); + self->thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&NodeDebugger::SendMessage, self->weak_factory_.GetWeakPtr(), + message8)); + } +} + +void NodeDebugger::DidAccept( + net::test_server::StreamListenSocket* server, + scoped_ptr socket) { + // Only accept one session. + if (accepted_socket_) { + socket->Send(std::string("Remote debugging session already active"), true); + return; + } + + accepted_socket_ = socket.Pass(); + SendConnectMessage(); +} + +void NodeDebugger::DidRead(net::test_server::StreamListenSocket* socket, + const char* data, + int len) { + buffer_.append(data, len); + + do { + if (buffer_.size() == 0) + return; + + // Read the "Content-Length" header. + if (content_length_ < 0) { + size_t pos = buffer_.find("\r\n\r\n"); + if (pos == std::string::npos) + return; + + // We can be sure that the header is "Content-Length: xxx\r\n". + std::string content_length = buffer_.substr(16, pos - 16); + if (!base::StringToInt(content_length, &content_length_)) { + DidClose(accepted_socket_.get()); + return; + } + + // Strip header from buffer. + buffer_ = buffer_.substr(pos + 4); + } + + // Read the message. + if (buffer_.size() >= static_cast(content_length_)) { + std::string message = buffer_.substr(0, content_length_); + buffer_ = buffer_.substr(content_length_); + + OnMessage(message); + + // Get ready for next message. + content_length_ = -1; + } + } while (true); +} + +void NodeDebugger::DidClose(net::test_server::StreamListenSocket* socket) { + // If we lost the connection, then simulate a disconnect msg: + OnMessage("{\"seq\":1,\"type\":\"request\",\"command\":\"disconnect\"}"); +} + +} // namespace atom diff --git a/atom/browser/node_debugger.h b/atom/browser/node_debugger.h new file mode 100644 index 000000000000..aedf7b2c0310 --- /dev/null +++ b/atom/browser/node_debugger.h @@ -0,0 +1,65 @@ +// Copyright (c) 2014 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NODE_DEBUGGER_H_ +#define ATOM_BROWSER_NODE_DEBUGGER_H_ + +#include + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread.h" +#include "net/test/embedded_test_server/stream_listen_socket.h" +#include "v8/include/v8-debug.h" +#include "vendor/node/deps/uv/include/uv.h" + +namespace atom { + +// Add support for node's "--debug" switch. +class NodeDebugger : public net::test_server::StreamListenSocket::Delegate { + public: + explicit NodeDebugger(v8::Isolate* isolate); + virtual ~NodeDebugger(); + + bool IsRunning() const; + + private: + void StartServer(int port); + void CloseSession(); + void OnMessage(const std::string& message); + void SendMessage(const std::string& message); + void SendConnectMessage(); + + static void ProcessMessageInUI(uv_async_t* handle); + + static void DebugMessageHandler(const v8::Debug::Message& message); + + // net::test_server::StreamListenSocket::Delegate: + void DidAccept( + net::test_server::StreamListenSocket* server, + scoped_ptr socket) override; + void DidRead(net::test_server::StreamListenSocket* socket, + const char* data, + int len) override; + void DidClose(net::test_server::StreamListenSocket* socket) override; + + v8::Isolate* isolate_; + + uv_async_t weak_up_ui_handle_; + + base::Thread thread_; + scoped_ptr server_; + scoped_ptr accepted_socket_; + + std::string buffer_; + int content_length_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(NodeDebugger); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NODE_DEBUGGER_H_ diff --git a/atom/browser/resources/mac/Info.plist b/atom/browser/resources/mac/Info.plist index 406dc84403f2..54ba3546a75f 100644 --- a/atom/browser/resources/mac/Info.plist +++ b/atom/browser/resources/mac/Info.plist @@ -17,7 +17,7 @@ CFBundleIconFile atom.icns CFBundleVersion - 0.26.0 + 0.33.0 LSMinimumSystemVersion 10.8.0 NSMainNibFile @@ -26,5 +26,7 @@ AtomApplication NSSupportsAutomaticGraphicsSwitching + NSHighResolutionCapable + diff --git a/atom/browser/resources/win/atom.manifest b/atom/browser/resources/win/atom.manifest new file mode 100644 index 000000000000..84970c717288 --- /dev/null +++ b/atom/browser/resources/win/atom.manifest @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atom/browser/resources/win/atom.rc b/atom/browser/resources/win/atom.rc index 2de44863443c..8863d8be7677 100644 --- a/atom/browser/resources/win/atom.rc +++ b/atom/browser/resources/win/atom.rc @@ -1,6 +1,12 @@ // Microsoft Visual C++ generated resource script. // +#include "grit\\ui_unscaled_resources.h" #include "resource.h" +#include +#ifdef IDC_STATIC +#undef IDC_STATIC +#endif +#define IDC_STATIC (-1) #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// @@ -50,8 +56,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,26,0,0 - PRODUCTVERSION 0,26,0,0 + FILEVERSION 0,33,0,0 + PRODUCTVERSION 0,33,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -68,12 +74,12 @@ BEGIN BEGIN VALUE "CompanyName", "GitHub, Inc." VALUE "FileDescription", "Electron" - VALUE "FileVersion", "0.26.0" + VALUE "FileVersion", "0.33.0" VALUE "InternalName", "electron.exe" VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved." VALUE "OriginalFilename", "electron.exe" VALUE "ProductName", "Electron" - VALUE "ProductVersion", "0.26.0" + VALUE "ProductVersion", "0.33.0" VALUE "SquirrelAwareVersion", "1" END END @@ -106,3 +112,28 @@ END IDR_MAINFRAME ICON "atom.ico" ///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +// +// Cursors +// +IDC_ALIAS CURSOR "ui\\resources\\cursors\\aliasb.cur" +IDC_CELL CURSOR "ui\\resources\\cursors\\cell.cur" +IDC_COLRESIZE CURSOR "ui\\resources\\cursors\\col_resize.cur" +IDC_COPYCUR CURSOR "ui\\resources\\cursors\\copy.cur" +IDC_CURSOR_NONE CURSOR "ui\\resources\\cursors\\none.cur" +IDC_HAND_GRAB CURSOR "ui\\resources\\cursors\\hand_grab.cur" +IDC_HAND_GRABBING CURSOR "ui\\resources\\cursors\\hand_grabbing.cur" +IDC_PAN_EAST CURSOR "ui\\resources\\cursors\\pan_east.cur" +IDC_PAN_MIDDLE CURSOR "ui\\resources\\cursors\\pan_middle.cur" +IDC_PAN_NORTH CURSOR "ui\\resources\\cursors\\pan_north.cur" +IDC_PAN_NORTH_EAST CURSOR "ui\\resources\\cursors\\pan_north_east.cur" +IDC_PAN_NORTH_WEST CURSOR "ui\\resources\\cursors\\pan_north_west.cur" +IDC_PAN_SOUTH CURSOR "ui\\resources\\cursors\\pan_south.cur" +IDC_PAN_SOUTH_EAST CURSOR "ui\\resources\\cursors\\pan_south_east.cur" +IDC_PAN_SOUTH_WEST CURSOR "ui\\resources\\cursors\\pan_south_west.cur" +IDC_PAN_WEST CURSOR "ui\\resources\\cursors\\pan_west.cur" +IDC_ROWRESIZE CURSOR "ui\\resources\\cursors\\row_resize.cur" +IDC_VERTICALTEXT CURSOR "ui\\resources\\cursors\\vertical_text.cur" +IDC_ZOOMIN CURSOR "ui\\resources\\cursors\\zoom_in.cur" +IDC_ZOOMOUT CURSOR "ui\\resources\\cursors\\zoom_out.cur" +///////////////////////////////////////////////////////////////////////////// diff --git a/atom/browser/ui/accelerator_util.cc b/atom/browser/ui/accelerator_util.cc index 87e56d063d7e..e25e14b7968c 100644 --- a/atom/browser/ui/accelerator_util.cc +++ b/atom/browser/ui/accelerator_util.cc @@ -9,6 +9,7 @@ #include #include +#include "atom/common/keyboad_util.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" @@ -17,74 +18,6 @@ namespace accelerator_util { -namespace { - -// Return key code of the char. -ui::KeyboardCode KeyboardCodeFromCharCode(char c, bool* shifted) { - *shifted = false; - switch (c) { - case 8: case 0x7F: return ui::VKEY_BACK; - case 9: return ui::VKEY_TAB; - case 0xD: case 3: 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; - } -} - -} // namespace - bool StringToAccelerator(const std::string& description, ui::Accelerator* accelerator) { if (!base::IsStringASCII(description)) { @@ -104,7 +37,7 @@ bool StringToAccelerator(const std::string& description, // to be correct and usually only uses few special tokens. if (tokens[i].size() == 1) { bool shifted = false; - key = KeyboardCodeFromCharCode(tokens[i][0], &shifted); + key = atom::KeyboardCodeFromCharCode(tokens[i][0], &shifted); if (shifted) modifiers |= ui::EF_SHIFT_DOWN; } else if (tokens[i] == "ctrl" || tokens[i] == "control") { @@ -152,9 +85,9 @@ bool StringToAccelerator(const std::string& description, key = ui::VKEY_HOME; } else if (tokens[i] == "end") { key = ui::VKEY_END; - } else if (tokens[i] == "pagedown") { - key = ui::VKEY_PRIOR; } 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; diff --git a/atom/browser/ui/atom_menu_model.cc b/atom/browser/ui/atom_menu_model.cc new file mode 100644 index 000000000000..9add7a22715e --- /dev/null +++ b/atom/browser/ui/atom_menu_model.cc @@ -0,0 +1,35 @@ +// 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 "atom/browser/ui/atom_menu_model.h" + +#include "base/stl_util.h" + +namespace atom { + +AtomMenuModel::AtomMenuModel(Delegate* delegate) + : ui::SimpleMenuModel(delegate), + delegate_(delegate) { +} + +AtomMenuModel::~AtomMenuModel() { +} + +void AtomMenuModel::SetRole(int index, const base::string16& role) { + roles_[index] = role; +} + +base::string16 AtomMenuModel::GetRoleAt(int index) { + if (ContainsKey(roles_, index)) + return roles_[index]; + else + return base::string16(); +} + +void AtomMenuModel::MenuClosed() { + ui::SimpleMenuModel::MenuClosed(); + FOR_EACH_OBSERVER(Observer, observers_, MenuClosed()); +} + +} // namespace atom diff --git a/atom/browser/ui/atom_menu_model.h b/atom/browser/ui/atom_menu_model.h new file mode 100644 index 000000000000..d091df9fb570 --- /dev/null +++ b/atom/browser/ui/atom_menu_model.h @@ -0,0 +1,53 @@ +// 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_BROWSER_UI_ATOM_MENU_MODEL_H_ +#define ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_ + +#include + +#include "base/observer_list.h" +#include "ui/base/models/simple_menu_model.h" + +namespace atom { + +class AtomMenuModel : public ui::SimpleMenuModel { + public: + class Delegate : public ui::SimpleMenuModel::Delegate { + public: + virtual ~Delegate() {} + }; + + class Observer { + public: + virtual ~Observer() {} + + // Notifies the menu has been closed. + virtual void MenuClosed() {} + }; + + explicit AtomMenuModel(Delegate* delegate); + virtual ~AtomMenuModel(); + + void AddObserver(Observer* obs) { observers_.AddObserver(obs); } + void RemoveObserver(Observer* obs) { observers_.RemoveObserver(obs); } + + void SetRole(int index, const base::string16& role); + base::string16 GetRoleAt(int index); + + // ui::SimpleMenuModel: + void MenuClosed() override; + + private: + Delegate* delegate_; // weak ref. + + std::map roles_; + base::ObserverList observers_; + + DISALLOW_COPY_AND_ASSIGN(AtomMenuModel); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_ diff --git a/atom/browser/ui/cocoa/atom_menu_controller.h b/atom/browser/ui/cocoa/atom_menu_controller.h index da10e1b0ba66..f8c48aa5dcb5 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.h +++ b/atom/browser/ui/cocoa/atom_menu_controller.h @@ -59,17 +59,4 @@ class MenuModel; @end -// Exposed only for unit testing, do not call directly. -@interface AtomMenuController (PrivateExposedForTesting) -- (BOOL)validateUserInterfaceItem:(id)item; -@end - -// Protected methods that subclassers can override. -@interface AtomMenuController (Protected) -- (void)addItemToMenu:(NSMenu*)menu - atIndex:(NSInteger)index - fromModel:(ui::MenuModel*)model; -- (NSMenu*)menuFromModel:(ui::MenuModel*)model; -@end - #endif // ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_ diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index 176f2db7e145..e3aa78aa248c 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -5,66 +5,40 @@ #import "atom/browser/ui/cocoa/atom_menu_controller.h" +#include "atom/browser/ui/atom_menu_model.h" #include "base/logging.h" #include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" #include "ui/base/accelerators/accelerator.h" #include "ui/base/accelerators/platform_accelerator_cocoa.h" #include "ui/base/l10n/l10n_util_mac.h" -#include "ui/base/models/simple_menu_model.h" +#include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/gfx/image/image.h" namespace { -bool isLeftButtonEvent(NSEvent* event) { - NSEventType type = [event type]; - return type == NSLeftMouseDown || - type == NSLeftMouseDragged || - type == NSLeftMouseUp; -} - -bool isRightButtonEvent(NSEvent* event) { - NSEventType type = [event type]; - return type == NSRightMouseDown || - type == NSRightMouseDragged || - type == NSRightMouseUp; -} - -bool isMiddleButtonEvent(NSEvent* event) { - if ([event buttonNumber] != 2) - return false; - - NSEventType type = [event type]; - return type == NSOtherMouseDown || - type == NSOtherMouseDragged || - type == NSOtherMouseUp; -} - -int EventFlagsFromNSEventWithModifiers(NSEvent* event, NSUInteger modifiers) { - int flags = 0; - flags |= (modifiers & NSAlphaShiftKeyMask) ? ui::EF_CAPS_LOCK_DOWN : 0; - flags |= (modifiers & NSShiftKeyMask) ? ui::EF_SHIFT_DOWN : 0; - flags |= (modifiers & NSControlKeyMask) ? ui::EF_CONTROL_DOWN : 0; - flags |= (modifiers & NSAlternateKeyMask) ? ui::EF_ALT_DOWN : 0; - flags |= (modifiers & NSCommandKeyMask) ? ui::EF_COMMAND_DOWN : 0; - flags |= isLeftButtonEvent(event) ? ui::EF_LEFT_MOUSE_BUTTON : 0; - flags |= isRightButtonEvent(event) ? ui::EF_RIGHT_MOUSE_BUTTON : 0; - flags |= isMiddleButtonEvent(event) ? ui::EF_MIDDLE_MOUSE_BUTTON : 0; - return flags; -} - -// Retrieves a bitsum of ui::EventFlags from NSEvent. -int EventFlagsFromNSEvent(NSEvent* event) { - NSUInteger modifiers = [event modifierFlags]; - return EventFlagsFromNSEventWithModifiers(event, modifiers); -} +struct Role { + SEL selector; + const char* role; +}; +Role kRolesMap[] = { + { @selector(orderFrontStandardAboutPanel:), "about" }, + { @selector(hide:), "hide" }, + { @selector(hideOtherApplications:), "hideothers" }, + { @selector(unhideAllApplications:), "unhide" }, + { @selector(arrangeInFront:), "front" }, + { @selector(undo:), "undo" }, + { @selector(redo:), "redo" }, + { @selector(cut:), "cut" }, + { @selector(copy:), "copy" }, + { @selector(paste:), "paste" }, + { @selector(selectAll:), "selectall" }, + { @selector(performMiniaturize:), "minimize" }, + { @selector(performClose:), "close" }, +}; } // namespace -@interface AtomMenuController (Private) -- (void)addSeparatorToMenu:(NSMenu*)menu - atIndex:(int)index; -@end - @implementation AtomMenuController @synthesize model = model_; @@ -147,7 +121,9 @@ int EventFlagsFromNSEvent(NSEvent* event) { // associated with the entry in the model identified by |modelIndex|. - (void)addItemToMenu:(NSMenu*)menu atIndex:(NSInteger)index - fromModel:(ui::MenuModel*)model { + fromModel:(ui::MenuModel*)ui_model { + atom::AtomMenuModel* model = static_cast(ui_model); + base::string16 label16 = model->GetLabelAt(index); NSString* label = l10n_util::FixUpWindowsStyleLabel(label16); base::scoped_nsobject item( @@ -166,18 +142,17 @@ int EventFlagsFromNSEvent(NSEvent* event) { [item setTarget:nil]; [item setAction:nil]; ui::MenuModel* submenuModel = model->GetSubmenuModelAt(index); - NSMenu* submenu = - [self menuFromModel:(ui::SimpleMenuModel*)submenuModel]; + NSMenu* submenu = [self menuFromModel:submenuModel]; [submenu setTitle:[item title]]; [item setSubmenu:submenu]; - // Hack to set window and help menu. - if ([[item title] isEqualToString:@"Window"] && [submenu numberOfItems] > 0) + // Set submenu's role. + base::string16 role = model->GetRoleAt(index); + if (role == base::ASCIIToUTF16("window")) [NSApp setWindowsMenu:submenu]; - else if ([[item title] isEqualToString:@"Help"]) + else if (role == base::ASCIIToUTF16("help")) [NSApp setHelpMenu:submenu]; - if ([[item title] isEqualToString:@"Services"] && - [submenu numberOfItems] == 0) + if (role == base::ASCIIToUTF16("services")) [NSApp setServicesMenu:submenu]; } else { // The MenuModel works on indexes so we can't just set the command id as the @@ -186,7 +161,6 @@ int EventFlagsFromNSEvent(NSEvent* event) { // model. Setting the target to |self| allows this class to participate // in validation of the menu items. [item setTag:index]; - [item setTarget:self]; NSValue* modelObject = [NSValue valueWithPointer:model]; [item setRepresentedObject:modelObject]; // Retains |modelObject|. ui::Accelerator accelerator; @@ -200,6 +174,19 @@ int EventFlagsFromNSEvent(NSEvent* event) { platformAccelerator->modifier_mask()]; } } + + // Set menu item's role. + base::string16 role = model->GetRoleAt(index); + if (role.empty()) { + [item setTarget:self]; + } else { + for (const Role& pair : kRolesMap) { + if (role == base::ASCIIToUTF16(pair.role)) { + [item setAction:pair.selector]; + break; + } + } + } } [menu insertItem:item atIndex:index]; } @@ -246,8 +233,9 @@ int EventFlagsFromNSEvent(NSEvent* event) { [[sender representedObject] pointerValue]); DCHECK(model); if (model) { - int event_flags = EventFlagsFromNSEvent([NSApp currentEvent]); - model->ActivatedAt(modelIndex, event_flags); + NSEvent* event = [NSApp currentEvent]; + model->ActivatedAt(modelIndex, + ui::EventFlagsFromModifiers([event modifierFlags])); } } diff --git a/atom/browser/ui/file_dialog_gtk.cc b/atom/browser/ui/file_dialog_gtk.cc index 0a74cf7551d7..5885ffe3611c 100644 --- a/atom/browser/ui/file_dialog_gtk.cc +++ b/atom/browser/ui/file_dialog_gtk.cc @@ -4,53 +4,25 @@ #include "atom/browser/ui/file_dialog.h" -#include -#include -#include - -// This conflicts with mate::Converter, -#undef True -#undef False -// and V8. -#undef None - #include "atom/browser/native_window.h" #include "base/callback.h" #include "base/files/file_util.h" #include "base/strings/string_util.h" #include "chrome/browser/ui/libgtk2ui/gtk2_signal.h" -#include "ui/aura/window.h" -#include "ui/aura/window_tree_host.h" +#include "chrome/browser/ui/libgtk2ui/gtk2_util.h" #include "ui/views/widget/desktop_aura/x11_desktop_handler.h" namespace file_dialog { namespace { -const char kAuraTransientParent[] = "aura-transient-parent"; - -void SetGtkTransientForAura(GtkWidget* dialog, aura::Window* parent) { - if (!parent || !parent->GetHost()) - return; - - gtk_widget_realize(dialog); - GdkWindow* gdk_window = gtk_widget_get_window(dialog); - - // TODO(erg): Check to make sure we're using X11 if wayland or some other - // display server ever happens. Otherwise, this will crash. - XSetTransientForHint(GDK_WINDOW_XDISPLAY(gdk_window), - GDK_WINDOW_XID(gdk_window), - parent->GetHost()->GetAcceleratedWidget()); - - // We also set the |parent| as a property of |dialog|, so that we can unlink - // the two later. - g_object_set_data(G_OBJECT(dialog), kAuraTransientParent, parent); -} - // Makes sure that .jpg also shows .JPG. gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info, std::string* file_extension) { - return EndsWith(file_info->filename, *file_extension, false); + // Makes .* file extension matches all file types. + if (*file_extension == ".*") + return true; + return base::EndsWith(file_info->filename, *file_extension, false); } // Deletes |data| when gtk_file_filter_add_custom() is done with it. @@ -65,7 +37,7 @@ class FileChooserDialog { const std::string& title, const base::FilePath& default_path, const Filters& filters) - : dialog_scope_(new atom::NativeWindow::DialogScope(parent_window)) { + : dialog_scope_(parent_window) { const char* confirm_text = GTK_STOCK_OK; if (action == GTK_FILE_CHOOSER_ACTION_SAVE) confirm_text = GTK_STOCK_SAVE; @@ -81,7 +53,7 @@ class FileChooserDialog { NULL); if (parent_window) { gfx::NativeWindow window = parent_window->GetNativeWindow(); - SetGtkTransientForAura(dialog_, window); + libgtk2ui::SetGtkTransientForAura(dialog_, window); } if (action == GTK_FILE_CHOOSER_ACTION_SAVE) @@ -93,12 +65,15 @@ class FileChooserDialog { gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE); if (!default_path.empty()) { - if (base::DirectoryExists(default_path)) + if (base::DirectoryExists(default_path)) { gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog_), default_path.value().c_str()); - else - gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog_), - default_path.value().c_str()); + } else { + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog_), + default_path.DirName().value().c_str()); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog_), + default_path.BaseName().value().c_str()); + } } if (!filters.empty()) @@ -159,13 +134,13 @@ class FileChooserDialog { private: void AddFilters(const Filters& filters); + atom::NativeWindow::DialogScope dialog_scope_; + GtkWidget* dialog_; SaveDialogCallback save_callback_; OpenDialogCallback open_callback_; - scoped_ptr dialog_scope_; - DISALLOW_COPY_AND_ASSIGN(FileChooserDialog); }; diff --git a/atom/browser/ui/file_dialog_mac.mm b/atom/browser/ui/file_dialog_mac.mm index 9cd6c0380be6..1cbe46e3b55b 100644 --- a/atom/browser/ui/file_dialog_mac.mm +++ b/atom/browser/ui/file_dialog_mac.mm @@ -18,25 +18,17 @@ namespace file_dialog { namespace { -CFStringRef CreateUTIFromExtension(const std::string& ext) { - base::ScopedCFTypeRef ext_cf(base::SysUTF8ToCFStringRef(ext)); - return UTTypeCreatePreferredIdentifierForTag( - kUTTagClassFilenameExtension, ext_cf.get(), NULL); -} - void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) { NSMutableSet* file_type_set = [NSMutableSet set]; for (size_t i = 0; i < filters.size(); ++i) { const Filter& filter = filters[i]; for (size_t j = 0; j < filter.second.size(); ++j) { - base::ScopedCFTypeRef uti( - CreateUTIFromExtension(filter.second[j])); - [file_type_set addObject:base::mac::CFToNSCast(uti.get())]; - - // Always allow the extension itself, in case the UTI doesn't map - // back to the original extension correctly. This occurs with dynamic - // UTIs on 10.7 and 10.8. - // See http://crbug.com/148840, http://openradar.me/12316273 + // If we meet a '*' file extension, we allow all the file types and no + // need to set the specified file types. + if (filter.second[j] == "*") { + [dialog setAllowsOtherFileTypes:YES]; + return; + } base::ScopedCFTypeRef ext_cf( base::SysUTF8ToCFStringRef(filter.second[j])); [file_type_set addObject:base::mac::CFToNSCast(ext_cf.get())]; diff --git a/atom/browser/ui/file_dialog_win.cc b/atom/browser/ui/file_dialog_win.cc index 4656557bf337..b169471c6e99 100644 --- a/atom/browser/ui/file_dialog_win.cc +++ b/atom/browser/ui/file_dialog_win.cc @@ -120,12 +120,13 @@ struct RunState { }; bool CreateDialogThread(RunState* run_state) { - base::Thread* thread = new base::Thread(ATOM_PRODUCT_NAME "FileDialogThread"); + scoped_ptr thread( + new base::Thread(ATOM_PRODUCT_NAME "FileDialogThread")); thread->init_com_with_mta(false); if (!thread->Start()) return false; - run_state->dialog_thread = thread; + run_state->dialog_thread = thread.release(); run_state->ui_message_loop = base::MessageLoop::current(); return true; } @@ -251,7 +252,7 @@ bool ShowSaveDialog(atom::NativeWindow* parent_window, bool matched = false; for (size_t i = 0; i < filter.second.size(); ++i) { - if (EndsWith(file_name, filter.second[i], false)) { + if (base::EndsWith(file_name, filter.second[i], false)) { matched = true; break;; } diff --git a/atom/browser/ui/message_box.h b/atom/browser/ui/message_box.h index 04792085b7ae..92052d3de4a4 100644 --- a/atom/browser/ui/message_box.h +++ b/atom/browser/ui/message_box.h @@ -22,7 +22,14 @@ class NativeWindow; enum MessageBoxType { MESSAGE_BOX_TYPE_NONE = 0, MESSAGE_BOX_TYPE_INFORMATION, - MESSAGE_BOX_TYPE_WARNING + MESSAGE_BOX_TYPE_WARNING, + MESSAGE_BOX_TYPE_ERROR, + MESSAGE_BOX_TYPE_QUESTION, +}; + +enum MessageBoxOptions { + MESSAGE_BOX_NONE = 0, + MESSAGE_BOX_NO_LINK = 1 << 0, }; typedef base::Callback MessageBoxCallback; @@ -30,6 +37,8 @@ typedef base::Callback MessageBoxCallback; int ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, + int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, @@ -38,6 +47,8 @@ int ShowMessageBox(NativeWindow* parent_window, void ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, + int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, diff --git a/atom/browser/ui/message_box_gtk.cc b/atom/browser/ui/message_box_gtk.cc new file mode 100644 index 000000000000..41682190e60e --- /dev/null +++ b/atom/browser/ui/message_box_gtk.cc @@ -0,0 +1,205 @@ +// 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 "atom/browser/ui/message_box.h" + +#include "atom/browser/browser.h" +#include "atom/browser/native_window.h" +#include "base/callback.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h" +#include "chrome/browser/ui/libgtk2ui/gtk2_util.h" +#include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h" +#include "ui/views/widget/desktop_aura/x11_desktop_handler.h" + +#define ANSI_FOREGROUND_RED "\x1b[31m" +#define ANSI_FOREGROUND_BLACK "\x1b[30m" +#define ANSI_TEXT_BOLD "\x1b[1m" +#define ANSI_BACKGROUND_GRAY "\x1b[47m" +#define ANSI_RESET "\x1b[0m" + +namespace atom { + +namespace { + +class GtkMessageBox { + public: + GtkMessageBox(NativeWindow* parent_window, + MessageBoxType type, + const std::vector& buttons, + int cancel_id, + const std::string& title, + const std::string& message, + const std::string& detail, + const gfx::ImageSkia& icon) + : dialog_scope_(parent_window), + cancel_id_(cancel_id) { + // Create dialog. + dialog_ = gtk_message_dialog_new( + nullptr, // parent + static_cast(0), // no flags + GetMessageType(type), // type + GTK_BUTTONS_NONE, // no buttons + "%s", message.c_str()); + if (!detail.empty()) + gtk_message_dialog_format_secondary_text( + GTK_MESSAGE_DIALOG(dialog_), "%s", detail.c_str()); + if (!title.empty()) + gtk_window_set_title(GTK_WINDOW(dialog_), title.c_str()); + + // Set dialog's icon. + if (!icon.isNull()) { + GdkPixbuf* pixbuf = libgtk2ui::GdkPixbufFromSkBitmap(*icon.bitmap()); + GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf); + gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog_), image); + gtk_widget_show(image); + g_object_unref(pixbuf); + } + + // Add buttons. + for (size_t i = 0; i < buttons.size(); ++i) { + gtk_dialog_add_button(GTK_DIALOG(dialog_), + TranslateToStock(i, buttons[i]), + i); + } + + // Parent window. + if (parent_window) { + gfx::NativeWindow window = parent_window->GetNativeWindow(); + libgtk2ui::SetGtkTransientForAura(dialog_, window); + } + } + + ~GtkMessageBox() { + gtk_widget_destroy(dialog_); + } + + GtkMessageType GetMessageType(MessageBoxType type) { + switch (type) { + case MESSAGE_BOX_TYPE_INFORMATION: + return GTK_MESSAGE_INFO; + case MESSAGE_BOX_TYPE_WARNING: + return GTK_MESSAGE_WARNING; + case MESSAGE_BOX_TYPE_QUESTION: + return GTK_MESSAGE_QUESTION; + case MESSAGE_BOX_TYPE_ERROR: + return GTK_MESSAGE_ERROR; + default: + return GTK_MESSAGE_OTHER; + } + } + + const char* TranslateToStock(int id, const std::string& text) { + std::string lower = base::StringToLowerASCII(text); + if (lower == "cancel") + return GTK_STOCK_CANCEL; + else if (lower == "no") + return GTK_STOCK_NO; + else if (lower == "ok") + return GTK_STOCK_OK; + else if (lower == "yes") + return GTK_STOCK_YES; + else + return text.c_str(); + } + + void Show() { + gtk_widget_show_all(dialog_); + // We need to call gtk_window_present after making the widgets visible to + // make sure window gets correctly raised and gets focus. + int time = views::X11DesktopHandler::get()->wm_user_time_ms(); + gtk_window_present_with_time(GTK_WINDOW(dialog_), time); + } + + int RunSynchronous() { + gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE); + Show(); + int response = gtk_dialog_run(GTK_DIALOG(dialog_)); + if (response < 0) + return cancel_id_; + else + return response; + } + + void RunAsynchronous(const MessageBoxCallback& callback) { + callback_ = callback; + g_signal_connect(dialog_, "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), nullptr); + g_signal_connect(dialog_, "response", + G_CALLBACK(OnResponseDialogThunk), this); + Show(); + } + + CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, int); + + private: + atom::NativeWindow::DialogScope dialog_scope_; + + // The id to return when the dialog is closed without pressing buttons. + int cancel_id_; + + GtkWidget* dialog_; + MessageBoxCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(GtkMessageBox); +}; + +void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) { + gtk_widget_hide_all(dialog_); + + if (response < 0) + callback_.Run(cancel_id_); + else + callback_.Run(response); + delete this; +} + +} // namespace + +int ShowMessageBox(NativeWindow* parent, + MessageBoxType type, + const std::vector& buttons, + int cancel_id, + int options, + const std::string& title, + const std::string& message, + const std::string& detail, + const gfx::ImageSkia& icon) { + return GtkMessageBox(parent, type, buttons, cancel_id, title, message, detail, + icon).RunSynchronous(); +} + +void ShowMessageBox(NativeWindow* parent, + MessageBoxType type, + const std::vector& buttons, + int cancel_id, + int options, + const std::string& title, + const std::string& message, + const std::string& detail, + const gfx::ImageSkia& icon, + const MessageBoxCallback& callback) { + (new GtkMessageBox(parent, type, buttons, cancel_id, title, message, detail, + icon))->RunAsynchronous(callback); +} + +void ShowErrorBox(const base::string16& title, const base::string16& content) { + if (Browser::Get()->is_ready()) { + GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, { "OK" }, 0, "Error", + base::UTF16ToUTF8(title).c_str(), + base::UTF16ToUTF8(content).c_str(), + gfx::ImageSkia()).RunSynchronous(); + } else { + fprintf(stderr, + ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY + ANSI_FOREGROUND_RED "%s\n" + ANSI_FOREGROUND_BLACK "%s" + ANSI_RESET "\n", + base::UTF16ToUTF8(title).c_str(), + base::UTF16ToUTF8(content).c_str()); + } +} + +} // namespace atom diff --git a/atom/browser/ui/message_box_mac.mm b/atom/browser/ui/message_box_mac.mm index b3af25311eaf..e518af653da4 100644 --- a/atom/browser/ui/message_box_mac.mm +++ b/atom/browser/ui/message_box_mac.mm @@ -94,6 +94,8 @@ void SetReturnCode(int* ret_code, int result) { int ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, + int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, @@ -125,6 +127,8 @@ int ShowMessageBox(NativeWindow* parent_window, void ShowMessageBox(NativeWindow* parent_window, MessageBoxType type, const std::vector& buttons, + int cancel_id, + int options, const std::string& title, const std::string& message, const std::string& detail, diff --git a/atom/browser/ui/message_box_views.cc b/atom/browser/ui/message_box_views.cc deleted file mode 100644 index 6e1cc9daa2ba..000000000000 --- a/atom/browser/ui/message_box_views.cc +++ /dev/null @@ -1,417 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/browser/ui/message_box.h" - -#if defined(USE_X11) -#include -#endif - -#include "atom/browser/native_window.h" -#include "base/callback.h" -#include "base/message_loop/message_loop.h" -#include "base/run_loop.h" -#include "base/strings/string_util.h" -#include "base/strings/string16.h" -#include "base/strings/utf_string_conversions.h" -#include "ui/views/background.h" -#include "ui/views/controls/button/label_button.h" -#include "ui/views/controls/message_box_view.h" -#include "ui/views/layout/grid_layout.h" -#include "ui/views/layout/layout_constants.h" -#include "ui/views/bubble/bubble_border.h" -#include "ui/views/bubble/bubble_frame_view.h" -#include "ui/views/widget/widget.h" -#include "ui/views/widget/widget_delegate.h" -#include "ui/wm/core/shadow_types.h" - -#if defined(USE_X11) -#include "atom/browser/browser.h" -#include "ui/views/window/native_frame_view.h" -#endif - -#if defined(OS_WIN) -#include "ui/base/win/message_box_win.h" -#endif - -#define ANSI_FOREGROUND_RED "\x1b[31m" -#define ANSI_FOREGROUND_BLACK "\x1b[30m" -#define ANSI_TEXT_BOLD "\x1b[1m" -#define ANSI_BACKGROUND_GRAY "\x1b[47m" -#define ANSI_RESET "\x1b[0m" - -namespace atom { - -namespace { - -// The group used by the buttons. This name is chosen voluntarily big not to -// conflict with other groups that could be in the dialog content. -const int kButtonGroup = 1127; - -class MessageDialogClientView; - -class MessageDialog : public views::WidgetDelegate, - public views::View, - public views::ButtonListener { - public: - MessageDialog(NativeWindow* parent_window, - MessageBoxType type, - const std::vector& buttons, - const std::string& title, - const std::string& message, - const std::string& detail, - const gfx::ImageSkia& icon); - virtual ~MessageDialog(); - - void Show(base::RunLoop* run_loop = NULL); - void Close(); - - int GetResult() const; - - void set_callback(const MessageBoxCallback& callback) { - delete_on_close_ = true; - callback_ = callback; - } - - private: - // Overridden from views::WidgetDelegate: - base::string16 GetWindowTitle() const override; - gfx::ImageSkia GetWindowAppIcon() override; - gfx::ImageSkia GetWindowIcon() override; - bool ShouldShowWindowIcon() const override; - views::Widget* GetWidget() override; - const views::Widget* GetWidget() const override; - views::View* GetContentsView() override; - views::View* GetInitiallyFocusedView() override; - ui::ModalType GetModalType() const override; - views::NonClientFrameView* CreateNonClientFrameView( - views::Widget* widget) override; - views::ClientView* CreateClientView(views::Widget* widget) override; - - // Overridden from views::View: - gfx::Size GetPreferredSize() const override; - void Layout() override; - bool AcceleratorPressed(const ui::Accelerator& accelerator) override; - - // Overridden from views::ButtonListener: - void ButtonPressed(views::Button* sender, const ui::Event& event) override; - - gfx::ImageSkia icon_; - - bool delete_on_close_; - int result_; - base::string16 title_; - - NativeWindow* parent_; - scoped_ptr widget_; - views::MessageBoxView* message_box_view_; - std::vector buttons_; - - base::RunLoop* run_loop_; - scoped_ptr dialog_scope_; - MessageBoxCallback callback_; - - DISALLOW_COPY_AND_ASSIGN(MessageDialog); -}; - -class MessageDialogClientView : public views::ClientView { - public: - MessageDialogClientView(MessageDialog* dialog, views::Widget* widget) - : views::ClientView(widget, dialog), - dialog_(dialog) { - } - - // views::ClientView: - bool CanClose() override { - dialog_->Close(); - return false; - } - - private: - MessageDialog* dialog_; - - DISALLOW_COPY_AND_ASSIGN(MessageDialogClientView); -}; - -//////////////////////////////////////////////////////////////////////////////// -// MessageDialog, public: - -MessageDialog::MessageDialog(NativeWindow* parent_window, - MessageBoxType type, - const std::vector& buttons, - const std::string& title, - const std::string& message, - const std::string& detail, - const gfx::ImageSkia& icon) - : icon_(icon), - delete_on_close_(false), - result_(-1), - title_(base::UTF8ToUTF16(title)), - parent_(parent_window), - message_box_view_(NULL), - run_loop_(NULL), - dialog_scope_(new NativeWindow::DialogScope(parent_window)) { - DCHECK_GT(buttons.size(), 0u); - set_owned_by_client(); - - if (!parent_) - set_background(views::Background::CreateStandardPanelBackground()); - - std::string content = message + "\n" + detail; - views::MessageBoxView::InitParams box_params(base::UTF8ToUTF16(content)); - message_box_view_ = new views::MessageBoxView(box_params); - AddChildView(message_box_view_); - - for (size_t i = 0; i < buttons.size(); ++i) { - views::LabelButton* button = new views::LabelButton( - this, base::UTF8ToUTF16(buttons[i])); - button->set_tag(i); - button->SetMinSize(gfx::Size(60, 30)); - button->SetStyle(views::Button::STYLE_BUTTON); - button->SetGroup(kButtonGroup); - - buttons_.push_back(button); - AddChildView(button); - } - - // First button is always default button. - buttons_[0]->SetIsDefault(true); - buttons_[0]->AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); - - views::Widget::InitParams params; - params.delegate = this; - params.type = views::Widget::InitParams::TYPE_WINDOW; - params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - if (parent_) { - params.parent = parent_->GetNativeWindow(); - params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; - // Use bubble style for dialog has a parent. - params.remove_standard_frame = true; - } - - widget_.reset(new views::Widget); - widget_->Init(params); - widget_->UpdateWindowIcon(); - - // Bind to ESC. - AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE)); -} - -MessageDialog::~MessageDialog() { -} - -void MessageDialog::Show(base::RunLoop* run_loop) { - run_loop_ = run_loop; - widget_->Show(); -} - -void MessageDialog::Close() { - dialog_scope_.reset(); - - if (delete_on_close_) { - callback_.Run(GetResult()); - base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); - } else if (run_loop_) { - run_loop_->Quit(); - } -} - -int MessageDialog::GetResult() const { - // When the dialog is closed without choosing anything, we think the user - // chose 'Cancel', otherwise we think the default behavior is chosen. - if (result_ == -1) { - for (size_t i = 0; i < buttons_.size(); ++i) - if (LowerCaseEqualsASCII(buttons_[i]->GetText(), "cancel")) { - return i; - } - - return 0; - } else { - return result_; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// MessageDialog, private: - -base::string16 MessageDialog::GetWindowTitle() const { - return title_; -} - -gfx::ImageSkia MessageDialog::GetWindowAppIcon() { - return icon_; -} - -gfx::ImageSkia MessageDialog::GetWindowIcon() { - return icon_; -} - -bool MessageDialog::ShouldShowWindowIcon() const { - return true; -} - -views::Widget* MessageDialog::GetWidget() { - return widget_.get(); -} - -const views::Widget* MessageDialog::GetWidget() const { - return widget_.get(); -} - -views::View* MessageDialog::GetContentsView() { - return this; -} - -views::View* MessageDialog::GetInitiallyFocusedView() { - if (buttons_.size() > 0) - return buttons_[0]; - else - return this; -} - -ui::ModalType MessageDialog::GetModalType() const { - return ui::MODAL_TYPE_SYSTEM; -} - -views::NonClientFrameView* MessageDialog::CreateNonClientFrameView( - views::Widget* widget) { - if (!parent_) { -#if defined(USE_X11) - return new views::NativeFrameView(widget); -#else - return NULL; -#endif - } - - // Create a bubble style frame like Chrome. - views::BubbleFrameView* frame = new views::BubbleFrameView(gfx::Insets()); - const SkColor color = widget->GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_DialogBackground); - scoped_ptr border(new views::BubbleBorder( - views::BubbleBorder::FLOAT, views::BubbleBorder::SMALL_SHADOW, color)); - frame->SetBubbleBorder(border.Pass()); - wm::SetShadowType(widget->GetNativeWindow(), wm::SHADOW_TYPE_NONE); - return frame; -} - -views::ClientView* MessageDialog::CreateClientView(views::Widget* widget) { - return new MessageDialogClientView(this, widget); -} - -gfx::Size MessageDialog::GetPreferredSize() const { - gfx::Size size(0, buttons_[0]->GetPreferredSize().height()); - for (size_t i = 0; i < buttons_.size(); ++i) - size.Enlarge(buttons_[i]->GetPreferredSize().width(), 0); - - // Button spaces. - size.Enlarge(views::kRelatedButtonHSpacing * (buttons_.size() - 1), - views::kRelatedControlVerticalSpacing); - - // The message box view. - gfx::Size contents_size = message_box_view_->GetPreferredSize(); - size.Enlarge(0, contents_size.height()); - if (contents_size.width() > size.width()) - size.set_width(contents_size.width()); - - return size; -} - -void MessageDialog::Layout() { - gfx::Rect bounds = GetContentsBounds(); - - // Layout the row containing the buttons. - int x = bounds.width(); - int height = buttons_[0]->GetPreferredSize().height() + - views::kRelatedControlVerticalSpacing; - - // NB: We iterate through the buttons backwards here because - // Mac and Windows buttons are laid out in opposite order. - for (int i = buttons_.size() - 1; i >= 0; --i) { - gfx::Size size = buttons_[i]->GetPreferredSize(); - x -= size.width() + views::kRelatedButtonHSpacing; - - buttons_[i]->SetBounds(x, bounds.height() - height, - size.width(), size.height()); - } - - // Layout the message box view. - message_box_view_->SetBounds(bounds.x(), bounds.y(), bounds.width(), - bounds.height() - height); -} - -bool MessageDialog::AcceleratorPressed(const ui::Accelerator& accelerator) { - DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE); - widget_->Close(); - return true; -} - -void MessageDialog::ButtonPressed(views::Button* sender, - const ui::Event& event) { - result_ = sender->tag(); - widget_->Close(); -} - -} // namespace - -int ShowMessageBox(NativeWindow* parent_window, - MessageBoxType type, - const std::vector& buttons, - const std::string& title, - const std::string& message, - const std::string& detail, - const gfx::ImageSkia& icon) { - MessageDialog dialog( - parent_window, type, buttons, title, message, detail, icon); - { - base::MessageLoop::ScopedNestableTaskAllower allow( - base::MessageLoopForUI::current()); - base::RunLoop run_loop; - dialog.Show(&run_loop); - run_loop.Run(); - } - - return dialog.GetResult(); -} - -void ShowMessageBox(NativeWindow* parent_window, - MessageBoxType type, - const std::vector& buttons, - const std::string& title, - const std::string& message, - const std::string& detail, - const gfx::ImageSkia& icon, - const MessageBoxCallback& callback) { - // The dialog would be deleted when the dialog is closed. - MessageDialog* dialog = new MessageDialog( - parent_window, type, buttons, title, message, detail, icon); - dialog->set_callback(callback); - dialog->Show(); -} - -void ShowErrorBox(const base::string16& title, const base::string16& content) { -#if defined(OS_WIN) - ui::MessageBox(NULL, content, title, MB_OK | MB_ICONERROR | MB_TASKMODAL); -#elif defined(USE_X11) - if (Browser::Get()->is_ready()) { - GtkWidget* dialog = gtk_message_dialog_new( - NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "%s", base::UTF16ToUTF8(title).c_str()); - gtk_message_dialog_format_secondary_text( - GTK_MESSAGE_DIALOG(dialog), - "%s", base::UTF16ToUTF8(content).c_str()); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - } else { - fprintf(stderr, - ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY - ANSI_FOREGROUND_RED "%s\n" - ANSI_FOREGROUND_BLACK "%s" - ANSI_RESET "\n", - base::UTF16ToUTF8(title).c_str(), - base::UTF16ToUTF8(content).c_str()); - } -#endif -} - -} // namespace atom diff --git a/atom/browser/ui/message_box_win.cc b/atom/browser/ui/message_box_win.cc new file mode 100644 index 000000000000..697a7ad410a2 --- /dev/null +++ b/atom/browser/ui/message_box_win.cc @@ -0,0 +1,236 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/message_box.h" + +#include +#include + +#include +#include + +#include "atom/browser/browser.h" +#include "atom/browser/native_window_views.h" +#include "base/callback.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread.h" +#include "base/win/scoped_gdi_object.h" +#include "content/public/browser/browser_thread.h" +#include "ui/gfx/icon_util.h" + +namespace atom { + +namespace { + +// Small command ID values are already taken by Windows, we have to start from +// a large number to avoid conflicts with Windows. +const int kIDStart = 100; + +// Get the common ID from button's name. +struct CommonButtonID { + int button; + int id; +}; +CommonButtonID GetCommonID(const base::string16& button) { + base::string16 lower = base::StringToLowerASCII(button); + if (lower == L"ok") + return { TDCBF_OK_BUTTON, IDOK }; + else if (lower == L"yes") + return { TDCBF_YES_BUTTON, IDYES }; + else if (lower == L"no") + return { TDCBF_NO_BUTTON, IDNO }; + else if (lower == L"cancel") + return { TDCBF_CANCEL_BUTTON, IDCANCEL }; + else if (lower == L"retry") + return { TDCBF_RETRY_BUTTON, IDRETRY }; + else if (lower == L"close") + return { TDCBF_CLOSE_BUTTON, IDCLOSE }; + return { -1, -1 }; +} + +// Determine whether the buttons are common buttons, if so map common ID +// to button ID. +void MapToCommonID(const std::vector& buttons, + std::map* id_map, + TASKDIALOG_COMMON_BUTTON_FLAGS* button_flags, + std::vector* dialog_buttons) { + for (size_t i = 0; i < buttons.size(); ++i) { + auto common = GetCommonID(buttons[i]); + if (common.button != -1) { + // It is a common button. + (*id_map)[common.id] = i; + (*button_flags) |= common.button; + } else { + // It is a custom button. + dialog_buttons->push_back({i + kIDStart, buttons[i].c_str()}); + } + } +} + +int ShowMessageBoxUTF16(HWND parent, + MessageBoxType type, + const std::vector& buttons, + int cancel_id, + int options, + const base::string16& title, + const base::string16& message, + const base::string16& detail, + const gfx::ImageSkia& icon) { + TASKDIALOG_FLAGS flags = + TDF_SIZE_TO_CONTENT | // Show all content. + TDF_ALLOW_DIALOG_CANCELLATION; // Allow canceling the dialog. + + TASKDIALOGCONFIG config = { 0 }; + config.cbSize = sizeof(config); + config.hwndParent = parent; + config.hInstance = GetModuleHandle(NULL); + config.dwFlags = flags; + + // TaskDialogIndirect doesn't allow empty name, if we set empty title it + // will show "electron.exe" in title. + base::string16 app_name = base::UTF8ToUTF16(Browser::Get()->GetName()); + if (title.empty()) + config.pszWindowTitle = app_name.c_str(); + else + config.pszWindowTitle = title.c_str(); + + base::win::ScopedHICON hicon; + if (!icon.isNull()) { + hicon.Set(IconUtil::CreateHICONFromSkBitmap(*icon.bitmap())); + config.dwFlags |= TDF_USE_HICON_MAIN; + config.hMainIcon = hicon.Get(); + } else { + // Show icon according to dialog's type. + switch (type) { + case MESSAGE_BOX_TYPE_INFORMATION: + case MESSAGE_BOX_TYPE_QUESTION: + config.pszMainIcon = TD_INFORMATION_ICON; + break; + case MESSAGE_BOX_TYPE_WARNING: + config.pszMainIcon = TD_WARNING_ICON; + break; + case MESSAGE_BOX_TYPE_ERROR: + config.pszMainIcon = TD_ERROR_ICON; + break; + } + } + + // If "detail" is empty then don't make message hilighted. + if (detail.empty()) { + config.pszContent = message.c_str(); + } else { + config.pszMainInstruction = message.c_str(); + config.pszContent = detail.c_str(); + } + + // Iterate through the buttons, put common buttons in dwCommonButtons + // and custom buttons in pButtons. + std::map id_map; + std::vector dialog_buttons; + if (options & MESSAGE_BOX_NO_LINK) { + for (size_t i = 0; i < buttons.size(); ++i) + dialog_buttons.push_back({i + kIDStart, buttons[i].c_str()}); + } else { + MapToCommonID(buttons, &id_map, &config.dwCommonButtons, &dialog_buttons); + } + if (dialog_buttons.size() > 0) { + config.pButtons = &dialog_buttons.front(); + config.cButtons = dialog_buttons.size(); + if (!(options & MESSAGE_BOX_NO_LINK)) + config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links. + } + + int id = 0; + TaskDialogIndirect(&config, &id, NULL, NULL); + if (id_map.find(id) != id_map.end()) // common button. + return id_map[id]; + else if (id >= kIDStart) // custom button. + return id - kIDStart; + else + return cancel_id; +} + +void RunMessageBoxInNewThread(base::Thread* thread, + NativeWindow* parent, + MessageBoxType type, + const std::vector& buttons, + int cancel_id, + int options, + const std::string& title, + const std::string& message, + const std::string& detail, + const gfx::ImageSkia& icon, + const MessageBoxCallback& callback) { + int result = ShowMessageBox(parent, type, buttons, cancel_id, options, title, + message, detail, icon); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); + content::BrowserThread::DeleteSoon( + content::BrowserThread::UI, FROM_HERE, thread); +} + +} // namespace + +int ShowMessageBox(NativeWindow* parent, + MessageBoxType type, + const std::vector& buttons, + int cancel_id, + int options, + const std::string& title, + const std::string& message, + const std::string& detail, + const gfx::ImageSkia& icon) { + std::vector utf16_buttons; + for (const auto& button : buttons) + utf16_buttons.push_back(base::UTF8ToUTF16(button)); + + HWND hwnd_parent = parent ? + static_cast(parent)->GetAcceleratedWidget() : + NULL; + + NativeWindow::DialogScope dialog_scope(parent); + return ShowMessageBoxUTF16(hwnd_parent, + type, + utf16_buttons, + cancel_id, + options, + base::UTF8ToUTF16(title), + base::UTF8ToUTF16(message), + base::UTF8ToUTF16(detail), + icon); +} + +void ShowMessageBox(NativeWindow* parent, + MessageBoxType type, + const std::vector& buttons, + int cancel_id, + int options, + const std::string& title, + const std::string& message, + const std::string& detail, + const gfx::ImageSkia& icon, + const MessageBoxCallback& callback) { + scoped_ptr thread( + new base::Thread(ATOM_PRODUCT_NAME "MessageBoxThread")); + thread->init_com_with_mta(false); + if (!thread->Start()) { + callback.Run(cancel_id); + return; + } + + base::Thread* unretained = thread.release(); + unretained->message_loop()->PostTask( + FROM_HERE, + base::Bind(&RunMessageBoxInNewThread, base::Unretained(unretained), + parent, type, buttons, cancel_id, options, title, message, + detail, icon, callback)); +} + +void ShowErrorBox(const base::string16& title, const base::string16& content) { + ShowMessageBoxUTF16(NULL, MESSAGE_BOX_TYPE_ERROR, {}, 0, 0, L"Error", title, + content, gfx::ImageSkia()); +} + +} // namespace atom diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index a3878f718a62..12c6be2ea74e 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -26,12 +26,16 @@ void TrayIcon::DisplayBalloon(const gfx::Image& icon, const base::string16& contents) { } -void TrayIcon::NotifyClicked(const gfx::Rect& bounds) { - FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnClicked(bounds)); +void TrayIcon::PopUpContextMenu(const gfx::Point& pos) { } -void TrayIcon::NotifyDoubleClicked() { - FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDoubleClicked()); +void TrayIcon::NotifyClicked(const gfx::Rect& bounds, int modifiers) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnClicked(bounds, modifiers)); +} + +void TrayIcon::NotifyDoubleClicked(const gfx::Rect& bounds, int modifiers) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, + OnDoubleClicked(bounds, modifiers)); } void TrayIcon::NotifyBalloonShow() { @@ -46,4 +50,13 @@ void TrayIcon::NotifyBalloonClosed() { FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnBalloonClosed()); } +void TrayIcon::NotifyRightClicked(const gfx::Rect& bounds, int modifiers) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, + OnRightClicked(bounds, modifiers)); +} + +void TrayIcon::NotfiyDropFiles(const std::vector& files) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDropFiles(files)); +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index 7dc67da1bac4..af774ddbfb42 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_UI_TRAY_ICON_H_ #include +#include #include "atom/browser/ui/tray_icon_observer.h" #include "base/observer_list.h" @@ -46,22 +47,27 @@ class TrayIcon { const base::string16& title, const base::string16& contents); + virtual void PopUpContextMenu(const gfx::Point& pos); + // Set the context menu for this icon. virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) = 0; void AddObserver(TrayIconObserver* obs) { observers_.AddObserver(obs); } void RemoveObserver(TrayIconObserver* obs) { observers_.RemoveObserver(obs); } - void NotifyClicked(const gfx::Rect& = gfx::Rect()); - void NotifyDoubleClicked(); + void NotifyClicked(const gfx::Rect& = gfx::Rect(), int modifiers = 0); + void NotifyDoubleClicked(const gfx::Rect& = gfx::Rect(), int modifiers = 0); void NotifyBalloonShow(); void NotifyBalloonClicked(); void NotifyBalloonClosed(); + void NotifyRightClicked(const gfx::Rect& bounds = gfx::Rect(), + int modifiers = 0); + void NotfiyDropFiles(const std::vector& files); protected: TrayIcon(); private: - ObserverList observers_; + base::ObserverList observers_; DISALLOW_COPY_AND_ASSIGN(TrayIcon); }; diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index 5723cb6b2196..7781c93a1c03 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -9,15 +9,17 @@ #include +#include "atom/browser/ui/atom_menu_model.h" #include "atom/browser/ui/tray_icon.h" #include "base/mac/scoped_nsobject.h" @class AtomMenuController; -@class StatusItemController; +@class StatusItemView; namespace atom { -class TrayIconCocoa : public TrayIcon { +class TrayIconCocoa : public TrayIcon, + public AtomMenuModel::Observer { public: TrayIconCocoa(); virtual ~TrayIconCocoa(); @@ -27,16 +29,23 @@ class TrayIconCocoa : public TrayIcon { void SetToolTip(const std::string& tool_tip) override; void SetTitle(const std::string& title) override; void SetHighlightMode(bool highlight) override; + void PopUpContextMenu(const gfx::Point& pos) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; - private: - base::scoped_nsobject item_; + protected: + // AtomMenuModel::Observer: + void MenuClosed() override; - base::scoped_nsobject controller_; + private: + // Atom custom view for NSStatusItem. + base::scoped_nsobject status_item_view_; // Status menu shown when right-clicking the system icon. base::scoped_nsobject menu_; + // Used for unregistering observer. + AtomMenuModel* menu_model_; // weak ref. + DISALLOW_COPY_AND_ASSIGN(TrayIconCocoa); }; diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index f989b9b580e2..ec6a6a3e1964 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -6,84 +6,339 @@ #include "atom/browser/ui/cocoa/atom_menu_controller.h" #include "base/strings/sys_string_conversions.h" +#include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/gfx/image/image.h" #include "ui/gfx/screen.h" -@interface StatusItemController : NSObject { +namespace { + +// By default, OS X sets 4px to tray image as left and right padding margin. +const CGFloat kHorizontalMargin = 4; +// OS X tends to make the title 2px lower. +const CGFloat kVerticalTitleMargin = 2; + +} // namespace + +@interface StatusItemView : NSView { atom::TrayIconCocoa* trayIcon_; // weak + AtomMenuController* menuController_; // weak + BOOL isHighlightEnable_; + BOOL inMouseEventSequence_; + base::scoped_nsobject image_; + base::scoped_nsobject alternateImage_; + base::scoped_nsobject image_view_; + base::scoped_nsobject title_; + base::scoped_nsobject statusItem_; } -- (id)initWithIcon:(atom::TrayIconCocoa*)icon; -- (void)handleClick:(id)sender; -- (void)handleDoubleClick:(id)sender; -@end // @interface StatusItemController +@end // @interface StatusItemView -@implementation StatusItemController +@implementation StatusItemView -- (id)initWithIcon:(atom::TrayIconCocoa*)icon { +- (id)initWithImage:(NSImage*)image icon:(atom::TrayIconCocoa*)icon { + image_.reset([image copy]); trayIcon_ = icon; + isHighlightEnable_ = YES; + + // Get the initial size. + NSStatusBar* statusBar = [NSStatusBar systemStatusBar]; + NSRect frame = NSMakeRect(0, 0, [self fullWidth], [statusBar thickness]); + + if ((self = [super initWithFrame:frame])) { + // Setup the image view. + NSRect iconFrame = frame; + iconFrame.size.width = [self iconWidth]; + image_view_.reset([[NSImageView alloc] initWithFrame:iconFrame]); + [image_view_ setImageScaling:NSImageScaleNone]; + [image_view_ setImageAlignment:NSImageAlignCenter]; + [self addSubview:image_view_]; + + // Unregister image_view_ as a dragged destination, allows its parent view + // (StatusItemView) handle dragging events. + [image_view_ unregisterDraggedTypes]; + NSArray* types = [NSArray arrayWithObjects:NSFilenamesPboardType, nil]; + [self registerForDraggedTypes:types]; + + // Create the status item. + statusItem_.reset([[[NSStatusBar systemStatusBar] + statusItemWithLength:NSWidth(frame)] retain]); + [statusItem_ setView:self]; + } return self; } -- (void)handleClick:(id)sender { - // Get the frame of the NSStatusItem. - NSRect frame = [NSApp currentEvent].window.frame; +- (void)removeItem { + [[NSStatusBar systemStatusBar] removeStatusItem:statusItem_]; + statusItem_.reset(); +} + +- (void)drawRect:(NSRect)dirtyRect { + // Draw the tray icon and title that align with NSStatusItem, layout: + // ---------------- + // | icon | title | + /// ---------------- + + // Draw background. + BOOL highlight = [self shouldHighlight]; + CGFloat thickness = [[statusItem_ statusBar] thickness]; + NSRect statusItemBounds = NSMakeRect(0, 0, [statusItem_ length], thickness); + [statusItem_ drawStatusBarBackgroundInRect:statusItemBounds + withHighlight:highlight]; + + // Make use of NSImageView to draw the image, which can correctly draw + // template image under dark menu bar. + if (highlight && alternateImage_ && + [image_view_ image] != alternateImage_.get()) { + [image_view_ setImage:alternateImage_]; + } else if ([image_view_ image] != image_.get()) { + [image_view_ setImage:image_]; + } + + if (title_) { + // Highlight the text when icon is highlighted or in dark mode. + highlight |= [self isDarkMode]; + // Draw title. + NSRect titleDrawRect = NSMakeRect( + [self iconWidth], -kVerticalTitleMargin, [self titleWidth], thickness); + [title_ drawInRect:titleDrawRect + withAttributes:[self titleAttributesWithHighlight:highlight]]; + } +} + +- (BOOL)isDarkMode { + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; + NSString* mode = [defaults stringForKey:@"AppleInterfaceStyle"]; + return mode && [mode isEqualToString:@"Dark"]; +} + +// The width of the full status item. +- (CGFloat)fullWidth { + if (title_) + return [self iconWidth] + [self titleWidth] + kHorizontalMargin; + else + return [self iconWidth]; +} + +// The width of the icon. +- (CGFloat)iconWidth { + CGFloat thickness = [[NSStatusBar systemStatusBar] thickness]; + CGFloat imageHeight = [image_ size].height; + CGFloat imageWidth = [image_ size].width; + CGFloat iconWidth = imageWidth; + if (imageWidth < thickness) { + // Image's width must be larger than menu bar's height. + iconWidth = thickness; + } else { + CGFloat verticalMargin = thickness - imageHeight; + // Image must have same horizontal vertical margin. + if (verticalMargin > 0 && imageWidth != imageHeight) + iconWidth = imageWidth + verticalMargin; + CGFloat horizontalMargin = thickness - imageWidth; + // Image must have at least kHorizontalMargin horizontal margin on each + // side. + if (horizontalMargin < 2 * kHorizontalMargin) + iconWidth = imageWidth + 2 * kHorizontalMargin; + } + return iconWidth; +} + +// The width of the title. +- (CGFloat)titleWidth { + if (!title_) + return 0; + base::scoped_nsobject attributes( + [[NSAttributedString alloc] initWithString:title_ + attributes:[self titleAttributes]]); + return [attributes size].width; +} + +- (NSDictionary*)titleAttributesWithHighlight:(BOOL)highlight { + NSFont* font = [NSFont menuBarFontOfSize:0]; + NSColor* foregroundColor = highlight ? + [NSColor whiteColor] : + [NSColor colorWithRed:0.265625 green:0.25390625 blue:0.234375 alpha:1.0]; + return [NSDictionary dictionaryWithObjectsAndKeys: + font, NSFontAttributeName, + foregroundColor, NSForegroundColorAttributeName, + nil]; +} + +- (NSDictionary*)titleAttributes { + return [self titleAttributesWithHighlight:[self isDarkMode]]; +} + +- (void)setImage:(NSImage*)image { + image_.reset([image copy]); + [self setNeedsDisplay:YES]; +} + +- (void)setAlternateImage:(NSImage*)image { + alternateImage_.reset([image copy]); +} + +- (void)setHighlight:(BOOL)highlight { + isHighlightEnable_ = highlight; +} + +- (void)setTitle:(NSString*)title { + if (title.length > 0) + title_.reset([title copy]); + else + title_.reset(); + [statusItem_ setLength:[self fullWidth]]; + [self setNeedsDisplay:YES]; +} + +- (void)setMenuController:(AtomMenuController*)menu { + menuController_ = menu; +} + +- (void)mouseDown:(NSEvent*)event { + inMouseEventSequence_ = YES; + [self setNeedsDisplay:YES]; +} + +- (void)mouseUp:(NSEvent*)event { + if (!inMouseEventSequence_) { + // If the menu is showing, when user clicked the tray icon, the `mouseDown` + // event will be dissmissed, we need to close the menu at this time. + [self setNeedsDisplay:YES]; + return; + } + inMouseEventSequence_ = NO; + + // Show menu when there is a context menu. + // NB(hokein): Make tray's behavior more like official one's. + // When the tray icon gets clicked quickly multiple times, the + // event.clickCount doesn't always return 1. Instead, it returns a value that + // counts the clicked times. + // So we don't check the clickCount here, just pop up the menu for each click + // event. + if (menuController_) + [statusItem_ popUpStatusItemMenu:[menuController_ menu]]; + + // Don't emit click events when menu is showing. + if (menuController_) + return; + + // Single click event. + if (event.clickCount == 1) + trayIcon_->NotifyClicked( + [self getBoundsFromEvent:event], + ui::EventFlagsFromModifiers([event modifierFlags])); + + // Double click event. + if (event.clickCount == 2) + trayIcon_->NotifyDoubleClicked( + [self getBoundsFromEvent:event], + ui::EventFlagsFromModifiers([event modifierFlags])); + + [self setNeedsDisplay:YES]; +} + +- (void)popUpContextMenu { + if (menuController_ && ![menuController_ isMenuOpen]) { + // Redraw the dray icon to show highlight if it is enabled. + [self setNeedsDisplay:YES]; + [statusItem_ popUpStatusItemMenu:[menuController_ menu]]; + // The popUpStatusItemMenu returns only after the showing menu is closed. + // When it returns, we need to redraw the tray icon to not show highlight. + [self setNeedsDisplay:YES]; + } +} + +- (void)rightMouseUp:(NSEvent*)event { + trayIcon_->NotifyRightClicked( + [self getBoundsFromEvent:event], + ui::EventFlagsFromModifiers([event modifierFlags])); +} + +- (NSDragOperation)draggingEntered:(id )sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id )sender { + NSPasteboard* pboard = [sender draggingPasteboard]; + + if ([[pboard types] containsObject:NSFilenamesPboardType]) { + std::vector dropFiles; + NSArray* files = [pboard propertyListForType:NSFilenamesPboardType]; + for (NSString* file in files) + dropFiles.push_back(base::SysNSStringToUTF8(file)); + trayIcon_->NotfiyDropFiles(dropFiles); + return YES; + } + return NO; +} + +- (BOOL)shouldHighlight { + BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen]; + return isHighlightEnable_ && (inMouseEventSequence_ || isMenuOpen); +} + +- (gfx::Rect)getBoundsFromEvent:(NSEvent*)event { + NSRect frame = event.window.frame; gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame)); - // Flip coordinates to gfx (0,0 in top-left corner) using current screen. NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame)); - - trayIcon_->NotifyClicked(bounds); + return bounds; } - -- (void)handleDoubleClick:(id)sender { - trayIcon_->NotifyDoubleClicked(); -} - @end namespace atom { -TrayIconCocoa::TrayIconCocoa() { - controller_.reset([[StatusItemController alloc] initWithIcon:this]); - - item_.reset([[[NSStatusBar systemStatusBar] - statusItemWithLength:NSVariableStatusItemLength] retain]); - [item_ setEnabled:YES]; - [item_ setTarget:controller_]; - [item_ setAction:@selector(handleClick:)]; - [item_ setDoubleAction:@selector(handleDoubleClick:)]; - [item_ setHighlightMode:YES]; +TrayIconCocoa::TrayIconCocoa() : menu_model_(nullptr) { } TrayIconCocoa::~TrayIconCocoa() { - // Remove the status item from the status bar. - [[NSStatusBar systemStatusBar] removeStatusItem:item_]; + [status_item_view_ removeItem]; + if (menu_model_) + menu_model_->RemoveObserver(this); } void TrayIconCocoa::SetImage(const gfx::Image& image) { - [item_ setImage:image.AsNSImage()]; + if (status_item_view_) { + [status_item_view_ setImage:image.AsNSImage()]; + } else { + status_item_view_.reset( + [[StatusItemView alloc] initWithImage:image.AsNSImage() + icon:this]); + } } void TrayIconCocoa::SetPressedImage(const gfx::Image& image) { - [item_ setAlternateImage:image.AsNSImage()]; + [status_item_view_ setAlternateImage:image.AsNSImage()]; } void TrayIconCocoa::SetToolTip(const std::string& tool_tip) { - [item_ setToolTip:base::SysUTF8ToNSString(tool_tip)]; + [status_item_view_ setToolTip:base::SysUTF8ToNSString(tool_tip)]; } void TrayIconCocoa::SetTitle(const std::string& title) { - [item_ setTitle:base::SysUTF8ToNSString(title)]; + [status_item_view_ setTitle:base::SysUTF8ToNSString(title)]; } void TrayIconCocoa::SetHighlightMode(bool highlight) { - [item_ setHighlightMode:highlight]; + [status_item_view_ setHighlight:highlight]; +} + +void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos) { + [status_item_view_ popUpContextMenu]; } void TrayIconCocoa::SetContextMenu(ui::SimpleMenuModel* menu_model) { + // Substribe to MenuClosed event. + if (menu_model_) + menu_model_->RemoveObserver(this); + static_cast(menu_model)->AddObserver(this); + + // Create native menu. menu_.reset([[AtomMenuController alloc] initWithModel:menu_model]); - [item_ setMenu:[menu_ menu]]; + [status_item_view_ setMenuController:menu_.get()]; +} + +void TrayIconCocoa::MenuClosed() { + [status_item_view_ setNeedsDisplay:YES]; } // static diff --git a/atom/browser/ui/tray_icon_gtk.cc b/atom/browser/ui/tray_icon_gtk.cc index d95109c26e66..666e64101e88 100644 --- a/atom/browser/ui/tray_icon_gtk.cc +++ b/atom/browser/ui/tray_icon_gtk.cc @@ -42,6 +42,7 @@ void TrayIconGtk::SetContextMenu(ui::SimpleMenuModel* menu_model) { } void TrayIconGtk::OnClick() { + NotifyClicked(); } bool TrayIconGtk::HasClickAction() { diff --git a/atom/browser/ui/tray_icon_observer.h b/atom/browser/ui/tray_icon_observer.h index 3a34888b5318..fa8090d7d6c5 100644 --- a/atom/browser/ui/tray_icon_observer.h +++ b/atom/browser/ui/tray_icon_observer.h @@ -5,6 +5,9 @@ #ifndef ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_ #define ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_ +#include +#include + namespace gfx { class Rect; } @@ -13,11 +16,13 @@ namespace atom { class TrayIconObserver { public: - virtual void OnClicked(const gfx::Rect&) {} - virtual void OnDoubleClicked() {} + virtual void OnClicked(const gfx::Rect& bounds, int modifiers) {} + virtual void OnDoubleClicked(const gfx::Rect& bounds, int modifiers) {} virtual void OnBalloonShow() {} virtual void OnBalloonClicked() {} virtual void OnBalloonClosed() {} + virtual void OnRightClicked(const gfx::Rect& bounds, int modifiers) {} + virtual void OnDropFiles(const std::vector& files) {} protected: virtual ~TrayIconObserver() {} diff --git a/atom/browser/ui/views/native_frame_view.cc b/atom/browser/ui/views/native_frame_view.cc new file mode 100644 index 000000000000..a434fb434961 --- /dev/null +++ b/atom/browser/ui/views/native_frame_view.cc @@ -0,0 +1,35 @@ +// 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 "atom/browser/ui/views/native_frame_view.h" + +#include "atom/browser/native_window_views.h" + +namespace atom { + +namespace { + +const char kViewClassName[] = "AtomNativeFrameView"; + +} // namespace + +NativeFrameView::NativeFrameView(NativeWindowViews* window, + views::Widget* widget) + : views::NativeFrameView(widget), + window_(window) { +} + +gfx::Size NativeFrameView::GetMinimumSize() const { + return window_->GetMinimumSize(); +} + +gfx::Size NativeFrameView::GetMaximumSize() const { + return window_->GetMaximumSize(); +} + +const char* NativeFrameView::GetClassName() const { + return kViewClassName; +} + +} // namespace atom diff --git a/atom/browser/ui/views/native_frame_view.h b/atom/browser/ui/views/native_frame_view.h new file mode 100644 index 000000000000..acbe9cddc8dc --- /dev/null +++ b/atom/browser/ui/views/native_frame_view.h @@ -0,0 +1,34 @@ +// 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_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_ +#define ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_ + +#include "ui/views/window/native_frame_view.h" + +namespace atom { + +class NativeWindowViews; + +// Like the views::NativeFrameView, but returns the min/max size from the +// NativeWindowViews. +class NativeFrameView : public views::NativeFrameView { + public: + NativeFrameView(NativeWindowViews* window, views::Widget* widget); + + protected: + // views::View: + gfx::Size GetMinimumSize() const override; + gfx::Size GetMaximumSize() const override; + const char* GetClassName() const override; + + private: + NativeWindowViews* window_; // weak ref. + + DISALLOW_COPY_AND_ASSIGN(NativeFrameView); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_ diff --git a/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc b/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc new file mode 100644 index 000000000000..84a6d9aa3e50 --- /dev/null +++ b/atom/browser/ui/win/atom_desktop_window_tree_host_win.cc @@ -0,0 +1,28 @@ +// 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 "atom/browser/ui/win/atom_desktop_window_tree_host_win.h" + +#include "atom/browser/ui/win/message_handler_delegate.h" + +namespace atom { + +AtomDesktopWindowTreeHostWin::AtomDesktopWindowTreeHostWin( + MessageHandlerDelegate* delegate, + views::internal::NativeWidgetDelegate* native_widget_delegate, + views::DesktopNativeWidgetAura* desktop_native_widget_aura) + : views::DesktopWindowTreeHostWin(native_widget_delegate, + desktop_native_widget_aura), + delegate_(delegate) { +} + +AtomDesktopWindowTreeHostWin::~AtomDesktopWindowTreeHostWin() { +} + +bool AtomDesktopWindowTreeHostWin::PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) { + return delegate_->PreHandleMSG(message, w_param, l_param, result); +} + +} // namespace atom diff --git a/atom/browser/ui/win/atom_desktop_window_tree_host_win.h b/atom/browser/ui/win/atom_desktop_window_tree_host_win.h new file mode 100644 index 000000000000..47e4cb6aed2a --- /dev/null +++ b/atom/browser/ui/win/atom_desktop_window_tree_host_win.h @@ -0,0 +1,39 @@ +// 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_BROWSER_UI_WIN_ATOM_DESKTOP_WINDOW_TREE_HOST_WIN_H_ +#define ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_WINDOW_TREE_HOST_WIN_H_ + +#include + +#include + +#include "atom/browser/native_window.h" +#include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h" + +namespace atom { + +class MessageHandlerDelegate; + +class AtomDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin { + public: + AtomDesktopWindowTreeHostWin( + MessageHandlerDelegate* delegate, + views::internal::NativeWidgetDelegate* native_widget_delegate, + views::DesktopNativeWidgetAura* desktop_native_widget_aura); + ~AtomDesktopWindowTreeHostWin() override; + + protected: + bool PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) override; + + private: + MessageHandlerDelegate* delegate_; // weak ref + + DISALLOW_COPY_AND_ASSIGN(AtomDesktopWindowTreeHostWin); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_ATOM_DESKTOP_WINDOW_TREE_HOST_WIN_H_ diff --git a/atom/browser/ui/win/message_handler_delegate.cc b/atom/browser/ui/win/message_handler_delegate.cc new file mode 100644 index 000000000000..791d1fd816d9 --- /dev/null +++ b/atom/browser/ui/win/message_handler_delegate.cc @@ -0,0 +1,14 @@ +// 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 "atom/browser/ui/win/message_handler_delegate.h" + +namespace atom { + +bool MessageHandlerDelegate::PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result) { + return false; +} + +} // namespace atom diff --git a/atom/browser/ui/win/message_handler_delegate.h b/atom/browser/ui/win/message_handler_delegate.h new file mode 100644 index 000000000000..d8cfcf7fc43b --- /dev/null +++ b/atom/browser/ui/win/message_handler_delegate.h @@ -0,0 +1,26 @@ +// 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_BROWSER_UI_WIN_MESSAGE_HANDLER_DELEGATE_H_ +#define ATOM_BROWSER_UI_WIN_MESSAGE_HANDLER_DELEGATE_H_ + +#include + +namespace atom { + +class MessageHandlerDelegate { + public: + // Catch-all message handling and filtering. Called before + // HWNDMessageHandler's built-in handling, which may pre-empt some + // expectations in Views/Aura if messages are consumed. Returns true if the + // message was consumed by the delegate and should not be processed further + // by the HWNDMessageHandler. In this case, |result| is returned. |result| is + // not modified otherwise. + virtual bool PreHandleMSG( + UINT message, WPARAM w_param, LPARAM l_param, LRESULT* result); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_MESSAGE_HANDLER_DELEGATE_H_ diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index 99b7153631af..4026d9ec4a6b 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -4,7 +4,10 @@ #include "atom/browser/ui/win/notify_icon.h" +#include + #include "atom/browser/ui/win/notify_icon_host.h" +#include "base/md5.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/win/windows_version.h" @@ -25,10 +28,34 @@ NotifyIcon::NotifyIcon(NotifyIconHost* host, icon_id_(id), window_(window), message_id_(message), - menu_model_(NULL) { + menu_model_(NULL), + has_tray_app_id_hash_(false) { + // NB: If we have an App Model ID, we should propagate that to the tray. + // Doing this prevents duplicate items from showing up in the notification + // preferences (i.e. "Always Show / Show notifications only / etc") + PWSTR explicit_app_id; + if (SUCCEEDED(GetCurrentProcessExplicitAppUserModelID(&explicit_app_id))) { + // GUIDs and MD5 hashes are the same length. So convenient! + base::MD5Sum(explicit_app_id, + sizeof(wchar_t) * wcslen(explicit_app_id), + reinterpret_cast(&tray_app_id_hash_)); + + // Set the GUID to version 4 as described in RFC 4122, section 4.4. + // The format of GUID version 4 must be like + // xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx, where y is one of [8, 9, A, B]. + tray_app_id_hash_.Data3 &= 0x0fff; + tray_app_id_hash_.Data3 |= 0x4000; + + // Set y to one of [8, 9, A, B]. + tray_app_id_hash_.Data4[0] = 1; + + has_tray_app_id_hash_ = true; + CoTaskMemFree(explicit_app_id); + } + NOTIFYICONDATA icon_data; InitIconData(&icon_data); - icon_data.uFlags = NIF_MESSAGE; + icon_data.uFlags |= NIF_MESSAGE; icon_data.uCallbackMessage = message_id_; BOOL result = Shell_NotifyIcon(NIM_ADD, &icon_data); // This can happen if the explorer process isn't running when we try to @@ -46,30 +73,34 @@ NotifyIcon::~NotifyIcon() { } void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, - bool left_mouse_click) { - // Pass to the observer if appropriate. + int modifiers, + bool left_mouse_click, + bool double_button_click) { + NOTIFYICONIDENTIFIER icon_id; + memset(&icon_id, 0, sizeof(NOTIFYICONIDENTIFIER)); + icon_id.uID = icon_id_; + icon_id.hWnd = window_; + icon_id.cbSize = sizeof(NOTIFYICONIDENTIFIER); + if (has_tray_app_id_hash_) + memcpy(reinterpret_cast(&icon_id.guidItem), + &tray_app_id_hash_, + sizeof(GUID)); + + RECT rect = { 0 }; + Shell_NotifyIconGetRect(&icon_id, &rect); + if (left_mouse_click) { - NotifyClicked(); + if (double_button_click) // double left click + NotifyDoubleClicked(gfx::Rect(rect), modifiers); + else // single left click + NotifyClicked(gfx::Rect(rect), modifiers); return; + } else if (!double_button_click) { // single right click + if (menu_model_) + PopUpContextMenu(cursor_pos); + else + NotifyRightClicked(gfx::Rect(rect), modifiers); } - - if (!menu_model_) - return; - - // Set our window as the foreground window, so the context menu closes when - // we click away from it. - if (!SetForegroundWindow(window_)) - return; - - views::MenuRunner menu_runner( - menu_model_, - views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); - ignore_result(menu_runner.RunMenuAt( - NULL, - NULL, - gfx::Rect(cursor_pos, gfx::Size()), - views::MENU_ANCHOR_TOPLEFT, - ui::MENU_SOURCE_MOUSE)); } void NotifyIcon::ResetIcon() { @@ -78,7 +109,7 @@ void NotifyIcon::ResetIcon() { // Delete any previously existing icon. Shell_NotifyIcon(NIM_DELETE, &icon_data); InitIconData(&icon_data); - icon_data.uFlags = NIF_MESSAGE; + icon_data.uFlags |= NIF_MESSAGE; icon_data.uCallbackMessage = message_id_; icon_data.hIcon = icon_.Get(); // If we have an image, then set the NIF_ICON flag, which tells @@ -95,7 +126,7 @@ void NotifyIcon::SetImage(const gfx::Image& image) { // Create the icon. NOTIFYICONDATA icon_data; InitIconData(&icon_data); - icon_data.uFlags = NIF_ICON; + icon_data.uFlags |= NIF_ICON; icon_.Set(IconUtil::CreateHICONFromSkBitmap(image.AsBitmap())); icon_data.hIcon = icon_.Get(); BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); @@ -112,7 +143,7 @@ void NotifyIcon::SetToolTip(const std::string& tool_tip) { // Create the icon. NOTIFYICONDATA icon_data; InitIconData(&icon_data); - icon_data.uFlags = NIF_TIP; + icon_data.uFlags |= NIF_TIP; wcscpy_s(icon_data.szTip, base::UTF8ToUTF16(tool_tip).c_str()); BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); if (!result) @@ -124,7 +155,7 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, const base::string16& contents) { NOTIFYICONDATA icon_data; InitIconData(&icon_data); - icon_data.uFlags = NIF_INFO; + icon_data.uFlags |= NIF_INFO; icon_data.dwInfoFlags = NIIF_INFO; wcscpy_s(icon_data.szInfoTitle, title.c_str()); wcscpy_s(icon_data.szInfo, contents.c_str()); @@ -142,6 +173,26 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, LOG(WARNING) << "Unable to create status tray balloon."; } +void NotifyIcon::PopUpContextMenu(const gfx::Point& pos) { + // Returns if context menu isn't set. + if (!menu_model_) + return; + // Set our window as the foreground window, so the context menu closes when + // we click away from it. + if (!SetForegroundWindow(window_)) + return; + + views::MenuRunner menu_runner( + menu_model_, + views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); + ignore_result(menu_runner.RunMenuAt( + NULL, + NULL, + gfx::Rect(pos, gfx::Size()), + views::MENU_ANCHOR_TOPLEFT, + ui::MENU_SOURCE_MOUSE)); +} + void NotifyIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { menu_model_ = menu_model; } @@ -151,6 +202,13 @@ void NotifyIcon::InitIconData(NOTIFYICONDATA* icon_data) { icon_data->cbSize = sizeof(NOTIFYICONDATA); icon_data->hWnd = window_; icon_data->uID = icon_id_; + + if (has_tray_app_id_hash_) { + icon_data->uFlags |= NIF_GUID; + memcpy(reinterpret_cast(&icon_data->guidItem), + &tray_app_id_hash_, + sizeof(GUID)); + } } } // namespace atom diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index 12eea1fcf725..136186b689b9 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -33,7 +33,10 @@ class NotifyIcon : public TrayIcon { // Handles a click event from the user - if |left_button_click| is true and // there is a registered observer, passes the click event to the observer, // otherwise displays the context menu if there is one. - void HandleClickEvent(const gfx::Point& cursor_pos, bool left_button_click); + void HandleClickEvent(const gfx::Point& cursor_pos, + int modifiers, + bool left_button_click, + bool double_button_click); // Re-creates the status tray icon now after the taskbar has been created. void ResetIcon(); @@ -49,6 +52,7 @@ class NotifyIcon : public TrayIcon { void DisplayBalloon(const gfx::Image& icon, const base::string16& title, const base::string16& contents) override; + void PopUpContextMenu(const gfx::Point& pos) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; private: @@ -75,6 +79,10 @@ class NotifyIcon : public TrayIcon { // The context menu. ui::SimpleMenuModel* menu_model_; + // A hash of the app model ID + GUID tray_app_id_hash_; + bool has_tray_app_id_hash_; + DISALLOW_COPY_AND_ASSIGN(NotifyIcon); }; diff --git a/atom/browser/ui/win/notify_icon_host.cc b/atom/browser/ui/win/notify_icon_host.cc index 4aac629f248a..2c84837e714d 100644 --- a/atom/browser/ui/win/notify_icon_host.cc +++ b/atom/browser/ui/win/notify_icon_host.cc @@ -5,13 +5,16 @@ #include "atom/browser/ui/win/notify_icon_host.h" #include +#include #include "atom/browser/ui/win/notify_icon.h" #include "base/bind.h" #include "base/stl_util.h" #include "base/threading/non_thread_safe.h" #include "base/threading/thread.h" +#include "base/win/win_util.h" #include "base/win/wrapped_window_proc.h" +#include "ui/events/event_constants.h" #include "ui/gfx/screen.h" #include "ui/gfx/win/hwnd_util.h" @@ -26,6 +29,24 @@ const UINT kBaseIconId = 2; const wchar_t kNotifyIconHostWindowClass[] = L"Electron_NotifyIconHostWindow"; +bool IsWinPressed() { + return ((::GetKeyState(VK_LWIN) & 0x8000) == 0x8000) || + ((::GetKeyState(VK_RWIN) & 0x8000) == 0x8000); +} + +int GetKeyboardModifers() { + int modifiers = ui::EF_NONE; + if (base::win::IsShiftPressed()) + modifiers |= ui::EF_SHIFT_DOWN; + if (base::win::IsCtrlPressed()) + modifiers |= ui::EF_CONTROL_DOWN; + if (base::win::IsAltPressed()) + modifiers |= ui::EF_ALT_DOWN; + if (IsWinPressed()) + modifiers |= ui::EF_COMMAND_DOWN; + return modifiers; +} + } // namespace NotifyIconHost::NotifyIconHost() @@ -146,12 +167,18 @@ LRESULT CALLBACK NotifyIconHost::WndProc(HWND hwnd, case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: case WM_CONTEXTMENU: // Walk our icons, find which one was clicked on, and invoke its // HandleClickEvent() method. gfx::Point cursor_pos( gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); - win_icon->HandleClickEvent(cursor_pos, lparam == WM_LBUTTONDOWN); + win_icon->HandleClickEvent( + cursor_pos, + GetKeyboardModifers(), + (lparam == WM_LBUTTONDOWN || lparam == WM_LBUTTONDBLCLK), + (lparam == WM_LBUTTONDBLCLK || lparam == WM_RBUTTONDBLCLK)); return TRUE; } } diff --git a/atom/browser/ui/win/taskbar_host.cc b/atom/browser/ui/win/taskbar_host.cc new file mode 100644 index 000000000000..a8e6ff2926cd --- /dev/null +++ b/atom/browser/ui/win/taskbar_host.cc @@ -0,0 +1,165 @@ +// 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 "atom/browser/ui/win/taskbar_host.h" + +#include + +#include "base/stl_util.h" +#include "base/win/scoped_gdi_object.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/icon_util.h" + +namespace atom { + +namespace { + +// From MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#thumbbars +// The thumbnail toolbar has a maximum of seven buttons due to the limited room. +const size_t kMaxButtonsCount = 7; + +// The base id of Thumbar button. +const int kButtonIdBase = 40001; + +bool GetThumbarButtonFlags(const std::vector& flags, + THUMBBUTTONFLAGS* out) { + THUMBBUTTONFLAGS result = THBF_ENABLED; // THBF_ENABLED == 0 + for (const auto& flag : flags) { + if (flag == "disabled") + result |= THBF_DISABLED; + else if (flag == "dismissonclick") + result |= THBF_DISMISSONCLICK; + else if (flag == "nobackground") + result |= THBF_NOBACKGROUND; + else if (flag == "hidden") + result |= THBF_HIDDEN; + else if (flag == "noninteractive") + result |= THBF_NONINTERACTIVE; + else + return false; + } + *out = result; + return true; +} + +} // namespace + +TaskbarHost::TaskbarHost() : thumbar_buttons_added_(false) { +} + +TaskbarHost::~TaskbarHost() { +} + +bool TaskbarHost::SetThumbarButtons( + HWND window, const std::vector& buttons) { + if (buttons.size() > kMaxButtonsCount || !InitailizeTaskbar()) + return false; + + callback_map_.clear(); + + // The number of buttons in thumbar can not be changed once it is created, + // so we have to claim kMaxButtonsCount buttons initialy in case users add + // more buttons later. + base::win::ScopedHICON icons[kMaxButtonsCount] = {}; + THUMBBUTTON thumb_buttons[kMaxButtonsCount] = {}; + + for (size_t i = 0; i < kMaxButtonsCount; ++i) { + THUMBBUTTON& thumb_button = thumb_buttons[i]; + + // Set ID. + thumb_button.iId = kButtonIdBase + i; + thumb_button.dwMask = THB_FLAGS; + + if (i >= buttons.size()) { + // This button is used to occupy the place in toolbar, and it does not + // show. + thumb_button.dwFlags = THBF_HIDDEN; + continue; + } + + // This button is user's button. + const ThumbarButton& button = buttons[i]; + + // Generate flags. + thumb_button.dwFlags = THBF_ENABLED; + if (!GetThumbarButtonFlags(button.flags, &thumb_button.dwFlags)) + return false; + + // Set icon. + if (!button.icon.IsEmpty()) { + thumb_button.dwMask |= THB_ICON; + icons[i] = IconUtil::CreateHICONFromSkBitmap(button.icon.AsBitmap()); + thumb_button.hIcon = icons[i].Get(); + } + + // Set tooltip. + if (!button.tooltip.empty()) { + thumb_button.dwMask |= THB_TOOLTIP; + wcscpy_s(thumb_button.szTip, base::UTF8ToUTF16(button.tooltip).c_str()); + } + + // Save callback. + callback_map_[thumb_button.iId] = button.clicked_callback; + } + + // Finally add them to taskbar. + HRESULT r; + if (thumbar_buttons_added_) + r = taskbar_->ThumbBarUpdateButtons(window, kMaxButtonsCount, + thumb_buttons); + else + r = taskbar_->ThumbBarAddButtons(window, kMaxButtonsCount, thumb_buttons); + + thumbar_buttons_added_ = true; + return SUCCEEDED(r); +} + +bool TaskbarHost::SetProgressBar(HWND window, double value) { + if (!InitailizeTaskbar()) + return false; + + HRESULT r; + if (value > 1.0) + r = taskbar_->SetProgressState(window, TBPF_INDETERMINATE); + else if (value < 0) + r = taskbar_->SetProgressState(window, TBPF_NOPROGRESS); + else + r = taskbar_->SetProgressValue(window, static_cast(value * 100), 100); + return SUCCEEDED(r); +} + +bool TaskbarHost::SetOverlayIcon( + HWND window, const gfx::Image& overlay, const std::string& text) { + if (!InitailizeTaskbar()) + return false; + + base::win::ScopedHICON icon( + IconUtil::CreateHICONFromSkBitmap(overlay.AsBitmap())); + return SUCCEEDED( + taskbar_->SetOverlayIcon(window, icon, base::UTF8ToUTF16(text).c_str())); +} + +bool TaskbarHost::HandleThumbarButtonEvent(int button_id) { + if (ContainsKey(callback_map_, button_id)) { + auto callback = callback_map_[button_id]; + if (!callback.is_null()) + callback.Run(); + return true; + } + return false; +} + +bool TaskbarHost::InitailizeTaskbar() { + if (FAILED(taskbar_.CreateInstance(CLSID_TaskbarList, + nullptr, + CLSCTX_INPROC_SERVER)) || + FAILED(taskbar_->HrInit())) { + return false; + } else { + return true; + } +} + +} // namespace atom diff --git a/atom/browser/ui/win/taskbar_host.h b/atom/browser/ui/win/taskbar_host.h new file mode 100644 index 000000000000..185b88a6b5b6 --- /dev/null +++ b/atom/browser/ui/win/taskbar_host.h @@ -0,0 +1,64 @@ +// 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_BROWSER_UI_WIN_TASKBAR_HOST_H_ +#define ATOM_BROWSER_UI_WIN_TASKBAR_HOST_H_ + +#include + +#include +#include +#include + +#include "base/callback.h" +#include "base/win/scoped_comptr.h" +#include "ui/gfx/image/image.h" + +namespace atom { + +class TaskbarHost { + public: + struct ThumbarButton { + std::string tooltip; + gfx::Image icon; + std::vector flags; + base::Closure clicked_callback; + }; + + TaskbarHost(); + virtual ~TaskbarHost(); + + // Add or update the buttons in thumbar. + bool SetThumbarButtons( + HWND window, const std::vector& buttons); + + // Set the progress state in taskbar. + bool SetProgressBar(HWND window, double value); + + // Set the overlay icon in taskbar. + bool SetOverlayIcon( + HWND window, const gfx::Image& overlay, const std::string& text); + + // Called by the window that there is a button in thumbar clicked. + bool HandleThumbarButtonEvent(int button_id); + + private: + // Initailize the taskbar object. + bool InitailizeTaskbar(); + + using CallbackMap = std::map; + CallbackMap callback_map_; + + // The COM object of taskbar. + base::win::ScopedComPtr taskbar_; + + // Whether we have already added the buttons to thumbar. + bool thumbar_buttons_added_; + + DISALLOW_COPY_AND_ASSIGN(TaskbarHost); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_TASKBAR_HOST_H_ diff --git a/atom/browser/ui/x/x_window_utils.cc b/atom/browser/ui/x/x_window_utils.cc index eaef5475b777..e57122839cd8 100644 --- a/atom/browser/ui/x/x_window_utils.cc +++ b/atom/browser/ui/x/x_window_utils.cc @@ -7,6 +7,9 @@ #include #include "base/strings/string_util.h" +#include "dbus/bus.h" +#include "dbus/object_proxy.h" +#include "dbus/message.h" #include "ui/base/x/x11_util.h" namespace atom { @@ -38,7 +41,7 @@ void SetWindowType(::Window xwindow, const std::string& type) { XDisplay* xdisplay = gfx::GetXDisplay(); std::string type_prefix = "_NET_WM_WINDOW_TYPE_"; ::Atom window_type = XInternAtom( - xdisplay, (type_prefix + StringToUpperASCII(type)).c_str(), False); + xdisplay, (type_prefix + base::StringToUpperASCII(type)).c_str(), False); XChangeProperty(xdisplay, xwindow, XInternAtom(xdisplay, "_NET_WM_WINDOW_TYPE", False), XA_ATOM, @@ -46,4 +49,37 @@ void SetWindowType(::Window xwindow, const std::string& type) { reinterpret_cast(&window_type), 1); } +bool ShouldUseGlobalMenuBar() { + dbus::Bus::Options options; + scoped_refptr bus(new dbus::Bus(options)); + + dbus::ObjectProxy* object_proxy = + bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS)); + dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, "ListNames"); + scoped_ptr response(object_proxy->CallMethodAndBlock( + &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); + if (!response) { + bus->ShutdownAndBlock(); + return false; + } + + dbus::MessageReader reader(response.get()); + dbus::MessageReader array_reader(NULL); + if (!reader.PopArray(&array_reader)) { + bus->ShutdownAndBlock(); + return false; + } + while (array_reader.HasMoreData()) { + std::string name; + if (array_reader.PopString(&name) && + name == "com.canonical.AppMenu.Registrar") { + bus->ShutdownAndBlock(); + return true; + } + } + + bus->ShutdownAndBlock(); + return false; +} + } // namespace atom diff --git a/atom/browser/ui/x/x_window_utils.h b/atom/browser/ui/x/x_window_utils.h index ccf56d1eb9cf..16f3ddac6ccd 100644 --- a/atom/browser/ui/x/x_window_utils.h +++ b/atom/browser/ui/x/x_window_utils.h @@ -22,6 +22,9 @@ void SetWMSpecState(::Window xwindow, bool enabled, ::Atom state); // Sets the _NET_WM_WINDOW_TYPE of window. void SetWindowType(::Window xwindow, const std::string& type); +// Returns true if the bus name "com.canonical.AppMenu.Registrar" is available. +bool ShouldUseGlobalMenuBar(); + } // namespace atom #endif // ATOM_BROWSER_UI_X_X_WINDOW_UTILS_H_ diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc new file mode 100644 index 000000000000..db55eddf7e2d --- /dev/null +++ b/atom/browser/web_contents_preferences.cc @@ -0,0 +1,154 @@ +// 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 "atom/browser/web_contents_preferences.h" + +#include + +#include "atom/common/options_switches.h" +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "content/public/common/web_preferences.h" +#include "net/base/filename_util.h" + +#if defined(OS_WIN) +#include "ui/gfx/switches.h" +#endif + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPreferences); + +namespace atom { + +namespace { + +// Array of available web runtime features. +const char* kWebRuntimeFeatures[] = { + switches::kExperimentalFeatures, + switches::kExperimentalCanvasFeatures, + switches::kSubpixelFontScaling, + switches::kOverlayScrollbars, + switches::kOverlayFullscreenVideo, + switches::kSharedWorker, + switches::kPageVisibility, +}; + +} // namespace + +WebContentsPreferences::WebContentsPreferences( + content::WebContents* web_contents, + base::DictionaryValue* web_preferences) { + web_preferences_.Swap(web_preferences); + web_contents->SetUserData(UserDataKey(), this); +} + +WebContentsPreferences::~WebContentsPreferences() { +} + +void WebContentsPreferences::Merge(const base::DictionaryValue& extend) { + web_preferences_.MergeDictionary(&extend); +} + +// static +void WebContentsPreferences::AppendExtraCommandLineSwitches( + content::WebContents* web_contents, base::CommandLine* command_line) { + WebContentsPreferences* self = FromWebContents(web_contents); + if (!self) + return; + + base::DictionaryValue& web_preferences = self->web_preferences_; + + bool b; +#if defined(OS_WIN) + // Check if DirectWrite is disabled. + if (web_preferences.GetBoolean(switches::kDirectWrite, &b) && !b) + command_line->AppendSwitch(::switches::kDisableDirectWrite); +#endif + + // Check if plugins are enabled. + if (web_preferences.GetBoolean("plugins", &b) && b) + command_line->AppendSwitch(switches::kEnablePlugins); + + // This set of options are not availabe in WebPreferences, so we have to pass + // them via command line and enable them in renderer procss. + for (size_t i = 0; i < arraysize(kWebRuntimeFeatures); ++i) { + const char* feature = kWebRuntimeFeatures[i]; + if (web_preferences.GetBoolean(feature, &b)) + command_line->AppendSwitchASCII(feature, b ? "true" : "false"); + } + + // Check if we have node integration specified. + bool node_integration = true; + web_preferences.GetBoolean(switches::kNodeIntegration, &node_integration); + // Be compatible with old API of "node-integration" option. + std::string old_token; + if (web_preferences.GetString(switches::kNodeIntegration, &old_token) && + old_token != "disable") + node_integration = true; + command_line->AppendSwitchASCII(switches::kNodeIntegration, + node_integration ? "true" : "false"); + + // The preload script. + base::FilePath::StringType preload; + if (web_preferences.GetString(switches::kPreloadScript, &preload)) { + if (base::FilePath(preload).IsAbsolute()) + command_line->AppendSwitchNative(switches::kPreloadScript, preload); + else + LOG(ERROR) << "preload script must have abosulute path."; + } else if (web_preferences.GetString(switches::kPreloadUrl, &preload)) { + // Translate to file path if there is "preload-url" option. + base::FilePath preload_path; + if (net::FileURLToFilePath(GURL(preload), &preload_path)) + command_line->AppendSwitchPath(switches::kPreloadScript, preload_path); + else + LOG(ERROR) << "preload url must be file:// protocol."; + } + + // The zoom factor. + double zoom_factor = 1.0; + if (web_preferences.GetDouble(switches::kZoomFactor, &zoom_factor) && + zoom_factor != 1.0) + command_line->AppendSwitchASCII(switches::kZoomFactor, + base::DoubleToString(zoom_factor)); + + // --guest-instance-id, which is used to identify guest WebContents. + int guest_instance_id; + if (web_preferences.GetInteger(switches::kGuestInstanceID, + &guest_instance_id)) + command_line->AppendSwitchASCII(switches::kGuestInstanceID, + base::IntToString(guest_instance_id)); +} + +// static +void WebContentsPreferences::OverrideWebkitPrefs( + content::WebContents* web_contents, content::WebPreferences* prefs) { + WebContentsPreferences* self = FromWebContents(web_contents); + if (!self) + return; + + bool b; + if (self->web_preferences_.GetBoolean("javascript", &b)) + prefs->javascript_enabled = b; + if (self->web_preferences_.GetBoolean("images", &b)) + prefs->images_enabled = b; + if (self->web_preferences_.GetBoolean("java", &b)) + prefs->java_enabled = b; + if (self->web_preferences_.GetBoolean("text-areas-are-resizable", &b)) + prefs->text_areas_are_resizable = b; + if (self->web_preferences_.GetBoolean("webgl", &b)) + prefs->experimental_webgl_enabled = b; + if (self->web_preferences_.GetBoolean("webaudio", &b)) + prefs->webaudio_enabled = b; + if (self->web_preferences_.GetBoolean("web-security", &b)) { + prefs->web_security_enabled = b; + prefs->allow_displaying_insecure_content = !b; + prefs->allow_running_insecure_content = !b; + } + if (self->web_preferences_.GetBoolean("allow-displaying-insecure-content", + &b)) + prefs->allow_displaying_insecure_content = b; + if (self->web_preferences_.GetBoolean("allow-running-insecure-content", &b)) + prefs->allow_running_insecure_content = b; +} + +} // namespace atom diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h new file mode 100644 index 000000000000..83b485f449bc --- /dev/null +++ b/atom/browser/web_contents_preferences.h @@ -0,0 +1,50 @@ +// 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_BROWSER_WEB_CONTENTS_PREFERENCES_H_ +#define ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ + +#include "base/values.h" +#include "content/public/browser/web_contents_user_data.h" + +namespace base { +class CommandLine; +} + +namespace content { +struct WebPreferences; +} + +namespace atom { + +// Stores and applies the preferences of WebContents. +class WebContentsPreferences + : public content::WebContentsUserData { + public: + // Append command paramters according to |web_contents|'s preferences. + static void AppendExtraCommandLineSwitches( + content::WebContents* web_contents, base::CommandLine* command_line); + + // Modify the WebPreferences according to |web_contents|'s preferences. + static void OverrideWebkitPrefs( + content::WebContents* web_contents, content::WebPreferences* prefs); + + WebContentsPreferences(content::WebContents* web_contents, + base::DictionaryValue* web_preferences); + ~WebContentsPreferences() override; + + // $.extend(|web_preferences_|, |new_web_preferences|). + void Merge(const base::DictionaryValue& new_web_preferences); + + private: + friend class content::WebContentsUserData; + + base::DictionaryValue web_preferences_; + + DISALLOW_COPY_AND_ASSIGN(WebContentsPreferences); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_WEB_CONTENTS_PREFERENCES_H_ diff --git a/atom/browser/web_dialog_helper.cc b/atom/browser/web_dialog_helper.cc index f6f233453582..c3d2a1d0f23f 100644 --- a/atom/browser/web_dialog_helper.cc +++ b/atom/browser/web_dialog_helper.cc @@ -4,17 +4,66 @@ #include "atom/browser/web_dialog_helper.h" +#include #include +#include "atom/browser/atom_browser_context.h" +#include "atom/browser/native_window.h" #include "atom/browser/ui/file_dialog.h" #include "base/bind.h" #include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/prefs/pref_service.h" #include "base/strings/utf_string_conversions.h" +#include "chrome/common/pref_names.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "content/public/common/file_chooser_file_info.h" +#include "net/base/mime_util.h" #include "ui/shell_dialogs/selected_file_info.h" +namespace { + +file_dialog::Filters GetFileTypesFromAcceptType( + const std::vector& accept_types) { + file_dialog::Filters filters; + if (accept_types.empty()) + return filters; + + std::vector extensions; + + for (const auto& accept_type : accept_types) { + std::string ascii_type = base::UTF16ToASCII(accept_type); + if (ascii_type[0] == '.') { + // If the type starts with a period it is assumed to be a file extension, + // like `.txt`, // so we just have to add it to the list. + base::FilePath::StringType extension( + ascii_type.begin(), ascii_type.end()); + // Skip the first character. + extensions.push_back(extension.substr(1)); + } else { + // For MIME Type, `audio/*, vidio/*, image/* + net::GetExtensionsForMimeType(ascii_type, &extensions); + } + } + + // If no valid exntesion is added, return empty filters. + if (extensions.empty()) + return filters; + + filters.push_back(file_dialog::Filter()); + for (const auto& extension : extensions) { +#if defined(OS_WIN) + filters[0].second.push_back(base::UTF16ToASCII(extension)); +#else + filters[0].second.push_back(extension); +#endif + } + return filters; +} + +} // namespace + namespace atom { WebDialogHelper::WebDialogHelper(NativeWindow* window) @@ -25,15 +74,18 @@ WebDialogHelper::WebDialogHelper(NativeWindow* window) WebDialogHelper::~WebDialogHelper() { } + void WebDialogHelper::RunFileChooser(content::WebContents* web_contents, const content::FileChooserParams& params) { std::vector result; + file_dialog::Filters filters = GetFileTypesFromAcceptType( + params.accept_types); if (params.mode == content::FileChooserParams::Save) { base::FilePath path; if (file_dialog::ShowSaveDialog(window_, base::UTF16ToUTF8(params.title), params.default_file_name, - file_dialog::Filters(), + filters, &path)) { content::FileChooserFileInfo info; info.file_path = path; @@ -56,10 +108,14 @@ void WebDialogHelper::RunFileChooser(content::WebContents* web_contents, } std::vector paths; + AtomBrowserContext* browser_context = static_cast( + window_->web_contents()->GetBrowserContext()); + base::FilePath default_file_path = browser_context->prefs()->GetFilePath( + prefs::kSelectFileLastDirectory).Append(params.default_file_name); if (file_dialog::ShowOpenDialog(window_, base::UTF16ToUTF8(params.title), - params.default_file_name, - file_dialog::Filters(), + default_file_path, + filters, flags, &paths)) { for (auto& path : paths) { @@ -68,6 +124,10 @@ void WebDialogHelper::RunFileChooser(content::WebContents* web_contents, info.display_name = path.BaseName().value(); result.push_back(info); } + if (!paths.empty()) { + browser_context->prefs()->SetFilePath(prefs::kSelectFileLastDirectory, + paths[0].DirName()); + } } } diff --git a/atom/browser/web_view_guest_delegate.cc b/atom/browser/web_view_guest_delegate.cc new file mode 100644 index 000000000000..8e1810c4a39b --- /dev/null +++ b/atom/browser/web_view_guest_delegate.cc @@ -0,0 +1,185 @@ +// 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 "atom/browser/web_view_guest_delegate.h" + +#include "atom/browser/api/atom_api_web_contents.h" +#include "atom/common/native_mate_converters/gurl_converter.h" +#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_view.h" + +namespace atom { + +namespace { + +const int kDefaultWidth = 300; +const int kDefaultHeight = 300; + +} // namespace + +WebViewGuestDelegate::WebViewGuestDelegate() + : guest_opaque_(true), + guest_host_(nullptr), + auto_size_enabled_(false), + is_full_page_plugin_(false), + api_web_contents_(nullptr) { +} + +WebViewGuestDelegate::~WebViewGuestDelegate() { +} + +void WebViewGuestDelegate::Initialize(api::WebContents* api_web_contents) { + api_web_contents_ = api_web_contents; + Observe(api_web_contents->GetWebContents()); +} + +void WebViewGuestDelegate::Destroy() { + // Give the content module an opportunity to perform some cleanup. + guest_host_->WillDestroy(); + guest_host_ = nullptr; +} + +void WebViewGuestDelegate::SetSize(const SetSizeParams& params) { + bool enable_auto_size = + params.enable_auto_size ? *params.enable_auto_size : auto_size_enabled_; + gfx::Size min_size = params.min_size ? *params.min_size : min_auto_size_; + gfx::Size max_size = params.max_size ? *params.max_size : max_auto_size_; + + if (params.normal_size) + normal_size_ = *params.normal_size; + + min_auto_size_ = min_size; + min_auto_size_.SetToMin(max_size); + max_auto_size_ = max_size; + max_auto_size_.SetToMax(min_size); + + enable_auto_size &= !min_auto_size_.IsEmpty() && !max_auto_size_.IsEmpty(); + + auto rvh = web_contents()->GetRenderViewHost(); + if (enable_auto_size) { + // Autosize is being enabled. + rvh->EnableAutoResize(min_auto_size_, max_auto_size_); + normal_size_.SetSize(0, 0); + } else { + // Autosize is being disabled. + // Use default width/height if missing from partially defined normal size. + if (normal_size_.width() && !normal_size_.height()) + normal_size_.set_height(GetDefaultSize().height()); + if (!normal_size_.width() && normal_size_.height()) + normal_size_.set_width(GetDefaultSize().width()); + + gfx::Size new_size; + if (!normal_size_.IsEmpty()) { + new_size = normal_size_; + } else if (!guest_size_.IsEmpty()) { + new_size = guest_size_; + } else { + new_size = GetDefaultSize(); + } + + if (auto_size_enabled_) { + // Autosize was previously enabled. + rvh->DisableAutoResize(new_size); + GuestSizeChangedDueToAutoSize(guest_size_, new_size); + } else { + // Autosize was already disabled. + guest_host_->SizeContents(new_size); + } + + guest_size_ = new_size; + } + + 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) { + if (embedder_web_contents_) + 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) { + api_web_contents_->Emit("load-commit", url, !render_frame_host->GetParent()); +} + +void WebViewGuestDelegate::DidAttach(int guest_proxy_routing_id) { + api_web_contents_->Emit("did-attach"); +} + +content::WebContents* WebViewGuestDelegate::GetOwnerWebContents() const { + return embedder_web_contents_; +} + +void WebViewGuestDelegate::GuestSizeChanged(const gfx::Size& new_size) { + if (!auto_size_enabled_) + return; + GuestSizeChangedDueToAutoSize(guest_size_, new_size); + guest_size_ = new_size; +} + +void WebViewGuestDelegate::SetGuestHost(content::GuestHost* guest_host) { + guest_host_ = guest_host; +} + +void WebViewGuestDelegate::WillAttach( + content::WebContents* embedder_web_contents, + int element_instance_id, + bool is_full_page_plugin, + const base::Closure& completion_callback) { + embedder_web_contents_ = embedder_web_contents; + is_full_page_plugin_ = is_full_page_plugin; + completion_callback.Run(); +} + +void WebViewGuestDelegate::GuestSizeChangedDueToAutoSize( + const gfx::Size& old_size, const gfx::Size& new_size) { + api_web_contents_->Emit("size-changed", + old_size.width(), old_size.height(), + new_size.width(), new_size.height()); +} + +gfx::Size WebViewGuestDelegate::GetDefaultSize() const { + if (is_full_page_plugin_) { + // Full page plugins default to the size of the owner's viewport. + return embedder_web_contents_->GetRenderWidgetHostView() + ->GetVisibleViewportSize(); + } else { + return gfx::Size(kDefaultWidth, kDefaultHeight); + } +} + +} // namespace atom diff --git a/atom/browser/web_view_guest_delegate.h b/atom/browser/web_view_guest_delegate.h new file mode 100644 index 000000000000..65e0bcde1916 --- /dev/null +++ b/atom/browser/web_view_guest_delegate.h @@ -0,0 +1,126 @@ +// 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_BROWSER_WEB_VIEW_GUEST_DELEGATE_H_ +#define ATOM_BROWSER_WEB_VIEW_GUEST_DELEGATE_H_ + +#include "content/public/browser/browser_plugin_guest_delegate.h" +#include "content/public/browser/web_contents_observer.h" + +namespace content { +struct NativeWebKeyboardEvent; +} + +namespace atom { + +namespace api { +class WebContents; +} + +// A struct of parameters for SetSize(). The parameters are all declared as +// scoped pointers since they are all optional. Null pointers indicate that the +// parameter has not been provided, and the last used value should be used. Note +// that when |enable_auto_size| is true, providing |normal_size| is not +// meaningful. This is because the normal size of the guestview is overridden +// whenever autosizing occurs. +struct SetSizeParams { + SetSizeParams() {} + ~SetSizeParams() {} + + scoped_ptr enable_auto_size; + scoped_ptr min_size; + scoped_ptr max_size; + scoped_ptr normal_size; +}; + +class WebViewGuestDelegate : public content::BrowserPluginGuestDelegate, + public content::WebContentsObserver { + public: + WebViewGuestDelegate(); + ~WebViewGuestDelegate() override; + + void Initialize(api::WebContents* api_web_contents); + + // Called when the WebContents is going to be destroyed. + void Destroy(); + + // Used to toggle autosize mode for this GuestView, and set both the automatic + // 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; + + // content::BrowserPluginGuestDelegate: + void DidAttach(int guest_proxy_routing_id) final; + content::WebContents* GetOwnerWebContents() const final; + void GuestSizeChanged(const gfx::Size& new_size) final; + void SetGuestHost(content::GuestHost* guest_host) final; + void WillAttach(content::WebContents* embedder_web_contents, + int element_instance_id, + bool is_full_page_plugin, + const base::Closure& completion_callback) final; + + private: + // This method is invoked when the contents auto-resized to give the container + // an opportunity to match it if it wishes. + // + // This gives the derived class an opportunity to inform its container element + // or perform other actions. + void GuestSizeChangedDueToAutoSize(const gfx::Size& old_size, + const gfx::Size& new_size); + + // 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_; + + // The size of the container element. + gfx::Size element_size_; + + // The size of the guest content. Note: In autosize mode, the container + // element may not match the size of the guest. + gfx::Size guest_size_; + + // A pointer to the guest_host. + content::GuestHost* guest_host_; + + // Indicates whether autosize mode is enabled or not. + bool auto_size_enabled_; + + // The maximum size constraints of the container element in autosize mode. + gfx::Size max_auto_size_; + + // The minimum size constraints of the container element in autosize mode. + gfx::Size min_auto_size_; + + // The size that will be used when autosize mode is disabled. + gfx::Size normal_size_; + + // Whether the guest view is inside a plugin document. + bool is_full_page_plugin_; + + api::WebContents* api_web_contents_; + + DISALLOW_COPY_AND_ASSIGN(WebViewGuestDelegate); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_WEB_VIEW_GUEST_DELEGATE_H_ diff --git a/atom/browser/web_view_manager.cc b/atom/browser/web_view_manager.cc index 77a772da9adf..d404c1a43680 100644 --- a/atom/browser/web_view_manager.cc +++ b/atom/browser/web_view_manager.cc @@ -10,45 +10,7 @@ namespace atom { -namespace { - -WebViewManager* GetManagerFromProcess(content::RenderProcessHost* process) { - if (!process) - return nullptr; - auto context = process->GetBrowserContext(); - if (!context) - return nullptr; - return static_cast(context->GetGuestManager()); -} - -} // namespace - -// static -bool WebViewManager::GetInfoForProcess(content::RenderProcessHost* process, - WebViewInfo* info) { - auto manager = GetManagerFromProcess(process); - if (!manager) - return false; - return manager->GetInfo(process->GetID(), info); -} - -// static -void WebViewManager::UpdateGuestProcessID( - content::RenderProcessHost* old_process, - content::RenderProcessHost* new_process) { - auto manager = GetManagerFromProcess(old_process); - if (manager) { - base::AutoLock auto_lock(manager->lock_); - int old_id = old_process->GetID(); - int new_id = new_process->GetID(); - if (!ContainsKey(manager->webview_info_map_, old_id)) - return; - manager->webview_info_map_[new_id] = manager->webview_info_map_[old_id]; - manager->webview_info_map_.erase(old_id); - } -} - -WebViewManager::WebViewManager(content::BrowserContext* context) { +WebViewManager::WebViewManager() { } WebViewManager::~WebViewManager() { @@ -57,13 +19,8 @@ WebViewManager::~WebViewManager() { void WebViewManager::AddGuest(int guest_instance_id, int element_instance_id, content::WebContents* embedder, - content::WebContents* web_contents, - const WebViewInfo& info) { - base::AutoLock auto_lock(lock_); - web_contents_embdder_map_[guest_instance_id] = { web_contents, embedder }; - - int guest_process_id = web_contents->GetRenderProcessHost()->GetID(); - webview_info_map_[guest_process_id] = info; + content::WebContents* web_contents) { + web_contents_embedder_map_[guest_instance_id] = { web_contents, embedder }; // Map the element in embedder to guest. int owner_process_id = embedder->GetRenderProcessHost()->GetID(); @@ -72,15 +29,10 @@ void WebViewManager::AddGuest(int guest_instance_id, } void WebViewManager::RemoveGuest(int guest_instance_id) { - base::AutoLock auto_lock(lock_); - if (!ContainsKey(web_contents_embdder_map_, guest_instance_id)) + if (!ContainsKey(web_contents_embedder_map_, guest_instance_id)) return; - auto web_contents = web_contents_embdder_map_[guest_instance_id].web_contents; - web_contents_embdder_map_.erase(guest_instance_id); - - int guest_process_id = web_contents->GetRenderProcessHost()->GetID(); - webview_info_map_.erase(guest_process_id); + web_contents_embedder_map_.erase(guest_instance_id); // Remove the record of element in embedder too. for (const auto& element : element_instance_id_to_guest_map_) @@ -90,16 +42,6 @@ void WebViewManager::RemoveGuest(int guest_instance_id) { } } -bool WebViewManager::GetInfo(int guest_process_id, WebViewInfo* webview_info) { - base::AutoLock auto_lock(lock_); - WebViewInfoMap::iterator iter = webview_info_map_.find(guest_process_id); - if (iter != webview_info_map_.end()) { - *webview_info = iter->second; - return true; - } - return false; -} - content::WebContents* WebViewManager::GetGuestByInstanceID( int owner_process_id, int element_instance_id) { @@ -108,15 +50,15 @@ content::WebContents* WebViewManager::GetGuestByInstanceID( return nullptr; int guest_instance_id = element_instance_id_to_guest_map_[key]; - if (ContainsKey(web_contents_embdder_map_, guest_instance_id)) - return web_contents_embdder_map_[guest_instance_id].web_contents; + if (ContainsKey(web_contents_embedder_map_, guest_instance_id)) + return web_contents_embedder_map_[guest_instance_id].web_contents; else return nullptr; } bool WebViewManager::ForEachGuest(content::WebContents* embedder_web_contents, const GuestCallback& callback) { - for (auto& item : web_contents_embdder_map_) + for (auto& item : web_contents_embedder_map_) if (item.second.embedder == embedder_web_contents && callback.Run(item.second.web_contents)) return true; diff --git a/atom/browser/web_view_manager.h b/atom/browser/web_view_manager.h index 2327b2e02b4c..ff9a8ecba2ab 100644 --- a/atom/browser/web_view_manager.h +++ b/atom/browser/web_view_manager.h @@ -7,51 +7,21 @@ #include -#include "base/files/file_path.h" -#include "base/synchronization/lock.h" #include "content/public/browser/browser_plugin_guest_manager.h" -namespace content { -class BrowserContext; -class RenderProcessHost; -} - namespace atom { class WebViewManager : public content::BrowserPluginGuestManager { public: - struct WebViewInfo { - int guest_instance_id; - content::WebContents* embedder; - bool node_integration; - bool plugins; - bool disable_web_security; - base::FilePath preload_script; - }; - - // Finds the WebViewManager attached with |process| and returns the - // WebViewInfo of it. - static bool GetInfoForProcess(content::RenderProcessHost* process, - WebViewInfo* info); - - // Updates the guest process ID. - static void UpdateGuestProcessID(content::RenderProcessHost* old_process, - content::RenderProcessHost* new_process); - - explicit WebViewManager(content::BrowserContext* context); - virtual ~WebViewManager(); + WebViewManager(); + ~WebViewManager() override; void AddGuest(int guest_instance_id, int element_instance_id, content::WebContents* embedder, - content::WebContents* web_contents, - const WebViewInfo& info); + content::WebContents* web_contents); void RemoveGuest(int guest_instance_id); - // Looks up the information for the embedder for a given render - // view, if one exists. Called on the IO thread. - bool GetInfo(int guest_process_id, WebViewInfo* webview_info); - protected: // content::BrowserPluginGuestManager: content::WebContents* GetGuestByInstanceID(int owner_process_id, @@ -65,7 +35,7 @@ class WebViewManager : public content::BrowserPluginGuestManager { content::WebContents* embedder; }; // guest_instance_id => (web_contents, embedder) - std::map web_contents_embdder_map_; + std::map web_contents_embedder_map_; struct ElementInstanceKey { int embedder_process_id; @@ -89,12 +59,6 @@ class WebViewManager : public content::BrowserPluginGuestManager { // (embedder_process_id, element_instance_id) => guest_instance_id std::map element_instance_id_to_guest_map_; - typedef std::map WebViewInfoMap; - // guest_process_id => (guest_instance_id, embedder, ...) - WebViewInfoMap webview_info_map_; - - base::Lock lock_; - DISALLOW_COPY_AND_ASSIGN(WebViewManager); }; diff --git a/atom/browser/window_list.cc b/atom/browser/window_list.cc index 9a2089e57434..b3bec5d08c1c 100644 --- a/atom/browser/window_list.cc +++ b/atom/browser/window_list.cc @@ -13,7 +13,7 @@ namespace atom { // static -base::LazyInstance>::Leaky +base::LazyInstance>::Leaky WindowList::observers_ = LAZY_INSTANCE_INITIALIZER; // static diff --git a/atom/browser/window_list.h b/atom/browser/window_list.h index 7ba5a7957561..bfb9a2b0aecc 100644 --- a/atom/browser/window_list.h +++ b/atom/browser/window_list.h @@ -60,7 +60,8 @@ class WindowList { // A list of observers which will be notified of every window addition and // removal across all WindowLists. - static base::LazyInstance>::Leaky observers_; + static base::LazyInstance>::Leaky + observers_; static WindowList* instance_; diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index eeb26614847b..b32df3cef39d 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -34,6 +34,10 @@ IPC_MESSAGE_ROUTED2(AtomViewMsg_Message, base::string16 /* channel */, base::ListValue /* arguments */) +IPC_MESSAGE_ROUTED2(AtomViewMsg_ExecuteJavaScript, + base::string16 /* code */, + bool /* has user gesture */) + // Sent by the renderer when the draggable regions are updated. IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions, std::vector /* regions */) diff --git a/atom/common/api/atom_api_asar.cc b/atom/common/api/atom_api_asar.cc index 3fd2ca6f86f7..4ea7d8c5c362 100644 --- a/atom/common/api/atom_api_asar.cc +++ b/atom/common/api/atom_api_asar.cc @@ -8,20 +8,19 @@ #include "atom_natives.h" // NOLINT: This file is generated with coffee2c. #include "atom/common/asar/archive.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/file_path_converter.h" +#include "atom/common/node_includes.h" #include "native_mate/arguments.h" -#include "native_mate/callback.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "native_mate/wrappable.h" -#include "atom/common/node_includes.h" - namespace { class Archive : public mate::Wrappable { public: - static v8::Handle Create(v8::Isolate* isolate, + static v8::Local Create(v8::Isolate* isolate, const base::FilePath& path) { scoped_ptr archive(new asar::Archive(path)); if (!archive->Init()) @@ -34,7 +33,7 @@ class Archive : public mate::Wrappable { : archive_(archive.Pass()) {} // Reads the offset and size of file. - v8::Handle GetFileInfo(v8::Isolate* isolate, + v8::Local GetFileInfo(v8::Isolate* isolate, const base::FilePath& path) { asar::Archive::FileInfo info; if (!archive_ || !archive_->GetFileInfo(path, &info)) @@ -47,7 +46,7 @@ class Archive : public mate::Wrappable { } // Returns a fake result of fs.stat(path). - v8::Handle Stat(v8::Isolate* isolate, + v8::Local Stat(v8::Isolate* isolate, const base::FilePath& path) { asar::Archive::Stats stats; if (!archive_ || !archive_->Stat(path, &stats)) @@ -62,7 +61,7 @@ class Archive : public mate::Wrappable { } // Returns all files under a directory. - v8::Handle Readdir(v8::Isolate* isolate, + v8::Local Readdir(v8::Isolate* isolate, const base::FilePath& path) { std::vector files; if (!archive_ || !archive_->Readdir(path, &files)) @@ -71,7 +70,7 @@ class Archive : public mate::Wrappable { } // Returns the path of file with symbol link resolved. - v8::Handle Realpath(v8::Isolate* isolate, + v8::Local Realpath(v8::Isolate* isolate, const base::FilePath& path) { base::FilePath realpath; if (!archive_ || !archive_->Realpath(path, &realpath)) @@ -80,7 +79,7 @@ class Archive : public mate::Wrappable { } // Copy the file out into a temporary file and returns the new path. - v8::Handle CopyFileOut(v8::Isolate* isolate, + v8::Local CopyFileOut(v8::Isolate* isolate, const base::FilePath& path) { base::FilePath new_path; if (!archive_ || !archive_->CopyFileOut(path, &new_path)) @@ -120,8 +119,8 @@ class Archive : public mate::Wrappable { }; void InitAsarSupport(v8::Isolate* isolate, - v8::Handle process, - v8::Handle require) { + v8::Local process, + v8::Local require) { // Evaluate asar_init.coffee. v8::Local asar_init = v8::Script::Compile(v8::String::NewFromUtf8( isolate, @@ -131,8 +130,8 @@ void InitAsarSupport(v8::Isolate* isolate, v8::Local result = asar_init->Run(); // Initialize asar support. - base::Callback, - v8::Handle, + base::Callback, + v8::Local, std::string)> init; if (mate::ConvertFromV8(isolate, result, &init)) { init.Run(process, @@ -141,8 +140,8 @@ void InitAsarSupport(v8::Isolate* isolate, } } -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("createArchive", &Archive::Create); dict.SetMethod("initAsarSupport", &InitAsarSupport); diff --git a/atom/common/api/atom_api_clipboard.cc b/atom/common/api/atom_api_clipboard.cc index 349b35ce5853..0837a3dc93f0 100644 --- a/atom/common/api/atom_api_clipboard.cc +++ b/atom/common/api/atom_api_clipboard.cc @@ -7,6 +7,8 @@ #include "atom/common/native_mate_converters/image_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" +#include "base/strings/utf_string_conversions.h" +#include "native_mate/arguments.h" #include "native_mate/dictionary.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/clipboard/clipboard.h" @@ -15,36 +17,32 @@ #include "atom/common/node_includes.h" -namespace mate { - -template<> -struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Handle val, - ui::ClipboardType* out) { - std::string type; - if (!Converter::FromV8(isolate, val, &type)) - return false; - - if (type == "selection") - *out = ui::CLIPBOARD_TYPE_SELECTION; - else - *out = ui::CLIPBOARD_TYPE_COPY_PASTE; - return true; - } -}; - -} // namespace mate - namespace { -bool Has(const std::string& format_string, ui::ClipboardType type) { +ui::ClipboardType GetClipboardType(mate::Arguments* args) { + std::string type; + if (args->GetNext(&type) && type == "selection") + return ui::CLIPBOARD_TYPE_SELECTION; + else + return ui::CLIPBOARD_TYPE_COPY_PASTE; +} + +std::vector AvailableFormats(mate::Arguments* args) { + std::vector format_types; + bool ignore; + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + clipboard->ReadAvailableTypes(GetClipboardType(args), &format_types, &ignore); + return format_types; +} + +bool Has(const std::string& format_string, mate::Arguments* args) { ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); ui::Clipboard::FormatType format(ui::Clipboard::GetFormatType(format_string)); - return clipboard->IsFormatAvailable(format, type); + return clipboard->IsFormatAvailable(format, GetClipboardType(args)); } std::string Read(const std::string& format_string, - ui::ClipboardType type) { + mate::Arguments* args) { ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); ui::Clipboard::FormatType format(ui::Clipboard::GetFormatType(format_string)); @@ -53,41 +51,89 @@ std::string Read(const std::string& format_string, return data; } -base::string16 ReadText(ui::ClipboardType type) { +void Write(const mate::Dictionary& data, + mate::Arguments* args) { + ui::ScopedClipboardWriter writer(GetClipboardType(args)); + base::string16 text, html; + gfx::Image image; + + if (data.Get("text", &text)) + writer.WriteText(text); + + if (data.Get("html", &html)) + writer.WriteHTML(html, std::string()); + + if (data.Get("image", &image)) + writer.WriteImage(image.AsBitmap()); +} + +base::string16 ReadText(mate::Arguments* args) { base::string16 data; - ui::Clipboard::GetForCurrentThread()->ReadText(type, &data); + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + auto type = GetClipboardType(args); + if (clipboard->IsFormatAvailable( + ui::Clipboard::GetPlainTextWFormatType(), type)) { + clipboard->ReadText(type, &data); + } else if (clipboard->IsFormatAvailable( + ui::Clipboard::GetPlainTextFormatType(), type)) { + std::string result; + clipboard->ReadAsciiText(type, &result); + data = base::ASCIIToUTF16(result); + } return data; } -void WriteText(const base::string16& text, ui::ClipboardType type) { - ui::ScopedClipboardWriter writer(type); +void WriteText(const base::string16& text, mate::Arguments* args) { + ui::ScopedClipboardWriter writer(GetClipboardType(args)); writer.WriteText(text); } -gfx::Image ReadImage(ui::ClipboardType type) { - SkBitmap bitmap = ui::Clipboard::GetForCurrentThread()->ReadImage(type); +base::string16 ReadHtml(mate::Arguments* args) { + base::string16 data; + base::string16 html; + std::string url; + uint32 start; + uint32 end; + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + clipboard->ReadHTML(GetClipboardType(args), &html, &url, &start, &end); + data = html.substr(start, end - start); + return data; +} + +void WriteHtml(const base::string16& html, mate::Arguments* args) { + ui::ScopedClipboardWriter writer(GetClipboardType(args)); + writer.WriteHTML(html, std::string()); +} + +gfx::Image ReadImage(mate::Arguments* args) { + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); + SkBitmap bitmap = clipboard->ReadImage(GetClipboardType(args)); return gfx::Image::CreateFrom1xBitmap(bitmap); } -void WriteImage(const gfx::Image& image, ui::ClipboardType type) { - ui::ScopedClipboardWriter writer(type); +void WriteImage(const gfx::Image& image, mate::Arguments* args) { + ui::ScopedClipboardWriter writer(GetClipboardType(args)); writer.WriteImage(image.AsBitmap()); } -void Clear(ui::ClipboardType type) { - ui::Clipboard::GetForCurrentThread()->Clear(type); +void Clear(mate::Arguments* args) { + ui::Clipboard::GetForCurrentThread()->Clear(GetClipboardType(args)); } -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); - dict.SetMethod("_has", &Has); - dict.SetMethod("_read", &Read); - dict.SetMethod("_readText", &ReadText); - dict.SetMethod("_writeText", &WriteText); - dict.SetMethod("_readImage", &ReadImage); - dict.SetMethod("_writeImage", &WriteImage); - dict.SetMethod("_clear", &Clear); + dict.SetMethod("availableFormats", &AvailableFormats); + dict.SetMethod("has", &Has); + dict.SetMethod("read", &Read); + dict.SetMethod("write", &Write); + dict.SetMethod("readText", &ReadText); + dict.SetMethod("writeText", &WriteText); + dict.SetMethod("readHtml", &ReadHtml); + dict.SetMethod("writeHtml", &WriteHtml); + dict.SetMethod("readImage", &ReadImage); + dict.SetMethod("writeImage", &WriteImage); + dict.SetMethod("clear", &Clear); } } // namespace diff --git a/atom/common/api/atom_api_crash_reporter.cc b/atom/common/api/atom_api_crash_reporter.cc index ba44be90b17e..e1932ad7f5f0 100644 --- a/atom/common/api/atom_api_crash_reporter.cc +++ b/atom/common/api/atom_api_crash_reporter.cc @@ -11,37 +11,52 @@ #include "atom/common/node_includes.h" +using crash_reporter::CrashReporter; + namespace mate { template<> struct Converter > { static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, std::map* out) { if (!val->IsObject()) return false; - v8::Handle dict = val->ToObject(); - v8::Handle keys = dict->GetOwnPropertyNames(); + v8::Local dict = val->ToObject(); + v8::Local keys = dict->GetOwnPropertyNames(); for (uint32_t i = 0; i < keys->Length(); ++i) { - v8::Handle key = keys->Get(i); + v8::Local key = keys->Get(i); (*out)[V8ToString(key)] = V8ToString(dict->Get(key)); } return true; } }; +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const CrashReporter::UploadReportResult& reports) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + dict.Set("date", v8::Date::New(isolate, reports.first*1000.0)); + dict.Set("id", reports.second); + return dict.GetHandle(); + } +}; + } // namespace mate namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { - using crash_reporter::CrashReporter; + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); + auto report = base::Unretained(CrashReporter::GetInstance()); dict.SetMethod("start", - base::Bind(&CrashReporter::Start, - base::Unretained(CrashReporter::GetInstance()))); + base::Bind(&CrashReporter::Start, report)); + dict.SetMethod("_getUploadedReports", + base::Bind(&CrashReporter::GetUploadedReports, report)); } } // namespace diff --git a/atom/common/api/atom_api_id_weak_map.cc b/atom/common/api/atom_api_id_weak_map.cc deleted file mode 100644 index cddf2d2dd4db..000000000000 --- a/atom/common/api/atom_api_id_weak_map.cc +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/common/api/atom_api_id_weak_map.h" - -#include - -#include "base/logging.h" -#include "native_mate/constructor.h" -#include "native_mate/object_template_builder.h" - -#include "atom/common/node_includes.h" - -namespace atom { - -namespace api { - -IDWeakMap::IDWeakMap() - : next_id_(0) { -} - -IDWeakMap::~IDWeakMap() { -} - -int32_t IDWeakMap::Add(v8::Isolate* isolate, v8::Handle object) { - int32_t key = GetNextID(); - object->SetHiddenValue(mate::StringToV8(isolate, "IDWeakMapKey"), - mate::Converter::ToV8(isolate, key)); - - map_[key] = new mate::RefCountedPersistent(isolate, object); - map_[key]->SetWeak(this, WeakCallback); - return key; -} - -v8::Handle IDWeakMap::Get(v8::Isolate* isolate, int32_t key) { - if (!Has(key)) { - node::ThrowError("Invalid key"); - return v8::Undefined(isolate); - } - - return map_[key]->NewHandle(); -} - -bool IDWeakMap::Has(int32_t key) const { - return map_.find(key) != map_.end(); -} - -std::vector IDWeakMap::Keys() const { - std::vector keys; - keys.reserve(map_.size()); - for (auto it = map_.begin(); it != map_.end(); ++it) - keys.push_back(it->first); - return keys; -} - -void IDWeakMap::Remove(int32_t key) { - if (Has(key)) - map_.erase(key); - else - LOG(WARNING) << "Object with key " << key << " is being GCed for twice."; -} - -int IDWeakMap::GetNextID() { - return ++next_id_; -} - -// static -void IDWeakMap::BuildPrototype(v8::Isolate* isolate, - v8::Handle prototype) { - mate::ObjectTemplateBuilder(isolate, prototype) - .SetMethod("add", &IDWeakMap::Add) - .SetMethod("get", &IDWeakMap::Get) - .SetMethod("has", &IDWeakMap::Has) - .SetMethod("keys", &IDWeakMap::Keys) - .SetMethod("remove", &IDWeakMap::Remove); -} - -// static -void IDWeakMap::WeakCallback( - const v8::WeakCallbackData& data) { - int32_t key = data.GetValue()->GetHiddenValue( - mate::StringToV8(data.GetIsolate(), "IDWeakMapKey"))->Int32Value(); - data.GetParameter()->Remove(key); -} - -} // namespace api - -} // namespace atom - - -namespace { - -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { - using atom::api::IDWeakMap; - v8::Isolate* isolate = context->GetIsolate(); - v8::Local constructor = mate::CreateConstructor( - isolate, - "IDWeakMap", - base::Bind(&mate::NewOperatorFactory)); - exports->Set(mate::StringToV8(isolate, "IDWeakMap"), constructor); -} - -} // namespace - -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_id_weak_map, Initialize) diff --git a/atom/common/api/atom_api_id_weak_map.h b/atom/common/api/atom_api_id_weak_map.h deleted file mode 100644 index e435da189f01..000000000000 --- a/atom/common/api/atom_api_id_weak_map.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2013 GitHub, Inc. -// Copyright (c) 2012 Intel Corp. All rights reserved. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_COMMON_API_ATOM_API_ID_WEAK_MAP_H_ -#define ATOM_COMMON_API_ATOM_API_ID_WEAK_MAP_H_ - -#include -#include - -#include "base/basictypes.h" -#include "native_mate/scoped_persistent.h" -#include "native_mate/wrappable.h" - -namespace atom { - -namespace api { - -// Like ES6's WeakMap, but the key is Integer and the value is Weak Pointer. -class IDWeakMap : public mate::Wrappable { - public: - IDWeakMap(); - - static void BuildPrototype(v8::Isolate* isolate, - v8::Handle prototype); - - private: - virtual ~IDWeakMap(); - - int32_t Add(v8::Isolate* isolate, v8::Handle object); - v8::Handle Get(v8::Isolate* isolate, int32_t key); - bool Has(int32_t key) const; - std::vector Keys() const; - void Remove(int32_t key); - int GetNextID(); - - static void WeakCallback( - const v8::WeakCallbackData& data); - - int32_t next_id_; - - typedef scoped_refptr > - RefCountedV8Object; - std::map map_; - - DISALLOW_COPY_AND_ASSIGN(IDWeakMap); -}; - -} // namespace api - -} // namespace atom - -#endif // ATOM_COMMON_API_ATOM_API_ID_WEAK_MAP_H_ diff --git a/atom/common/api/atom_api_native_image.cc b/atom/common/api/atom_api_native_image.cc index 23da1b9f1761..df6c14dab350 100644 --- a/atom/common/api/atom_api_native_image.cc +++ b/atom/common/api/atom_api_native_image.cc @@ -11,8 +11,10 @@ #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" +#include "atom/common/node_includes.h" #include "base/base64.h" #include "base/strings/string_util.h" +#include "base/strings/pattern.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" #include "net/base/data_url.h" @@ -23,7 +25,11 @@ #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_util.h" -#include "atom/common/node_includes.h" +#if defined(OS_WIN) +#include "atom/common/asar/archive.h" +#include "base/win/scoped_gdi_object.h" +#include "ui/gfx/icon_util.h" +#endif namespace atom { @@ -57,7 +63,7 @@ float GetScaleFactorFromPath(const base::FilePath& path) { // We don't try to convert string to float here because it is very very // expensive. for (unsigned i = 0; i < arraysize(kScaleFactorPairs); ++i) { - if (EndsWith(filename, kScaleFactorPairs[i].name, true)) + if (base::EndsWith(filename, kScaleFactorPairs[i].name, true)) return kScaleFactorPairs[i].scale; } @@ -78,7 +84,7 @@ bool AddImageSkiaRep(gfx::ImageSkia* image, if (!decoded) return false; - image->AddRepresentation(gfx::ImageSkiaRep(*decoded.release(), scale_factor)); + image->AddRepresentation(gfx::ImageSkiaRep(*decoded, scale_factor)); return true; } @@ -99,7 +105,7 @@ bool PopulateImageSkiaRepsFromPath(gfx::ImageSkia* image, const base::FilePath& path) { bool succeed = false; std::string filename(path.BaseName().RemoveExtension().AsUTF8Unsafe()); - if (MatchPattern(filename, "*@*x")) + if (base::MatchPattern(filename, "*@*x")) // Don't search for other representations if the DPI has been specified. return AddImageSkiaRep(image, path, GetScaleFactorFromPath(path)); else @@ -113,9 +119,36 @@ bool PopulateImageSkiaRepsFromPath(gfx::ImageSkia* image, } #if defined(OS_MACOSX) -bool IsTemplateImage(const base::FilePath& path) { - return (MatchPattern(path.value(), "*Template.*") || - MatchPattern(path.value(), "*Template@*x.*")); +bool IsTemplateFilename(const base::FilePath& path) { + return (base::MatchPattern(path.value(), "*Template.*") || + base::MatchPattern(path.value(), "*Template@*x.*")); +} +#endif + +#if defined(OS_WIN) +bool ReadImageSkiaFromICO(gfx::ImageSkia* image, const base::FilePath& path) { + // If file is in asar archive, we extract it to a temp file so LoadImage can + // load it. + base::FilePath asar_path, relative_path; + base::FilePath image_path(path); + if (asar::GetAsarArchivePath(image_path, &asar_path, &relative_path)) { + std::shared_ptr archive = + asar::GetOrCreateAsarArchive(asar_path); + if (archive) + archive->CopyFileOut(relative_path, &image_path); + } + + // Load the icon from file. + base::win::ScopedHICON icon(static_cast( + LoadImage(NULL, image_path.value().c_str(), IMAGE_ICON, 0, 0, + LR_DEFAULTSIZE | LR_LOADFROMFILE))); + if (!icon) + return false; + + // Convert the icon from the Windows specific HICON to gfx::ImageSkia. + scoped_ptr bitmap(IconUtil::CreateSkBitmapFromHICON(icon)); + image->AddRepresentation(gfx::ImageSkiaRep(*bitmap, 1.0f)); + return true; } #endif @@ -139,25 +172,27 @@ mate::ObjectTemplateBuilder NativeImage::GetObjectTemplateBuilder( .SetMethod("isEmpty", &NativeImage::IsEmpty) .SetMethod("getSize", &NativeImage::GetSize) .SetMethod("setTemplateImage", &NativeImage::SetTemplateImage) + .SetMethod("isTemplateImage", &NativeImage::IsTemplateImage) .Build()); return mate::ObjectTemplateBuilder( isolate, v8::Local::New(isolate, template_)); } -v8::Handle NativeImage::ToPNG(v8::Isolate* isolate) { +v8::Local NativeImage::ToPNG(v8::Isolate* isolate) { scoped_refptr png = image_.As1xPNGBytes(); - return node::Buffer::New(isolate, - reinterpret_cast(png->front()), - png->size()); + return node::Buffer::Copy(isolate, + reinterpret_cast(png->front()), + static_cast(png->size())).ToLocalChecked(); } -v8::Handle NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { +v8::Local NativeImage::ToJPEG(v8::Isolate* isolate, int quality) { std::vector output; gfx::JPEG1xEncodedDataFromImage(image_, quality, &output); - return node::Buffer::New(isolate, - reinterpret_cast(&output.front()), - output.size()); + return node::Buffer::Copy( + isolate, + reinterpret_cast(&output.front()), + static_cast(output.size())).ToLocalChecked(); } std::string NativeImage::ToDataURL() { @@ -180,6 +215,10 @@ gfx::Size NativeImage::GetSize() { #if !defined(OS_MACOSX) void NativeImage::SetTemplateImage(bool setAsTemplate) { } + +bool NativeImage::IsTemplateImage() { + return false; +} #endif // static @@ -213,11 +252,17 @@ mate::Handle NativeImage::CreateFromJPEG( mate::Handle NativeImage::CreateFromPath( v8::Isolate* isolate, const base::FilePath& path) { gfx::ImageSkia image_skia; - PopulateImageSkiaRepsFromPath(&image_skia, path); + if (path.MatchesExtension(FILE_PATH_LITERAL(".ico"))) { +#if defined(OS_WIN) + ReadImageSkiaFromICO(&image_skia, path); +#endif + } else { + PopulateImageSkiaRepsFromPath(&image_skia, path); + } gfx::Image image(image_skia); mate::Handle handle = Create(isolate, image); #if defined(OS_MACOSX) - if (IsTemplateImage(path)) + if (IsTemplateFilename(path)) handle->SetTemplateImage(true); #endif return handle; @@ -225,7 +270,7 @@ mate::Handle NativeImage::CreateFromPath( // static mate::Handle NativeImage::CreateFromBuffer( - mate::Arguments* args, v8::Handle buffer) { + mate::Arguments* args, v8::Local buffer) { double scale_factor = 1.; args->GetNext(&scale_factor); @@ -258,8 +303,8 @@ mate::Handle NativeImage::CreateFromDataURL( namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("createEmpty", &atom::api::NativeImage::CreateEmpty); dict.SetMethod("createFromPath", &atom::api::NativeImage::CreateFromPath); diff --git a/atom/common/api/atom_api_native_image.h b/atom/common/api/atom_api_native_image.h index 081501348868..1f0fe946ba51 100644 --- a/atom/common/api/atom_api_native_image.h +++ b/atom/common/api/atom_api_native_image.h @@ -41,7 +41,7 @@ class NativeImage : public mate::Wrappable { static mate::Handle CreateFromPath( v8::Isolate* isolate, const base::FilePath& path); static mate::Handle CreateFromBuffer( - mate::Arguments* args, v8::Handle buffer); + mate::Arguments* args, v8::Local buffer); static mate::Handle CreateFromDataURL( v8::Isolate* isolate, const GURL& url); @@ -59,14 +59,16 @@ class NativeImage : public mate::Wrappable { v8::Isolate* isolate) override; private: - v8::Handle ToPNG(v8::Isolate* isolate); - v8::Handle ToJPEG(v8::Isolate* isolate, int quality); + v8::Local ToPNG(v8::Isolate* isolate); + v8::Local ToJPEG(v8::Isolate* isolate, int quality); std::string ToDataURL(); bool IsEmpty(); gfx::Size GetSize(); // Mark the image as template image. void SetTemplateImage(bool setAsTemplate); + // Determine if the image is a template image. + bool IsTemplateImage(); gfx::Image image_; diff --git a/atom/common/api/atom_api_native_image_mac.mm b/atom/common/api/atom_api_native_image_mac.mm index a80462766bc2..ad72d4b14924 100644 --- a/atom/common/api/atom_api_native_image_mac.mm +++ b/atom/common/api/atom_api_native_image_mac.mm @@ -14,6 +14,10 @@ void NativeImage::SetTemplateImage(bool setAsTemplate) { [image_.AsNSImage() setTemplate:setAsTemplate]; } +bool NativeImage::IsTemplateImage() { + return [image_.AsNSImage() isTemplate]; +} + } // namespace api } // namespace atom diff --git a/atom/common/api/atom_api_shell.cc b/atom/common/api/atom_api_shell.cc index ecbb99f71aac..a4599ee0c359 100644 --- a/atom/common/api/atom_api_shell.cc +++ b/atom/common/api/atom_api_shell.cc @@ -7,14 +7,13 @@ #include "atom/common/platform_util.h" #include "atom/common/native_mate_converters/file_path_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" -#include "native_mate/dictionary.h" - #include "atom/common/node_includes.h" +#include "native_mate/dictionary.h" namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +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); diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index 9412f406777a..bba3399a8dbd 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -3,52 +3,56 @@ // found in the LICENSE file. #include "atom/common/api/object_life_monitor.h" +#include "atom/common/node_includes.h" #include "native_mate/dictionary.h" #include "v8/include/v8-profiler.h" -#include "atom/common/node_includes.h" - namespace { -v8::Handle CreateObjectWithName(v8::Isolate* isolate, - v8::Handle name) { +v8::Local CreateObjectWithName(v8::Isolate* isolate, + v8::Local name) { v8::Local t = v8::FunctionTemplate::New(isolate); t->SetClassName(name); return t->GetFunction()->NewInstance(); } -v8::Handle GetHiddenValue(v8::Handle object, - v8::Handle key) { +v8::Local GetHiddenValue(v8::Local object, + v8::Local key) { return object->GetHiddenValue(key); } -void SetHiddenValue(v8::Handle object, - v8::Handle key, - v8::Handle value) { +void SetHiddenValue(v8::Local object, + v8::Local key, + v8::Local value) { object->SetHiddenValue(key, value); } -int32_t GetObjectHash(v8::Handle object) { +void DeleteHiddenValue(v8::Local object, + v8::Local key) { + object->DeleteHiddenValue(key); +} + +int32_t GetObjectHash(v8::Local object) { return object->GetIdentityHash(); } void SetDestructor(v8::Isolate* isolate, - v8::Handle object, - v8::Handle callback) { + v8::Local object, + v8::Local callback) { atom::ObjectLifeMonitor::BindTo(isolate, object, callback); } void TakeHeapSnapshot(v8::Isolate* isolate) { - isolate->GetHeapProfiler()->TakeHeapSnapshot( - mate::StringToV8(isolate, "test")); + isolate->GetHeapProfiler()->TakeHeapSnapshot(); } -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +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); dict.SetMethod("getObjectHash", &GetObjectHash); dict.SetMethod("setDestructor", &SetDestructor); dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot); diff --git a/atom/common/api/atom_bindings.cc b/atom/common/api/atom_bindings.cc index 202819eb539e..d6fb355e09d8 100644 --- a/atom/common/api/atom_bindings.cc +++ b/atom/common/api/atom_bindings.cc @@ -11,6 +11,7 @@ #include "atom/common/chrome_version.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "base/logging.h" +#include "base/process/process_metrics.h" #include "native_mate/dictionary.h" #include "atom/common/node_includes.h" @@ -26,6 +27,11 @@ void Crash() { static_cast(NULL)->crash = true; } +void Hang() { + for (;;) + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); +} + // Called when there is a fatal error in V8, we just crash the process here so // we can get the stack trace. void FatalErrorCallback(const char* location, const char* message) { @@ -49,12 +55,16 @@ AtomBindings::~AtomBindings() { } void AtomBindings::BindTo(v8::Isolate* isolate, - v8::Handle process) { + v8::Local process) { v8::V8::SetFatalErrorHandler(FatalErrorCallback); mate::Dictionary dict(isolate, process); dict.SetMethod("crash", &Crash); + dict.SetMethod("hang", &Hang); dict.SetMethod("log", &Log); +#if defined(OS_POSIX) + dict.SetMethod("setFdLimit", &base::SetFdLimit); +#endif dict.SetMethod("activateUvLoop", base::Bind(&AtomBindings::ActivateUVLoop, base::Unretained(this))); @@ -92,6 +102,10 @@ void AtomBindings::OnCallNextTick(uv_async_t* handle) { 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; diff --git a/atom/common/api/atom_bindings.h b/atom/common/api/atom_bindings.h index 1d92f174d5eb..b3536c234030 100644 --- a/atom/common/api/atom_bindings.h +++ b/atom/common/api/atom_bindings.h @@ -24,7 +24,7 @@ class AtomBindings { // Add process.atomBinding function, which behaves like process.binding but // load native code from atom-shell instead. - void BindTo(v8::Isolate* isolate, v8::Handle process); + void BindTo(v8::Isolate* isolate, v8::Local process); private: void ActivateUVLoop(v8::Isolate* isolate); diff --git a/atom/common/api/event_emitter_caller.cc b/atom/common/api/event_emitter_caller.cc new file mode 100644 index 000000000000..94eb9ce9e79a --- /dev/null +++ b/atom/common/api/event_emitter_caller.cc @@ -0,0 +1,31 @@ +// 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 "atom/common/api/event_emitter_caller.h" + +#include "atom/common/api/locker.h" +#include "atom/common/node_includes.h" +#include "base/memory/scoped_ptr.h" +#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" + +namespace mate { + +namespace internal { + +v8::Local CallEmitWithArgs(v8::Isolate* isolate, + v8::Local obj, + ValueVector* args) { + // Perform microtask checkpoint after running JavaScript. + scoped_ptr script_scope( + Locker::IsBrowserProcess() ? + nullptr : new blink::WebScopedRunV8Script(isolate)); + // Use node::MakeCallback to call the callback, and it will also run pending + // tasks in Node.js. + return node::MakeCallback( + isolate, obj, "emit", args->size(), &args->front()); +} + +} // namespace internal + +} // namespace mate diff --git a/atom/common/api/event_emitter_caller.h b/atom/common/api/event_emitter_caller.h new file mode 100644 index 000000000000..a2567da9d109 --- /dev/null +++ b/atom/common/api/event_emitter_caller.h @@ -0,0 +1,53 @@ +// 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_API_EVENT_EMITTER_CALLER_H_ +#define ATOM_COMMON_API_EVENT_EMITTER_CALLER_H_ + +#include + +#include "native_mate/converter.h" + +namespace mate { + +namespace internal { + +using ValueVector = std::vector>; + +v8::Local CallEmitWithArgs(v8::Isolate* isolate, + v8::Local obj, + ValueVector* args); + +} // namespace internal + +// obj.emit.apply(obj, name, args...); +// The caller is responsible of allocating a HandleScope. +template +v8::Local EmitEvent(v8::Isolate* isolate, + v8::Local obj, + const StringType& name, + const internal::ValueVector& args) { + internal::ValueVector concatenated_args = { StringToV8(isolate, name) }; + concatenated_args.reserve(1 + args.size()); + concatenated_args.insert(concatenated_args.end(), args.begin(), args.end()); + return internal::CallEmitWithArgs(isolate, obj, &concatenated_args); +} + +// obj.emit(name, args...); +// The caller is responsible of allocating a HandleScope. +template +v8::Local EmitEvent(v8::Isolate* isolate, + v8::Local obj, + const StringType& name, + const Args&... args) { + internal::ValueVector converted_args = { + StringToV8(isolate, name), + ConvertToV8(isolate, args)..., + }; + return internal::CallEmitWithArgs(isolate, obj, &converted_args); +} + +} // namespace mate + +#endif // ATOM_COMMON_API_EVENT_EMITTER_CALLER_H_ diff --git a/atom/common/api/lib/callbacks-registry.coffee b/atom/common/api/lib/callbacks-registry.coffee index b549d17c7285..8f5eb62916c0 100644 --- a/atom/common/api/lib/callbacks-registry.coffee +++ b/atom/common/api/lib/callbacks-registry.coffee @@ -1,3 +1,5 @@ +savedGlobal = global # the "global.global" might be deleted later + module.exports = class CallbacksRegistry constructor: -> @@ -16,10 +18,10 @@ class CallbacksRegistry @callbacks[id] ? -> call: (id, args...) -> - @get(id).call global, args... + @get(id).call savedGlobal, args... apply: (id, args...) -> - @get(id).apply global, args... + @get(id).apply savedGlobal, args... remove: (id) -> delete @callbacks[id] diff --git a/atom/common/api/lib/clipboard.coffee b/atom/common/api/lib/clipboard.coffee index 582feba4b1ef..5c4bb10d4ae5 100644 --- a/atom/common/api/lib/clipboard.coffee +++ b/atom/common/api/lib/clipboard.coffee @@ -1,9 +1,5 @@ -binding = process.atomBinding 'clipboard' -module.exports = - has: (format, type='standard') -> binding._has format, type - read: (format, type='standard') -> binding._read format, type - readText: (type='standard') -> binding._readText type - writeText: (text, type='standard') -> binding._writeText text, type - readImage: (type='standard') -> binding._readImage type - writeImage: (image, type='standard') -> binding._writeImage image, type - clear: (type='standard') -> binding._clear type +if process.platform is 'linux' and process.type is 'renderer' + # On Linux we could not access clipboard in renderer process. + module.exports = require('remote').require 'clipboard' +else + module.exports = process.atomBinding 'clipboard' diff --git a/atom/common/api/lib/crash-reporter.coffee b/atom/common/api/lib/crash-reporter.coffee index d87ff9ea772f..451fa1729428 100644 --- a/atom/common/api/lib/crash-reporter.coffee +++ b/atom/common/api/lib/crash-reporter.coffee @@ -41,19 +41,22 @@ class CrashReporter start() getLastCrashReport: -> + reports = this.getUploadedReports() + if reports.length > 0 then reports[0] else null + + getUploadedReports: -> tmpdir = if process.platform is 'win32' os.tmpdir() else '/tmp' - log = path.join tmpdir, "#{@productName} Crashes", 'uploads.log' - try - reports = String(fs.readFileSync(log)).split('\n') - return null unless reports.length > 1 - [time, id] = reports[reports.length - 2].split ',' - return {date: new Date(parseInt(time) * 1000), id} - catch e - return null + log = + if process.platform is 'darwin' + path.join tmpdir, "#{@productName} Crashes" + else + path.join tmpdir, "#{@productName} Crashes", 'uploads.log' + binding._getUploadedReports log + crashRepoter = new CrashReporter module.exports = crashRepoter diff --git a/atom/common/api/lib/id-weak-map.coffee b/atom/common/api/lib/id-weak-map.coffee deleted file mode 100644 index 794573ba5e26..000000000000 --- a/atom/common/api/lib/id-weak-map.coffee +++ /dev/null @@ -1,3 +0,0 @@ -IDWeakMap = process.atomBinding('id_weak_map').IDWeakMap - -module.exports = IDWeakMap diff --git a/atom/common/api/lib/original-fs.coffee b/atom/common/api/lib/original-fs.coffee deleted file mode 100644 index e4e47f33120b..000000000000 --- a/atom/common/api/lib/original-fs.coffee +++ /dev/null @@ -1,8 +0,0 @@ -vm = require 'vm' - -# Execute the 'fs.js' and pass the 'exports' to it. -source = '(function (exports, require, module, __filename, __dirname) { ' + - process.binding('natives').originalFs + - '\n});' -fn = vm.runInThisContext source, { filename: 'fs.js' } -fn exports, require, module diff --git a/atom/common/api/locker.cc b/atom/common/api/locker.cc new file mode 100644 index 000000000000..fe0b23479a46 --- /dev/null +++ b/atom/common/api/locker.cc @@ -0,0 +1,17 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#include "atom/common/api/locker.h" + +namespace mate { + +Locker::Locker(v8::Isolate* isolate) { + if (IsBrowserProcess()) + locker_.reset(new v8::Locker(isolate)); +} + +Locker::~Locker() { +} + +} // namespace mate diff --git a/atom/common/api/locker.h b/atom/common/api/locker.h new file mode 100644 index 000000000000..201217ff625a --- /dev/null +++ b/atom/common/api/locker.h @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE.chromium file. + +#ifndef ATOM_COMMON_API_LOCKER_H_ +#define ATOM_COMMON_API_LOCKER_H_ + +#include "base/memory/scoped_ptr.h" +#include "v8/include/v8.h" + +namespace mate { + +// Only lock when lockers are used in current thread. +class Locker { + public: + explicit Locker(v8::Isolate* isolate); + ~Locker(); + + // Returns whether current process is browser process, currently we detect it + // by checking whether current has used V8 Lock, but it might be a bad idea. + static inline bool IsBrowserProcess() { return v8::Locker::IsActive(); } + + private: + void* operator new(size_t size); + void operator delete(void*, size_t); + + scoped_ptr locker_; + + DISALLOW_COPY_AND_ASSIGN(Locker); +}; + +} // namespace mate + +#endif // ATOM_COMMON_API_LOCKER_H_ diff --git a/atom/common/api/object_life_monitor.cc b/atom/common/api/object_life_monitor.cc index ac954d8d54f7..9b7c7fe6d05f 100644 --- a/atom/common/api/object_life_monitor.cc +++ b/atom/common/api/object_life_monitor.cc @@ -5,32 +5,52 @@ #include "atom/common/api/object_life_monitor.h" -#include "native_mate/compat.h" +#include "base/bind.h" +#include "base/message_loop/message_loop.h" namespace atom { // static void ObjectLifeMonitor::BindTo(v8::Isolate* isolate, - v8::Handle target, - v8::Handle destructor) { - target->SetHiddenValue(MATE_STRING_NEW(isolate, "destructor"), destructor); - - ObjectLifeMonitor* olm = new ObjectLifeMonitor(); - olm->handle_.reset(isolate, target); - olm->handle_.SetWeak(olm, WeakCallback); + v8::Local target, + v8::Local destructor) { + new ObjectLifeMonitor(isolate, target, destructor); } -ObjectLifeMonitor::ObjectLifeMonitor() { +ObjectLifeMonitor::ObjectLifeMonitor(v8::Isolate* isolate, + v8::Local target, + v8::Local destructor) + : isolate_(isolate), + context_(isolate, isolate->GetCurrentContext()), + target_(isolate, target), + destructor_(isolate, destructor), + weak_ptr_factory_(this) { + target_.SetWeak(this, OnObjectGC, v8::WeakCallbackType::kParameter); } // static -void ObjectLifeMonitor::WeakCallback( - const v8::WeakCallbackData& data) { - // destructor.call(object, object); - v8::Local obj = data.GetValue(); - v8::Local::Cast(obj->GetHiddenValue( - MATE_STRING_NEW(data.GetIsolate(), "destructor")))->Call(obj, 0, NULL); - delete data.GetParameter(); +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())); +} + +void ObjectLifeMonitor::RunCallback() { + v8::HandleScope handle_scope(isolate_); + v8::Local context = v8::Local::New( + isolate_, context_); + 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 6717c68db52d..90216d8227a5 100644 --- a/atom/common/api/object_life_monitor.h +++ b/atom/common/api/object_life_monitor.h @@ -6,23 +6,32 @@ #define ATOM_COMMON_API_OBJECT_LIFE_MONITOR_H_ #include "base/basictypes.h" -#include "native_mate/scoped_persistent.h" +#include "base/memory/weak_ptr.h" +#include "v8/include/v8.h" namespace atom { class ObjectLifeMonitor { public: static void BindTo(v8::Isolate* isolate, - v8::Handle target, - v8::Handle destructor); + v8::Local target, + v8::Local destructor); private: - ObjectLifeMonitor(); + ObjectLifeMonitor(v8::Isolate* isolate, + v8::Local target, + v8::Local destructor); - static void WeakCallback( - const v8::WeakCallbackData& data); + static void OnObjectGC(const v8::WeakCallbackInfo& data); - mate::ScopedPersistent handle_; + void RunCallback(); + + v8::Isolate* isolate_; + v8::Global context_; + v8::Global target_; + v8::Global destructor_; + + base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(ObjectLifeMonitor); }; diff --git a/atom/common/asar/archive.cc b/atom/common/asar/archive.cc index 98e464650bac..0b2f59ae0f18 100644 --- a/atom/common/asar/archive.cc +++ b/atom/common/asar/archive.cc @@ -15,8 +15,9 @@ #include "base/files/file.h" #include "base/logging.h" #include "base/pickle.h" -#include "base/json/json_string_value_serializer.h" +#include "base/json/json_reader.h" #include "base/strings/string_number_conversions.h" +#include "base/values.h" namespace asar { @@ -53,6 +54,11 @@ bool GetChildNode(const base::DictionaryValue* root, const std::string& name, const base::DictionaryValue* dir, const base::DictionaryValue** out) { + if (name == "") { + *out = root; + return true; + } + const base::DictionaryValue* files = NULL; return GetFilesNode(root, dir, &files) && files->GetDictionaryWithoutPathExpansion(name, out); @@ -130,7 +136,8 @@ bool Archive::Init() { } uint32 size; - if (!PickleIterator(Pickle(buf.data(), buf.size())).ReadUInt32(&size)) { + if (!base::PickleIterator(base::Pickle(buf.data(), buf.size())).ReadUInt32( + &size)) { LOG(ERROR) << "Failed to parse header size from " << path_.value(); return false; } @@ -143,21 +150,22 @@ bool Archive::Init() { } std::string header; - if (!PickleIterator(Pickle(buf.data(), buf.size())).ReadString(&header)) { + if (!base::PickleIterator(base::Pickle(buf.data(), buf.size())).ReadString( + &header)) { LOG(ERROR) << "Failed to parse header from " << path_.value(); return false; } std::string error; - JSONStringValueSerializer serializer(&header); - base::Value* value = serializer.Deserialize(NULL, &error); + base::JSONReader reader; + scoped_ptr value(reader.ReadToValue(header)); if (!value || !value->IsType(base::Value::TYPE_DICTIONARY)) { LOG(ERROR) << "Failed to parse header: " << error; return false; } header_size_ = 8 + size; - header_.reset(static_cast(value)); + header_.reset(static_cast(value.release())); return true; } diff --git a/atom/common/asar/archive.h b/atom/common/asar/archive.h index 2acd17fd7ab4..dda7aa78e0c0 100644 --- a/atom/common/asar/archive.h +++ b/atom/common/asar/archive.h @@ -73,7 +73,8 @@ class Archive { scoped_ptr header_; // Cached external temporary files. - base::ScopedPtrHashMap external_files_; + base::ScopedPtrHashMap> + external_files_; DISALLOW_COPY_AND_ASSIGN(Archive); }; diff --git a/atom/app/atom_main_args.cc b/atom/common/atom_command_line.cc similarity index 66% rename from atom/app/atom_main_args.cc rename to atom/common/atom_command_line.cc index b9ef7c43a34b..2ac62385aeac 100644 --- a/atom/app/atom_main_args.cc +++ b/atom/common/atom_command_line.cc @@ -2,8 +2,10 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include "atom/app/atom_main_args.h" -#include "vendor/node/deps/uv/include/uv.h" +#include "atom/common/atom_command_line.h" + +#include "base/command_line.h" +#include "node/deps/uv/include/uv.h" namespace atom { @@ -19,4 +21,11 @@ void AtomCommandLine::Init(int argc, const char* const* argv) { } } +#if defined(OS_LINUX) +// static +void AtomCommandLine::InitializeFromCommandLine() { + argv_ = base::CommandLine::ForCurrentProcess()->argv(); +} +#endif + } // namespace atom diff --git a/atom/app/atom_main_args.h b/atom/common/atom_command_line.h similarity index 61% rename from atom/app/atom_main_args.h rename to atom/common/atom_command_line.h index c3bc0a9823dc..7c8840f70751 100644 --- a/atom/app/atom_main_args.h +++ b/atom/common/atom_command_line.h @@ -2,13 +2,13 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#ifndef ATOM_APP_ATOM_MAIN_ARGS_H_ -#define ATOM_APP_ATOM_MAIN_ARGS_H_ +#ifndef ATOM_COMMON_ATOM_COMMAND_LINE_H_ +#define ATOM_COMMON_ATOM_COMMAND_LINE_H_ #include #include -#include "base/macros.h" +#include "base/basictypes.h" namespace atom { @@ -18,6 +18,12 @@ class AtomCommandLine { static void Init(int argc, const char* const* argv); static std::vector argv() { return argv_; } +#if defined(OS_LINUX) + // On Linux the command line has to be read from base::CommandLine since + // it is using zygote. + static void InitializeFromCommandLine(); +#endif + private: static std::vector argv_; @@ -26,4 +32,4 @@ class AtomCommandLine { } // namespace atom -#endif // ATOM_APP_ATOM_MAIN_ARGS_H_ +#endif // ATOM_COMMON_ATOM_COMMAND_LINE_H_ diff --git a/atom/common/atom_version.h b/atom/common/atom_version.h index b14baf7ad702..deb09585434e 100644 --- a/atom/common/atom_version.h +++ b/atom/common/atom_version.h @@ -6,7 +6,7 @@ #define ATOM_VERSION_H #define ATOM_MAJOR_VERSION 0 -#define ATOM_MINOR_VERSION 26 +#define ATOM_MINOR_VERSION 33 #define ATOM_PATCH_VERSION 0 #define ATOM_VERSION_IS_RELEASE 1 diff --git a/atom/common/chrome_version.h b/atom/common/chrome_version.h index 6b2ea8d91a45..250051683786 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 "42.0.2311.107" +#define CHROME_VERSION_STRING "45.0.2454.85" #define CHROME_VERSION "v" CHROME_VERSION_STRING #endif // ATOM_COMMON_CHROME_VERSION_H_ diff --git a/atom/common/crash_reporter/crash_reporter.cc b/atom/common/crash_reporter/crash_reporter.cc index 0a02086056b8..59b7fd51e45e 100644 --- a/atom/common/crash_reporter/crash_reporter.cc +++ b/atom/common/crash_reporter/crash_reporter.cc @@ -7,6 +7,9 @@ #include "atom/browser/browser.h" #include "atom/common/atom_version.h" #include "base/command_line.h" +#include "base/files/file_util.h" +#include "base/strings/string_split.h" +#include "base/strings/string_number_conversions.h" #include "content/public/common/content_switches.h" namespace crash_reporter { @@ -39,4 +42,26 @@ void CrashReporter::SetUploadParameters(const StringMap& parameters) { SetUploadParameters(); } +std::vector +CrashReporter::GetUploadedReports(const std::string& path) { + std::string file_content; + std::vector result; + if (base::ReadFileToString(base::FilePath::FromUTF8Unsafe(path), + &file_content)) { + std::vector reports; + base::SplitString(file_content, '\n', &reports); + for (const std::string& report : reports) { + std::vector report_item; + base::SplitString(report, ',', &report_item); + int report_time = 0; + if (report_item.size() >= 2 && base::StringToInt(report_item[0], + &report_time)) { + result.push_back(CrashReporter::UploadReportResult(report_time, + report_item[1])); + } + } + } + return result; +} + } // namespace crash_reporter diff --git a/atom/common/crash_reporter/crash_reporter.h b/atom/common/crash_reporter/crash_reporter.h index 43548a105b6c..c7d58ca3aa76 100644 --- a/atom/common/crash_reporter/crash_reporter.h +++ b/atom/common/crash_reporter/crash_reporter.h @@ -7,6 +7,8 @@ #include #include +#include +#include #include "base/basictypes.h" @@ -15,6 +17,7 @@ namespace crash_reporter { class CrashReporter { public: typedef std::map StringMap; + typedef std::pair UploadReportResult; // upload-date, id static CrashReporter* GetInstance(); @@ -25,6 +28,9 @@ class CrashReporter { bool skip_system_crash_handler, const StringMap& extra_parameters); + virtual std::vector GetUploadedReports( + const std::string& path); + protected: CrashReporter(); virtual ~CrashReporter(); diff --git a/atom/common/crash_reporter/crash_reporter_mac.h b/atom/common/crash_reporter/crash_reporter_mac.h index 882744db3c7e..cbdb3c65feb1 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.h +++ b/atom/common/crash_reporter/crash_reporter_mac.h @@ -6,10 +6,13 @@ #define ATOM_COMMON_CRASH_REPORTER_CRASH_REPORTER_MAC_H_ #include +#include #include "atom/common/crash_reporter/crash_reporter.h" #include "base/compiler_specific.h" -#import "vendor/breakpad/src/client/mac/Framework/Breakpad.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "vendor/crashpad/client/simple_string_dictionary.h" template struct DefaultSingletonTraits; @@ -33,7 +36,14 @@ class CrashReporterMac : public CrashReporter { CrashReporterMac(); virtual ~CrashReporterMac(); - BreakpadRef breakpad_; + void SetUploadsEnabled(bool enable_uploads); + void SetCrashKeyValue(const base::StringPiece& key, + const base::StringPiece& value); + + std::vector GetUploadedReports( + const std::string& path) override; + + scoped_ptr simple_string_dictionary_; DISALLOW_COPY_AND_ASSIGN(CrashReporterMac); }; diff --git a/atom/common/crash_reporter/crash_reporter_mac.mm b/atom/common/crash_reporter/crash_reporter_mac.mm index c251e0e0d100..00f37cc3febb 100644 --- a/atom/common/crash_reporter/crash_reporter_mac.mm +++ b/atom/common/crash_reporter/crash_reporter_mac.mm @@ -4,20 +4,25 @@ #include "atom/common/crash_reporter/crash_reporter_mac.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/mac/bundle_locations.h" #include "base/mac/mac_util.h" #include "base/memory/singleton.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" -#import "vendor/breakpad/src/client/apple/Framework/BreakpadDefines.h" +#include "vendor/crashpad/client/crash_report_database.h" +#include "vendor/crashpad/client/crashpad_client.h" +#include "vendor/crashpad/client/crashpad_info.h" +#include "vendor/crashpad/client/settings.h" namespace crash_reporter { -CrashReporterMac::CrashReporterMac() - : breakpad_(NULL) { +CrashReporterMac::CrashReporterMac() { } CrashReporterMac::~CrashReporterMac() { - if (breakpad_ != NULL) - BreakpadRelease(breakpad_); } void CrashReporterMac::InitBreakpad(const std::string& product_name, @@ -26,54 +31,52 @@ void CrashReporterMac::InitBreakpad(const std::string& product_name, const std::string& submit_url, bool auto_submit, bool skip_system_crash_handler) { - if (breakpad_ != NULL) - BreakpadRelease(breakpad_); - - NSMutableDictionary* parameters = - [NSMutableDictionary dictionaryWithCapacity:4]; - - [parameters setValue:@ATOM_PRODUCT_NAME - forKey:@BREAKPAD_PRODUCT]; - [parameters setValue:base::SysUTF8ToNSString(product_name) - forKey:@BREAKPAD_PRODUCT_DISPLAY]; - [parameters setValue:base::SysUTF8ToNSString(version) - forKey:@BREAKPAD_VERSION]; - [parameters setValue:base::SysUTF8ToNSString(company_name) - forKey:@BREAKPAD_VENDOR]; - [parameters setValue:base::SysUTF8ToNSString(submit_url) - forKey:@BREAKPAD_URL]; - [parameters setValue:(auto_submit ? @"YES" : @"NO") - forKey:@BREAKPAD_SKIP_CONFIRM]; - [parameters setValue:(skip_system_crash_handler ? @"YES" : @"NO") - forKey:@BREAKPAD_SEND_AND_EXIT]; - - // Report all crashes (important for testing the crash reporter). - [parameters setValue:@"0" forKey:@BREAKPAD_REPORT_INTERVAL]; - - // Put dump files under "/tmp/ProductName Crashes". - std::string dump_dir = "/tmp/" + product_name + " Crashes"; - [parameters setValue:base::SysUTF8ToNSString(dump_dir) - forKey:@BREAKPAD_DUMP_DIRECTORY]; - - // Temporarily run Breakpad in-process on 10.10 and later because APIs that - // it depends on got broken (http://crbug.com/386208). - // This can catch crashes in the browser process only. - if (base::mac::IsOSYosemiteOrLater()) { - [parameters setObject:[NSNumber numberWithBool:YES] - forKey:@BREAKPAD_IN_PROCESS]; - } - - breakpad_ = BreakpadCreate(parameters); - if (!breakpad_) { - LOG(ERROR) << "Failed to initialize breakpad"; + // check whether crashpad has been initilized. + // Only need to initilize once. + if (simple_string_dictionary_) return; + + std::string dump_dir = "/tmp/" + product_name + " Crashes"; + base::FilePath database_path(dump_dir); + if (is_browser_) { + @autoreleasepool { + base::FilePath framework_bundle_path = base::mac::FrameworkBundlePath(); + base::FilePath handler_path = + framework_bundle_path.Append("Resources").Append("crashpad_handler"); + + crashpad::CrashpadClient crashpad_client; + if (crashpad_client.StartHandler(handler_path, database_path, + submit_url, + StringMap(), + std::vector())) { + crashpad_client.UseHandler(); + } + } // @autoreleasepool } - for (StringMap::const_iterator iter = upload_parameters_.begin(); - iter != upload_parameters_.end(); ++iter) { - BreakpadAddUploadParameter(breakpad_, - base::SysUTF8ToNSString(iter->first), - base::SysUTF8ToNSString(iter->second)); + crashpad::CrashpadInfo* crashpad_info = + crashpad::CrashpadInfo::GetCrashpadInfo(); + if (skip_system_crash_handler) { + crashpad_info->set_system_crash_reporter_forwarding( + crashpad::TriState::kDisabled); + } + + simple_string_dictionary_.reset(new crashpad::SimpleStringDictionary()); + crashpad_info->set_simple_annotations(simple_string_dictionary_.get()); + + SetCrashKeyValue("prod", ATOM_PRODUCT_NAME); + SetCrashKeyValue("process_type", is_browser_ ? "browser" : "renderer"); + SetCrashKeyValue("ver", version); + + for (const auto& upload_parameter: upload_parameters_) { + SetCrashKeyValue(upload_parameter.first, upload_parameter.second); + } + if (is_browser_) { + scoped_ptr database = + crashpad::CrashReportDatabase::Initialize(database_path); + if (database) { + database->GetSettings()->SetUploadsEnabled(auto_submit); + } } } @@ -81,6 +84,46 @@ void CrashReporterMac::SetUploadParameters() { upload_parameters_["platform"] = "darwin"; } +void CrashReporterMac::SetCrashKeyValue(const base::StringPiece& key, + const base::StringPiece& value) { + simple_string_dictionary_->SetKeyValue(key.data(), value.data()); +} + +std::vector +CrashReporterMac::GetUploadedReports(const std::string& path) { + std::vector uploaded_reports; + + base::FilePath file_path(path); + if (!base::PathExists(file_path)) { + return uploaded_reports; + } + // Load crashpad database. + scoped_ptr database = + crashpad::CrashReportDatabase::Initialize(file_path); + DCHECK(database); + + std::vector completed_reports; + crashpad::CrashReportDatabase::OperationStatus status = + database->GetCompletedReports(&completed_reports); + if (status != crashpad::CrashReportDatabase::kNoError) { + return uploaded_reports; + } + + for (const crashpad::CrashReportDatabase::Report& completed_report : + completed_reports) { + if (completed_report.uploaded) { + uploaded_reports.push_back( + UploadReportResult(static_cast(completed_report.creation_time), + completed_report.id)); + } + } + + auto sort_by_time = [](const UploadReportResult& a, + const UploadReportResult& b) {return a.first >= b.first;}; + std::sort(uploaded_reports.begin(), uploaded_reports.end(), sort_by_time); + return uploaded_reports; +} + // static CrashReporterMac* CrashReporterMac::GetInstance() { return Singleton::get(); diff --git a/atom/common/crash_reporter/crash_reporter_win.cc b/atom/common/crash_reporter/crash_reporter_win.cc index e5c5edc582ae..be096da80e2c 100644 --- a/atom/common/crash_reporter/crash_reporter_win.cc +++ b/atom/common/crash_reporter/crash_reporter_win.cc @@ -21,6 +21,7 @@ const MINIDUMP_TYPE kSmallDumpType = static_cast( MiniDumpWithProcessThreadData | // Get PEB and TEB. MiniDumpWithUnloadedModules); // Get unloaded modules when available. +const wchar_t kWaitEventFormat[] = L"$1CrashServiceWaitEvent"; const wchar_t kPipeNameFormat[] = L"\\\\.\\pipe\\$1 Crash Service"; } // namespace @@ -47,12 +48,20 @@ void CrashReporterWin::InitBreakpad(const std::string& product_name, base::string16 pipe_name = ReplaceStringPlaceholders( kPipeNameFormat, base::UTF8ToUTF16(product_name), NULL); + base::string16 wait_name = ReplaceStringPlaceholders( + kWaitEventFormat, base::UTF8ToUTF16(product_name), NULL); // Wait until the crash service is started. - HANDLE waiting_event = - ::CreateEventW(NULL, TRUE, FALSE, L"g_atom_shell_crash_service"); - if (waiting_event != INVALID_HANDLE_VALUE) - WaitForSingleObject(waiting_event, 1000); + HANDLE wait_event = ::CreateEventW(NULL, TRUE, FALSE, wait_name.c_str()); + if (wait_event != NULL) { + WaitForSingleObject(wait_event, 1000); + CloseHandle(wait_event); + } + + // ExceptionHandler() attaches our handler and ~ExceptionHandler() detaches + // it, so we must explicitly reset *before* we instantiate our new handler + // to allow any previous handler to detach in the correct order. + breakpad_.reset(); int handler_types = google_breakpad::ExceptionHandler::HANDLER_EXCEPTION | google_breakpad::ExceptionHandler::HANDLER_PURECALL; diff --git a/atom/common/crash_reporter/win/crash_service.cc b/atom/common/crash_reporter/win/crash_service.cc index 10e0fdfcc555..d315b0b9419e 100644 --- a/atom/common/crash_reporter/win/crash_service.cc +++ b/atom/common/crash_reporter/win/crash_service.cc @@ -14,6 +14,7 @@ #include "base/files/file_util.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" #include "base/time/time.h" #include "base/win/windows_version.h" #include "vendor/breakpad/src/client/windows/crash_generation/client_info.h" @@ -24,6 +25,9 @@ namespace breakpad { namespace { +const wchar_t kWaitEventFormat[] = L"$1CrashServiceWaitEvent"; +const wchar_t kClassNameFormat[] = L"$1CrashServiceWindow"; + const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices"; const wchar_t kGoogleReportURL[] = L"https://clients2.google.com/cr/report"; @@ -111,13 +115,18 @@ LRESULT __stdcall CrashSvcWndProc(HWND hwnd, UINT message, // This is the main and only application window. HWND g_top_window = NULL; -bool CreateTopWindow(HINSTANCE instance, bool visible) { +bool CreateTopWindow(HINSTANCE instance, + const base::string16& application_name, + bool visible) { + base::string16 class_name = ReplaceStringPlaceholders( + kClassNameFormat, application_name, NULL); + WNDCLASSEXW wcx = {0}; wcx.cbSize = sizeof(wcx); wcx.style = CS_HREDRAW | CS_VREDRAW; wcx.lpfnWndProc = CrashSvcWndProc; wcx.hInstance = instance; - wcx.lpszClassName = L"crash_svc_class"; + wcx.lpszClassName = class_name.c_str(); ATOM atom = ::RegisterClassExW(&wcx); DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED; @@ -193,7 +202,8 @@ CrashService::~CrashService() { delete sender_; } -bool CrashService::Initialize(const base::FilePath& operating_dir, +bool CrashService::Initialize(const base::string16& application_name, + const base::FilePath& operating_dir, const base::FilePath& dumps_path) { using google_breakpad::CrashReportSender; using google_breakpad::CrashGenerationServer; @@ -260,6 +270,7 @@ bool CrashService::Initialize(const base::FilePath& operating_dir, } if (!CreateTopWindow(::GetModuleHandleW(NULL), + application_name, !cmd_line.HasSwitch(kNoWindow))) { LOG(ERROR) << "could not create window"; if (security_attributes.lpSecurityDescriptor) @@ -298,11 +309,10 @@ bool CrashService::Initialize(const base::FilePath& operating_dir, // Create or open an event to signal the browser process that the crash // service is initialized. - HANDLE running_event = - ::CreateEventW(NULL, TRUE, TRUE, L"g_atom_shell_crash_service"); - // If the browser already had the event open, the CreateEvent call did not - // signal it. We need to do it manually. - ::SetEvent(running_event); + base::string16 wait_name = ReplaceStringPlaceholders( + kWaitEventFormat, application_name, NULL); + HANDLE wait_event = ::CreateEventW(NULL, TRUE, FALSE, wait_name.c_str()); + ::SetEvent(wait_event); return true; } diff --git a/atom/common/crash_reporter/win/crash_service.h b/atom/common/crash_reporter/win/crash_service.h index 730e4da3c6b2..7195ec2a958c 100644 --- a/atom/common/crash_reporter/win/crash_service.h +++ b/atom/common/crash_reporter/win/crash_service.h @@ -37,7 +37,8 @@ class CrashService { // other members in that case. |operating_dir| is where the CrashService // should store breakpad's checkpoint file. |dumps_path| is the directory // where the crash dumps should be stored. - bool Initialize(const base::FilePath& operating_dir, + bool Initialize(const base::string16& application_name, + const base::FilePath& operating_dir, const base::FilePath& dumps_path); // Command line switches: diff --git a/atom/common/crash_reporter/win/crash_service_main.cc b/atom/common/crash_reporter/win/crash_service_main.cc index 0bd72deba9e9..7a5eeb10133a 100644 --- a/atom/common/crash_reporter/win/crash_service_main.cc +++ b/atom/common/crash_reporter/win/crash_service_main.cc @@ -77,7 +77,8 @@ int Main(const wchar_t* cmd) { cmd_line.AppendSwitchNative("pipe-name", pipe_name); breakpad::CrashService crash_service; - if (!crash_service.Initialize(operating_dir, operating_dir)) + if (!crash_service.Initialize(application_name, operating_dir, + operating_dir)) return 2; VLOG(1) << "Ready to process crash requests"; diff --git a/atom/common/id_weak_map.cc b/atom/common/id_weak_map.cc new file mode 100644 index 000000000000..c5c4b60cac5c --- /dev/null +++ b/atom/common/id_weak_map.cc @@ -0,0 +1,88 @@ +// 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 "atom/common/id_weak_map.h" + +#include + +#include "native_mate/converter.h" + +namespace atom { + +namespace { + +struct ObjectKey { + ObjectKey(int id, IDWeakMap* map) : id(id), map(map) {} + int id; + IDWeakMap* map; +}; + +void OnObjectGC(const v8::WeakCallbackInfo& data) { + ObjectKey* key = data.GetParameter(); + key->map->Remove(key->id); + delete key; +} + +} // namespace + +IDWeakMap::IDWeakMap() : next_id_(0) { +} + +IDWeakMap::~IDWeakMap() { +} + +int32_t IDWeakMap::Add(v8::Isolate* isolate, v8::Local object) { + int32_t id = GetNextID(); + auto global = make_linked_ptr(new v8::Global(isolate, object)); + ObjectKey* key = new ObjectKey(id, this); + global->SetWeak(key, OnObjectGC, v8::WeakCallbackType::kParameter); + map_[id] = global; + return id; +} + +v8::MaybeLocal IDWeakMap::Get(v8::Isolate* isolate, int32_t id) { + auto iter = map_.find(id); + if (iter == map_.end()) + return v8::MaybeLocal(); + else + return v8::Local::New(isolate, *iter->second); +} + +bool IDWeakMap::Has(int32_t id) const { + return map_.find(id) != map_.end(); +} + +std::vector IDWeakMap::Keys() const { + std::vector keys; + keys.reserve(map_.size()); + for (const auto& iter : map_) + keys.emplace_back(iter.first); + return keys; +} + +std::vector> IDWeakMap::Values(v8::Isolate* isolate) { + std::vector> keys; + keys.reserve(map_.size()); + for (const auto& iter : map_) + keys.emplace_back(v8::Local::New(isolate, *iter.second)); + return keys; +} + +void IDWeakMap::Remove(int32_t id) { + auto iter = map_.find(id); + if (iter == map_.end()) + LOG(WARNING) << "Removing unexist object with ID " << id; + else + map_.erase(iter); +} + +void IDWeakMap::Clear() { + map_.clear(); +} + +int32_t IDWeakMap::GetNextID() { + return ++next_id_; +} + +} // namespace atom diff --git a/atom/common/id_weak_map.h b/atom/common/id_weak_map.h new file mode 100644 index 000000000000..9fe71ebb616f --- /dev/null +++ b/atom/common/id_weak_map.h @@ -0,0 +1,58 @@ +// 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_ID_WEAK_MAP_H_ +#define ATOM_COMMON_ID_WEAK_MAP_H_ + +#include +#include + +#include "base/memory/linked_ptr.h" +#include "v8/include/v8.h" + +namespace atom { + +// Like ES6's WeakMap, but the key is Integer and the value is Weak Pointer. +class IDWeakMap { + public: + IDWeakMap(); + ~IDWeakMap(); + + // Adds |object| to WeakMap and returns its allocated |id|. + int32_t Add(v8::Isolate* isolate, v8::Local object); + + // Gets the object from WeakMap by its |id|. + v8::MaybeLocal Get(v8::Isolate* isolate, int32_t id); + + // Whethere there is an object with |id| in this WeakMap. + bool Has(int32_t id) const; + + // Returns IDs of all available objects. + std::vector Keys() const; + + // Returns all objects. + std::vector> Values(v8::Isolate* isolate); + + // Remove object with |id| in the WeakMap. + void Remove(int32_t key); + + // Clears the weak map. + void Clear(); + + private: + // Returns next available ID. + int32_t GetNextID(); + + // ID of next stored object. + int32_t next_id_; + + // Map of stored objects. + std::unordered_map>> map_; + + DISALLOW_COPY_AND_ASSIGN(IDWeakMap); +}; + +} // namespace atom + +#endif // ATOM_COMMON_ID_WEAK_MAP_H_ diff --git a/atom/common/keyboad_util.cc b/atom/common/keyboad_util.cc new file mode 100644 index 000000000000..1baa829ff74a --- /dev/null +++ b/atom/common/keyboad_util.cc @@ -0,0 +1,73 @@ +// 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 "atom/common/keyboad_util.h" + +namespace atom { + +// Return key code of the char. +ui::KeyboardCode KeyboardCodeFromCharCode(char c, bool* shifted) { + *shifted = false; + switch (c) { + case 8: case 0x7F: return ui::VKEY_BACK; + case 9: return ui::VKEY_TAB; + case 0xD: case 3: 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; + } +} + +} // namespace atom diff --git a/atom/common/keyboad_util.h b/atom/common/keyboad_util.h new file mode 100644 index 000000000000..0496886e40bd --- /dev/null +++ b/atom/common/keyboad_util.h @@ -0,0 +1,18 @@ +// 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 "ui/events/keycodes/keyboard_codes.h" + +namespace atom { + +// Return key code of the char, and also determine whether the SHIFT key is +// pressed. +ui::KeyboardCode KeyboardCodeFromCharCode(char c, bool* shifted); + +} // namespace atom + +#endif // ATOM_COMMON_KEYBOAD_UTIL_H_ diff --git a/atom/common/lib/asar.coffee b/atom/common/lib/asar.coffee index f70d2c456d78..900a686d44a2 100644 --- a/atom/common/lib/asar.coffee +++ b/atom/common/lib/asar.coffee @@ -14,12 +14,13 @@ getOrCreateArchive = (p) -> # Clean cache on quit. process.on 'exit', -> - archive.destroy() for p, archive of cachedArchives + archive.destroy() for own p, archive of cachedArchives # Separate asar package's path from full path. splitPath = (p) -> return [false] if typeof p isnt 'string' return [true, p, ''] if p.substr(-5) is '.asar' + p = path.normalize p index = p.lastIndexOf ".asar#{path.sep}" return [false] if index is -1 [true, p.substr(0, index + 5), p.substr(index + 6)] @@ -310,6 +311,42 @@ exports.wrapFsWithAsar = (fs) -> files + internalModuleReadFile = process.binding('fs').internalModuleReadFile + process.binding('fs').internalModuleReadFile = (p) -> + [isAsar, asarPath, filePath] = splitPath p + return internalModuleReadFile p unless isAsar + + archive = getOrCreateArchive asarPath + return undefined unless archive + + info = archive.getFileInfo filePath + return undefined unless info + return '' if info.size is 0 + + if info.unpacked + realPath = archive.copyFileOut filePath + return fs.readFileSync realPath, encoding: 'utf8' + + buffer = new Buffer(info.size) + fd = archive.getFd() + retrun undefined unless fd >= 0 + + fs.readSync fd, buffer, 0, info.size, info.offset + buffer.toString 'utf8' + + internalModuleStat = process.binding('fs').internalModuleStat + process.binding('fs').internalModuleStat = (p) -> + [isAsar, asarPath, filePath] = splitPath p + return internalModuleStat p unless isAsar + + archive = getOrCreateArchive asarPath + return -34 unless archive # -ENOENT + + stats = archive.stat filePath + return -34 unless stats # -ENOENT + + if stats.isDirectory then return 1 else return 0 + overrideAPI fs, 'open' overrideAPI child_process, 'execFile' overrideAPISync process, 'dlopen', 1 diff --git a/atom/common/lib/asar_init.coffee b/atom/common/lib/asar_init.coffee index 49efc902d9af..211c79ee9099 100644 --- a/atom/common/lib/asar_init.coffee +++ b/atom/common/lib/asar_init.coffee @@ -9,10 +9,10 @@ return (process, require, asarSource) -> # Make graceful-fs work with asar. source = process.binding 'natives' - source.originalFs = source.fs - source.fs = """ + source['original-fs'] = source.fs + source['fs'] = """ var src = '(function (exports, require, module, __filename, __dirname) { ' + - process.binding('natives').originalFs + + process.binding('natives')['original-fs'] + ' });'; var vm = require('vm'); var fn = vm.runInThisContext(src, { filename: 'fs.js' }); diff --git a/atom/common/lib/init.coffee b/atom/common/lib/init.coffee index 88b32d8c0254..4bc3e36986c0 100644 --- a/atom/common/lib/init.coffee +++ b/atom/common/lib/init.coffee @@ -37,13 +37,18 @@ wrapWithActivateUvLoop = (func) -> process.activateUvLoop() func.apply this, arguments process.nextTick = wrapWithActivateUvLoop process.nextTick -global.setImmediate = wrapWithActivateUvLoop timers.setImmediate -global.clearImmediate = timers.clearImmediate -# 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. if process.type is '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.setImmediate = wrapWithActivateUvLoop timers.setImmediate + global.clearImmediate = wrapWithActivateUvLoop timers.clearImmediate +else + # There are no setImmediate under renderer process by default, so we need to + # manually setup them here. + global.setImmediate = setImmediate + global.clearImmediate = clearImmediate diff --git a/atom/common/native_mate_converters/accelerator_converter.cc b/atom/common/native_mate_converters/accelerator_converter.cc index 74d65161585e..15eaafda2e3d 100644 --- a/atom/common/native_mate_converters/accelerator_converter.cc +++ b/atom/common/native_mate_converters/accelerator_converter.cc @@ -12,7 +12,7 @@ namespace mate { // static bool Converter::FromV8( - v8::Isolate* isolate, v8::Handle val, ui::Accelerator* out) { + v8::Isolate* isolate, v8::Local val, ui::Accelerator* out) { std::string keycode; if (!ConvertFromV8(isolate, val, &keycode)) return false; diff --git a/atom/common/native_mate_converters/accelerator_converter.h b/atom/common/native_mate_converters/accelerator_converter.h index 0a08f059253a..499077c08e28 100644 --- a/atom/common/native_mate_converters/accelerator_converter.h +++ b/atom/common/native_mate_converters/accelerator_converter.h @@ -15,7 +15,7 @@ namespace mate { template<> struct Converter { - static bool FromV8(v8::Isolate* isolate, v8::Handle val, + static bool FromV8(v8::Isolate* isolate, v8::Local val, ui::Accelerator* out); }; diff --git a/atom/common/native_mate_converters/blink_converter.cc b/atom/common/native_mate_converters/blink_converter.cc new file mode 100644 index 000000000000..67c7e7e95fd8 --- /dev/null +++ b/atom/common/native_mate_converters/blink_converter.cc @@ -0,0 +1,258 @@ +// 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 "atom/common/native_mate_converters/blink_converter.h" + +#include +#include + +#include "atom/common/keyboad_util.h" +#include "base/strings/string_util.h" +#include "content/public/browser/native_web_keyboard_event.h" +#include "native_mate/dictionary.h" +#include "third_party/WebKit/public/web/WebDeviceEmulationParams.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" + +namespace { + +template +int VectorToBitArray(const std::vector& vec) { + int bits = 0; + for (const T& item : vec) + bits |= item; + return bits; +} + +} // namespace + +namespace mate { + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + char* out) { + std::string code = base::StringToLowerASCII(V8ToString(val)); + if (code.length() != 1) + return false; + *out = code[0]; + return true; + } +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + blink::WebInputEvent::Type* out) { + std::string type = base::StringToLowerASCII(V8ToString(val)); + if (type == "mousedown") + *out = blink::WebInputEvent::MouseDown; + else if (type == "mouseup") + *out = blink::WebInputEvent::MouseUp; + else if (type == "mousemove") + *out = blink::WebInputEvent::MouseMove; + else if (type == "mouseenter") + *out = blink::WebInputEvent::MouseEnter; + else if (type == "mouseleave") + *out = blink::WebInputEvent::MouseLeave; + else if (type == "contextmenu") + *out = blink::WebInputEvent::ContextMenu; + else if (type == "mousewheel") + *out = blink::WebInputEvent::MouseWheel; + else if (type == "keydown") + *out = blink::WebInputEvent::KeyDown; + else if (type == "keyup") + *out = blink::WebInputEvent::KeyUp; + else if (type == "char") + *out = blink::WebInputEvent::Char; + else if (type == "touchstart") + *out = blink::WebInputEvent::TouchStart; + else if (type == "touchmove") + *out = blink::WebInputEvent::TouchMove; + else if (type == "touchend") + *out = blink::WebInputEvent::TouchEnd; + else if (type == "touchcancel") + *out = blink::WebInputEvent::TouchCancel; + return true; + } +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + blink::WebInputEvent::Modifiers* out) { + std::string modifier = base::StringToLowerASCII(V8ToString(val)); + if (modifier == "shift") + *out = blink::WebInputEvent::ShiftKey; + else if (modifier == "control" || modifier == "ctrl") + *out = blink::WebInputEvent::ControlKey; + else if (modifier == "alt") + *out = blink::WebInputEvent::AltKey; + else if (modifier == "meta" || modifier == "command" || modifier == "cmd") + *out = blink::WebInputEvent::MetaKey; + else if (modifier == "iskeypad") + *out = blink::WebInputEvent::IsKeyPad; + else if (modifier == "isautorepeat") + *out = blink::WebInputEvent::IsAutoRepeat; + else if (modifier == "leftbuttondown") + *out = blink::WebInputEvent::LeftButtonDown; + else if (modifier == "middlebuttondown") + *out = blink::WebInputEvent::MiddleButtonDown; + else if (modifier == "rightbuttondown") + *out = blink::WebInputEvent::RightButtonDown; + else if (modifier == "capslock") + *out = blink::WebInputEvent::CapsLockOn; + else if (modifier == "numlock") + *out = blink::WebInputEvent::NumLockOn; + else if (modifier == "left") + *out = blink::WebInputEvent::IsLeft; + else if (modifier == "right") + *out = blink::WebInputEvent::IsRight; + return true; + } +}; + +int GetWebInputEventType(v8::Isolate* isolate, v8::Local val) { + blink::WebInputEvent::Type type = blink::WebInputEvent::Undefined; + mate::Dictionary dict; + ConvertFromV8(isolate, val, &dict) && dict.Get("type", &type); + return type; +} + +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, + blink::WebInputEvent* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + if (!dict.Get("type", &out->type)) + return false; + std::vector modifiers; + if (dict.Get("modifiers", &modifiers)) + out->modifiers = VectorToBitArray(modifiers); + out->timeStampSeconds = base::Time::Now().ToDoubleT(); + return true; +} + +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, + blink::WebKeyboardEvent* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + if (!ConvertFromV8(isolate, val, static_cast(out))) + return false; + char code; + if (!dict.Get("keyCode", &code)) + return false; + bool shifted = false; + out->windowsKeyCode = atom::KeyboardCodeFromCharCode(code, &shifted); + if (out->windowsKeyCode == ui::VKEY_UNKNOWN) + return false; + if (shifted) + out->modifiers |= blink::WebInputEvent::ShiftKey; + out->setKeyIdentifierFromWindowsKeyCode(); + return true; +} + +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, + content::NativeWebKeyboardEvent* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + if (!ConvertFromV8(isolate, val, static_cast(out))) + return false; + dict.Get("skipInBrowser", &out->skip_in_browser); + return true; +} + +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, blink::WebMouseEvent* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + if (!ConvertFromV8(isolate, val, static_cast(out))) + return false; + if (!dict.Get("x", &out->x) || !dict.Get("y", &out->y)) + return false; + dict.Get("globalX", &out->globalX); + dict.Get("globalY", &out->globalY); + dict.Get("movementX", &out->movementX); + dict.Get("movementY", &out->movementY); + dict.Get("clickCount", &out->clickCount); + return true; +} + +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, + blink::WebMouseWheelEvent* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + if (!ConvertFromV8(isolate, val, static_cast(out))) + return false; + dict.Get("deltaX", &out->deltaX); + dict.Get("deltaY", &out->deltaY); + dict.Get("wheelTicksX", &out->wheelTicksX); + dict.Get("wheelTicksY", &out->wheelTicksY); + dict.Get("accelerationRatioX", &out->accelerationRatioX); + dict.Get("accelerationRatioY", &out->accelerationRatioY); + dict.Get("hasPreciseScrollingDeltas", &out->hasPreciseScrollingDeltas); + dict.Get("canScroll", &out->canScroll); + return true; +} + +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, blink::WebFloatPoint* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + return dict.Get("x", &out->x) && dict.Get("y", &out->y); +} + +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, blink::WebPoint* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + return dict.Get("x", &out->x) && dict.Get("y", &out->y); +} + +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, blink::WebSize* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + return dict.Get("width", &out->width) && dict.Get("height", &out->height); +} + +bool Converter::FromV8( + v8::Isolate* isolate, v8::Local val, + blink::WebDeviceEmulationParams* out) { + mate::Dictionary dict; + if (!ConvertFromV8(isolate, val, &dict)) + return false; + + std::string screen_position; + if (dict.Get("screenPosition", &screen_position)) { + screen_position = base::StringToLowerASCII(screen_position); + if (screen_position == "mobile") + out->screenPosition = blink::WebDeviceEmulationParams::Mobile; + else if (screen_position == "desktop") + out->screenPosition = blink::WebDeviceEmulationParams::Desktop; + else + return false; + } + + dict.Get("screenSize", &out->screenSize); + dict.Get("viewPosition", &out->viewPosition); + dict.Get("deviceScaleFactor", &out->deviceScaleFactor); + dict.Get("viewSize", &out->viewSize); + dict.Get("fitToView", &out->fitToView); + dict.Get("offset", &out->offset); + dict.Get("scale", &out->scale); + return true; +} + +} // namespace mate diff --git a/atom/common/native_mate_converters/blink_converter.h b/atom/common/native_mate_converters/blink_converter.h new file mode 100644 index 000000000000..17bb108d349e --- /dev/null +++ b/atom/common/native_mate_converters/blink_converter.h @@ -0,0 +1,85 @@ +// 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_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ +#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ + +#include "native_mate/converter.h" + +namespace blink { +class WebInputEvent; +class WebMouseEvent; +class WebMouseWheelEvent; +class WebKeyboardEvent; +struct WebDeviceEmulationParams; +struct WebFloatPoint; +struct WebPoint; +struct WebSize; +} + +namespace content { +struct NativeWebKeyboardEvent; +} + +namespace mate { + +int GetWebInputEventType(v8::Isolate* isolate, v8::Local val); + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebInputEvent* out); +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebKeyboardEvent* out); +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + content::NativeWebKeyboardEvent* out); +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebMouseEvent* out); +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebMouseWheelEvent* out); +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebFloatPoint* out); +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebPoint* out); +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebSize* out); +}; + +template<> +struct Converter { + static bool FromV8(v8::Isolate* isolate, v8::Local val, + blink::WebDeviceEmulationParams* out); +}; + +} // namespace mate + +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ diff --git a/atom/common/native_mate_converters/callback.h b/atom/common/native_mate_converters/callback.h new file mode 100644 index 000000000000..6e51cda79c49 --- /dev/null +++ b/atom/common/native_mate_converters/callback.h @@ -0,0 +1,104 @@ +// Copyright (c) 2015 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_ +#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_ + +#include + +#include "atom/common/api/locker.h" +#include "base/bind.h" +#include "base/callback.h" +#include "native_mate/function_template.h" +#include "native_mate/scoped_persistent.h" +#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" + +namespace mate { + +namespace internal { + +typedef scoped_refptr > SafeV8Function; + +template +struct V8FunctionInvoker {}; + +template +struct V8FunctionInvoker(ArgTypes...)> { + static v8::Local Go(v8::Isolate* isolate, SafeV8Function function, + ArgTypes... raw) { + Locker locker(isolate); + v8::EscapableHandleScope handle_scope(isolate); + scoped_ptr script_scope( + Locker::IsBrowserProcess() ? + nullptr : new blink::WebScopedRunV8Script(isolate)); + v8::Local holder = function->NewHandle(); + v8::Local context = holder->CreationContext(); + v8::Context::Scope context_scope(context); + std::vector> args = { ConvertToV8(isolate, raw)... }; + v8::Local ret(holder->Call(holder, args.size(), &args.front())); + return handle_scope.Escape(ret); + } +}; + +template +struct V8FunctionInvoker { + static void Go(v8::Isolate* isolate, SafeV8Function function, + ArgTypes... raw) { + Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + scoped_ptr script_scope( + Locker::IsBrowserProcess() ? + nullptr : new blink::WebScopedRunV8Script(isolate)); + v8::Local holder = function->NewHandle(); + v8::Local context = holder->CreationContext(); + v8::Context::Scope context_scope(context); + std::vector> args = { ConvertToV8(isolate, raw)... }; + holder->Call(holder, args.size(), &args.front()); + } +}; + +template +struct V8FunctionInvoker { + static ReturnType Go(v8::Isolate* isolate, SafeV8Function function, + ArgTypes... raw) { + Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + scoped_ptr script_scope( + Locker::IsBrowserProcess() ? + nullptr : new blink::WebScopedRunV8Script(isolate)); + v8::Local holder = function->NewHandle(); + v8::Local context = holder->CreationContext(); + v8::Context::Scope context_scope(context); + ReturnType ret; + std::vector> args = { ConvertToV8(isolate, raw)... }; + v8::Local val(holder->Call(holder, args.size(), &args.front())); + Converter::FromV8(isolate, val, &ret); + return ret; + } +}; + +} // namespace internal + +template +struct Converter > { + static v8::Local ToV8(v8::Isolate* isolate, + const base::Callback& val) { + return CreateFunctionTemplate(isolate, val)->GetFunction(); + } + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + base::Callback* out) { + if (!val->IsFunction()) + return false; + + internal::SafeV8Function function( + new RefCountedPersistent(isolate, val)); + *out = base::Bind(&internal::V8FunctionInvoker::Go, isolate, function); + return true; + } +}; + +} // namespace mate + +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_ diff --git a/atom/common/native_mate_converters/file_path_converter.h b/atom/common/native_mate_converters/file_path_converter.h index f41449d5f85a..468f506de8a3 100644 --- a/atom/common/native_mate_converters/file_path_converter.h +++ b/atom/common/native_mate_converters/file_path_converter.h @@ -14,12 +14,12 @@ namespace mate { template<> struct Converter { - static v8::Handle ToV8(v8::Isolate* isolate, + static v8::Local ToV8(v8::Isolate* isolate, const base::FilePath& val) { return Converter::ToV8(isolate, val.value()); } static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, base::FilePath* out) { base::FilePath::StringType path; if (Converter::FromV8(isolate, val, &path)) { diff --git a/atom/common/native_mate_converters/gfx_converter.cc b/atom/common/native_mate_converters/gfx_converter.cc index 7f18debb96e1..37e7aeb3a48b 100644 --- a/atom/common/native_mate_converters/gfx_converter.cc +++ b/atom/common/native_mate_converters/gfx_converter.cc @@ -12,16 +12,17 @@ namespace mate { -v8::Handle Converter::ToV8(v8::Isolate* isolate, +v8::Local Converter::ToV8(v8::Isolate* isolate, const gfx::Point& val) { - mate::Dictionary dict(isolate, v8::Object::New(isolate)); + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); dict.Set("x", val.x()); dict.Set("y", val.y()); return dict.GetHandle(); } bool Converter::FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::Point* out) { mate::Dictionary dict; if (!ConvertFromV8(isolate, val, &dict)) @@ -33,16 +34,17 @@ bool Converter::FromV8(v8::Isolate* isolate, return true; } -v8::Handle Converter::ToV8(v8::Isolate* isolate, +v8::Local Converter::ToV8(v8::Isolate* isolate, const gfx::Size& val) { - mate::Dictionary dict(isolate, v8::Object::New(isolate)); + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); dict.Set("width", val.width()); dict.Set("height", val.height()); return dict.GetHandle(); } bool Converter::FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::Size* out) { mate::Dictionary dict; if (!ConvertFromV8(isolate, val, &dict)) @@ -54,9 +56,10 @@ bool Converter::FromV8(v8::Isolate* isolate, return true; } -v8::Handle Converter::ToV8(v8::Isolate* isolate, +v8::Local Converter::ToV8(v8::Isolate* isolate, const gfx::Rect& val) { - mate::Dictionary dict(isolate, v8::Object::New(isolate)); + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); dict.Set("x", val.x()); dict.Set("y", val.y()); dict.Set("width", val.width()); @@ -65,7 +68,7 @@ v8::Handle Converter::ToV8(v8::Isolate* isolate, } bool Converter::FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::Rect* out) { mate::Dictionary dict; if (!ConvertFromV8(isolate, val, &dict)) @@ -80,7 +83,7 @@ bool Converter::FromV8(v8::Isolate* isolate, template<> struct Converter { - static v8::Handle ToV8(v8::Isolate* isolate, + static v8::Local ToV8(v8::Isolate* isolate, const gfx::Display::TouchSupport& val) { switch (val) { case gfx::Display::TOUCH_SUPPORT_AVAILABLE: @@ -93,9 +96,10 @@ struct Converter { } }; -v8::Handle Converter::ToV8(v8::Isolate* isolate, +v8::Local Converter::ToV8(v8::Isolate* isolate, const gfx::Display& val) { - mate::Dictionary dict(isolate, v8::Object::New(isolate)); + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); dict.Set("id", val.id()); dict.Set("bounds", val.bounds()); dict.Set("workArea", val.work_area()); diff --git a/atom/common/native_mate_converters/gfx_converter.h b/atom/common/native_mate_converters/gfx_converter.h index 35fd5039a72f..c6da76a2d0e7 100644 --- a/atom/common/native_mate_converters/gfx_converter.h +++ b/atom/common/native_mate_converters/gfx_converter.h @@ -18,37 +18,37 @@ namespace mate { template<> struct Converter { - static v8::Handle ToV8(v8::Isolate* isolate, + static v8::Local ToV8(v8::Isolate* isolate, const gfx::Point& val); static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::Point* out); }; template<> struct Converter { - static v8::Handle ToV8(v8::Isolate* isolate, + static v8::Local ToV8(v8::Isolate* isolate, const gfx::Size& val); static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::Size* out); }; template<> struct Converter { - static v8::Handle ToV8(v8::Isolate* isolate, + static v8::Local ToV8(v8::Isolate* isolate, const gfx::Rect& val); static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::Rect* out); }; template<> struct Converter { - static v8::Handle ToV8(v8::Isolate* isolate, + static v8::Local ToV8(v8::Isolate* isolate, const gfx::Display& val); static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::Display* out); }; diff --git a/atom/common/native_mate_converters/gurl_converter.h b/atom/common/native_mate_converters/gurl_converter.h index f578ad4cffcd..34408913b789 100644 --- a/atom/common/native_mate_converters/gurl_converter.h +++ b/atom/common/native_mate_converters/gurl_converter.h @@ -14,12 +14,12 @@ namespace mate { template<> struct Converter { - static v8::Handle ToV8(v8::Isolate* isolate, + static v8::Local ToV8(v8::Isolate* isolate, const GURL& val) { return ConvertToV8(isolate, val.spec()); } static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, GURL* out) { std::string url; if (Converter::FromV8(isolate, val, &url)) { diff --git a/atom/common/native_mate_converters/image_converter.cc b/atom/common/native_mate_converters/image_converter.cc index ec5c50f31564..550bb7b904a7 100644 --- a/atom/common/native_mate_converters/image_converter.cc +++ b/atom/common/native_mate_converters/image_converter.cc @@ -11,7 +11,7 @@ namespace mate { bool Converter::FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::ImageSkia* out) { gfx::Image image; if (!ConvertFromV8(isolate, val, &image)) @@ -22,7 +22,7 @@ bool Converter::FromV8(v8::Isolate* isolate, } bool Converter::FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::Image* out) { if (val->IsNull()) return true; @@ -43,7 +43,7 @@ bool Converter::FromV8(v8::Isolate* isolate, return true; } -v8::Handle Converter::ToV8(v8::Isolate* isolate, +v8::Local Converter::ToV8(v8::Isolate* isolate, const gfx::Image& val) { return ConvertToV8(isolate, atom::api::NativeImage::Create(isolate, val)); } diff --git a/atom/common/native_mate_converters/image_converter.h b/atom/common/native_mate_converters/image_converter.h index 1b1a12c3ae02..be52288eb049 100644 --- a/atom/common/native_mate_converters/image_converter.h +++ b/atom/common/native_mate_converters/image_converter.h @@ -17,16 +17,16 @@ namespace mate { template<> struct Converter { static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::ImageSkia* out); }; template<> struct Converter { static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, gfx::Image* out); - static v8::Handle ToV8(v8::Isolate* isolate, + static v8::Local ToV8(v8::Isolate* isolate, const gfx::Image& val); }; diff --git a/atom/common/native_mate_converters/string16_converter.h b/atom/common/native_mate_converters/string16_converter.h index 4eb7d7698870..e2a5b8ca489e 100644 --- a/atom/common/native_mate_converters/string16_converter.h +++ b/atom/common/native_mate_converters/string16_converter.h @@ -12,13 +12,13 @@ namespace mate { template<> struct Converter { - static v8::Handle ToV8(v8::Isolate* isolate, + static v8::Local ToV8(v8::Isolate* isolate, const base::string16& val) { return MATE_STRING_NEW_FROM_UTF16( isolate, reinterpret_cast(val.data()), val.size()); } static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, base::string16* out) { if (!val->IsString()) return false; @@ -29,6 +29,12 @@ struct Converter { } }; +inline v8::Local StringToV8( + v8::Isolate* isolate, + const base::string16& input) { + return ConvertToV8(isolate, input).As(); +} + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_STRING16_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 7c34b222ff01..a91e614fc6dd 100644 --- a/atom/common/native_mate_converters/v8_value_converter.cc +++ b/atom/common/native_mate_converters/v8_value_converter.cc @@ -11,12 +11,14 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/values.h" +#include "native_mate/dictionary.h" +#include "vendor/node/src/node_buffer.h" namespace atom { namespace { -const int kMaxRecursionDepth = 20; +const int kMaxRecursionDepth = 100; } // namespace @@ -46,7 +48,7 @@ class V8ValueConverter::FromV8ValueState { // other handle B in the map points to the same object as A. Note that A can // be unique even if there already is another handle with the same identity // hash (key) in the map, because two objects can have the same hash. - bool UpdateAndCheckUniqueness(v8::Handle handle) { + bool UpdateAndCheckUniqueness(v8::Local handle) { typedef HashToHandleMap::const_iterator Iterator; int hash = handle->GetIdentityHash(); // We only compare using == with handles to objects with the same identity @@ -67,7 +69,7 @@ class V8ValueConverter::FromV8ValueState { } private: - typedef std::multimap > HashToHandleMap; + typedef std::multimap > HashToHandleMap; HashToHandleMap unique_map_; int max_recursion_depth_; @@ -178,7 +180,8 @@ v8::Local V8ValueConverter::ToV8Array( v8::Local V8ValueConverter::ToV8Object( v8::Isolate* isolate, const base::DictionaryValue* val) const { - v8::Local result(v8::Object::New(isolate)); + mate::Dictionary result = mate::Dictionary::CreateEmpty(isolate); + result.SetHidden("simple", true); for (base::DictionaryValue::Iterator iter(*val); !iter.IsAtEnd(); iter.Advance()) { @@ -187,22 +190,19 @@ v8::Local V8ValueConverter::ToV8Object( CHECK(!child_v8.IsEmpty()); v8::TryCatch try_catch; - result->Set( - v8::String::NewFromUtf8(isolate, key.c_str(), v8::String::kNormalString, - key.length()), - child_v8); + result.Set(key, child_v8); if (try_catch.HasCaught()) { LOG(ERROR) << "Setter for property " << key.c_str() << " threw an " << "exception."; } } - return result; + return result.GetHandle(); } base::Value* V8ValueConverter::FromV8ValueImpl( FromV8ValueState* state, - v8::Handle val, + v8::Local val, v8::Isolate* isolate) const { CHECK(!val.IsEmpty()); @@ -211,7 +211,7 @@ base::Value* V8ValueConverter::FromV8ValueImpl( return NULL; if (val->IsNull()) - return base::Value::CreateNullValue(); + return base::Value::CreateNullValue().release(); if (val->IsBoolean()) return new base::FundamentalValue(val->ToBoolean()->Value()); @@ -258,6 +258,10 @@ base::Value* V8ValueConverter::FromV8ValueImpl( return FromV8Object(val->ToObject(), state, isolate); } + if (node::Buffer::HasInstance(val)) { + return FromNodeBuffer(val, state, isolate); + } + if (val->IsObject()) { return FromV8Object(val->ToObject(), state, isolate); } @@ -267,11 +271,11 @@ base::Value* V8ValueConverter::FromV8ValueImpl( } base::Value* V8ValueConverter::FromV8Array( - v8::Handle val, + v8::Local val, FromV8ValueState* state, v8::Isolate* isolate) const { if (!state->UpdateAndCheckUniqueness(val)) - return base::Value::CreateNullValue(); + return base::Value::CreateNullValue().release(); scoped_ptr scope; // If val was created in a different context than our current one, change to @@ -305,12 +309,20 @@ base::Value* V8ValueConverter::FromV8Array( return result; } +base::Value* V8ValueConverter::FromNodeBuffer( + v8::Local value, + FromV8ValueState* state, + v8::Isolate* isolate) const { + return base::BinaryValue::CreateWithCopiedBuffer( + node::Buffer::Data(value), node::Buffer::Length(value)); +} + base::Value* V8ValueConverter::FromV8Object( v8::Local val, FromV8ValueState* state, v8::Isolate* isolate) const { if (!state->UpdateAndCheckUniqueness(val)) - return base::Value::CreateNullValue(); + return base::Value::CreateNullValue().release(); scoped_ptr scope; // If val was created in a different context than our current one, change to diff --git a/atom/common/native_mate_converters/v8_value_converter.h b/atom/common/native_mate_converters/v8_value_converter.h index 94250a335f2a..db108ad9b043 100644 --- a/atom/common/native_mate_converters/v8_value_converter.h +++ b/atom/common/native_mate_converters/v8_value_converter.h @@ -43,12 +43,14 @@ class V8ValueConverter { const base::DictionaryValue* dictionary) const; base::Value* FromV8ValueImpl(FromV8ValueState* state, - v8::Handle value, + v8::Local value, v8::Isolate* isolate) const; - base::Value* FromV8Array(v8::Handle array, + base::Value* FromV8Array(v8::Local array, FromV8ValueState* state, v8::Isolate* isolate) const; - + base::Value* FromNodeBuffer(v8::Local value, + FromV8ValueState* state, + v8::Isolate* isolate) const; base::Value* FromV8Object(v8::Local object, FromV8ValueState* state, v8::Isolate* isolate) const; diff --git a/atom/common/native_mate_converters/value_converter.cc b/atom/common/native_mate_converters/value_converter.cc index 65d47b2af8c4..c9c1a861ba26 100644 --- a/atom/common/native_mate_converters/value_converter.cc +++ b/atom/common/native_mate_converters/value_converter.cc @@ -10,7 +10,7 @@ namespace mate { bool Converter::FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, base::DictionaryValue* out) { scoped_ptr converter(new atom::V8ValueConverter); scoped_ptr value(converter->FromV8Value( @@ -23,8 +23,15 @@ bool Converter::FromV8(v8::Isolate* isolate, } } +v8::Local Converter::ToV8( + v8::Isolate* isolate, + const base::DictionaryValue& val) { + scoped_ptr converter(new atom::V8ValueConverter); + return converter->ToV8Value(&val, isolate->GetCurrentContext()); +} + bool Converter::FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, base::ListValue* out) { scoped_ptr converter(new atom::V8ValueConverter); scoped_ptr value(converter->FromV8Value( @@ -37,7 +44,7 @@ bool Converter::FromV8(v8::Isolate* isolate, } } -v8::Handle Converter::ToV8( +v8::Local Converter::ToV8( v8::Isolate* isolate, const base::ListValue& val) { scoped_ptr converter(new atom::V8ValueConverter); diff --git a/atom/common/native_mate_converters/value_converter.h b/atom/common/native_mate_converters/value_converter.h index 4810e6aa26f0..013dd99cc798 100644 --- a/atom/common/native_mate_converters/value_converter.h +++ b/atom/common/native_mate_converters/value_converter.h @@ -17,17 +17,19 @@ namespace mate { template<> struct Converter { static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, base::DictionaryValue* out); + static v8::Local ToV8(v8::Isolate* isolate, + const base::DictionaryValue& val); }; template<> struct Converter { static bool FromV8(v8::Isolate* isolate, - v8::Handle val, + v8::Local val, base::ListValue* out); - static v8::Handle ToV8(v8::Isolate* isolate, - const base::ListValue& val); + static v8::Local ToV8(v8::Isolate* isolate, + const base::ListValue& val); }; } // namespace mate diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 10ab8f4929a4..5aec200550ad 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -7,8 +7,11 @@ #include #include -#include "atom/app/atom_main_args.h" +#include "atom/common/api/event_emitter_caller.h" +#include "atom/common/api/locker.h" +#include "atom/common/atom_command_line.h" #include "atom/common/native_mate_converters/file_path_converter.h" +#include "atom/common/node_includes.h" #include "base/command_line.h" #include "base/base_paths.h" #include "base/files/file_path.h" @@ -16,10 +19,8 @@ #include "base/path_service.h" #include "content/public/browser/browser_thread.h" #include "content/public/common/content_paths.h" -#include "native_mate/locker.h" #include "native_mate/dictionary.h" - -#include "atom/common/node_includes.h" +#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h" using content::BrowserThread; @@ -35,8 +36,10 @@ REFERENCE_MODULE(atom_browser_content_tracing); REFERENCE_MODULE(atom_browser_dialog); REFERENCE_MODULE(atom_browser_menu); REFERENCE_MODULE(atom_browser_power_monitor); +REFERENCE_MODULE(atom_browser_power_save_blocker); REFERENCE_MODULE(atom_browser_protocol); REFERENCE_MODULE(atom_browser_global_shortcut); +REFERENCE_MODULE(atom_browser_session); REFERENCE_MODULE(atom_browser_tray); REFERENCE_MODULE(atom_browser_web_contents); REFERENCE_MODULE(atom_browser_web_view_manager); @@ -44,7 +47,6 @@ REFERENCE_MODULE(atom_browser_window); REFERENCE_MODULE(atom_common_asar); REFERENCE_MODULE(atom_common_clipboard); REFERENCE_MODULE(atom_common_crash_reporter); -REFERENCE_MODULE(atom_common_id_weak_map); REFERENCE_MODULE(atom_common_native_image); REFERENCE_MODULE(atom_common_screen); REFERENCE_MODULE(atom_common_shell); @@ -128,10 +130,11 @@ void NodeBindings::Initialize() { node::g_standalone_mode = is_browser_; node::g_upstream_node_mode = false; - // Parse the debug args. - auto args = AtomCommandLine::argv(); - for (const std::string& arg : args) - node::ParseDebugOpt(arg.c_str()); +#if defined(OS_LINUX) + // Get real command line in renderer process forked by zygote. + if (!is_browser_) + AtomCommandLine::InitializeFromCommandLine(); +#endif // Init node. // (we assume node::Init would not modify the parameters under embedded mode). @@ -170,14 +173,8 @@ node::Environment* NodeBindings::CreateEnvironment( } void NodeBindings::LoadEnvironment(node::Environment* env) { - node::node_isolate = env->isolate(); - if (node::use_debug_agent) - node::StartDebug(env, node::debug_wait_connect); - node::LoadEnvironment(env); - - if (node::use_debug_agent) - node::EnableDebug(env); + mate::EmitEvent(env->isolate(), env->process_object(), "loaded"); } void NodeBindings::PrepareMessageLoop() { @@ -217,6 +214,10 @@ void NodeBindings::UvRunOnce() { // Enter node context while dealing with uv events. v8::Context::Scope context_scope(env->context()); + // Perform microtask checkpoint after running JavaScript. + scoped_ptr script_scope( + is_browser_ ? nullptr : new blink::WebScopedRunV8Script(env->isolate())); + // Deal with uv events. int r = uv_run(uv_loop_, UV_RUN_NOWAIT); if (r == 0 || uv_loop_->stop_flag != 0) diff --git a/atom/common/node_bindings_win.cc b/atom/common/node_bindings_win.cc index b1ecaa570607..b8de4f59da3b 100644 --- a/atom/common/node_bindings_win.cc +++ b/atom/common/node_bindings_win.cc @@ -22,37 +22,26 @@ NodeBindingsWin::~NodeBindingsWin() { } void NodeBindingsWin::PollEvents() { - // Unlike Unix, in which we can just rely on one backend fd to determine - // whether we should iterate libuv loop, on Window, IOCP is just one part - // of the libuv loop, we should also check whether we have other types of - // events. - bool block = uv_loop_->idle_handles == NULL && - uv_loop_->pending_reqs_tail == NULL && - uv_loop_->endgame_handles == NULL && - !uv_loop_->stop_flag && - (uv_loop_->active_handles > 0 || - !QUEUE_EMPTY(&uv_loop_->active_reqs)); + // If there are other kinds of events pending, uv_backend_timeout will + // instruct us not to wait. + DWORD bytes, timeout; + ULONG_PTR key; + OVERLAPPED* overlapped; - // When there is no other types of events, we block on the IOCP. - if (block) { - DWORD bytes, timeout; - ULONG_PTR key; - OVERLAPPED* overlapped; + timeout = uv_backend_timeout(uv_loop_); - timeout = uv_backend_timeout(uv_loop_); - GetQueuedCompletionStatus(uv_loop_->iocp, - &bytes, - &key, - &overlapped, - timeout); + GetQueuedCompletionStatus(uv_loop_->iocp, + &bytes, + &key, + &overlapped, + timeout); - // Give the event back so libuv can deal with it. - if (overlapped != NULL) - PostQueuedCompletionStatus(uv_loop_->iocp, - bytes, - key, - overlapped); - } + // Give the event back so libuv can deal with it. + if (overlapped != NULL) + PostQueuedCompletionStatus(uv_loop_->iocp, + bytes, + key, + overlapped); } // static diff --git a/atom/common/node_includes.h b/atom/common/node_includes.h index 0c4189b2fac3..3876d8622913 100644 --- a/atom/common/node_includes.h +++ b/atom/common/node_includes.h @@ -5,6 +5,8 @@ #ifndef ATOM_COMMON_NODE_INCLUDES_H_ #define ATOM_COMMON_NODE_INCLUDES_H_ +#include "base/logging.h" + // Include common headers for using node APIs. #define BUILDING_NODE_EXTENSION diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 7f642ecbbabd..c70e1ba4afa6 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -42,6 +42,9 @@ const char kAcceptFirstMouse[] = "accept-first-mouse"; // Whether window size should include window frame. const char kUseContentSize[] = "use-content-size"; +// The requested title bar style for the window +const char kTitleBarStyle[] = "title-bar-style"; + // The WebPreferences. const char kWebPreferences[] = "web-preferences"; @@ -75,6 +78,9 @@ const char kGuestInstanceID[] = "guest-instance-id"; // Script that will be loaded by guest WebContents before other scripts. const char kPreloadScript[] = "preload"; +// Like --preload, but the passed argument is an URL. +const char kPreloadUrl[] = "preload-url"; + // Whether the window should be transparent. const char kTransparent[] = "transparent"; @@ -87,6 +93,9 @@ const char kDisableAutoHideCursor[] = "disable-auto-hide-cursor"; // Use the OS X's standard window instead of the textured window. const char kStandardWindow[] = "standard-window"; +// Path to client certificate. +const char kClientCertificate[] = "client-certificate"; + // Web runtime features. const char kExperimentalFeatures[] = "experimental-features"; const char kExperimentalCanvasFeatures[] = "experimental-canvas-features"; @@ -95,9 +104,18 @@ const char kOverlayScrollbars[] = "overlay-scrollbars"; const char kOverlayFullscreenVideo[] = "overlay-fullscreen-video"; const char kSharedWorker[] = "shared-worker"; +// Set page visiblity to always visible. +const char kPageVisibility[] = "page-visibility"; + // Disable HTTP cache. const char kDisableHttpCache[] = "disable-http-cache"; +// Register schemes to standard. +const char kRegisterStandardSchemes[] = "register-standard-schemes"; + +// The browser process app model ID +const char kAppUserModelId[] = "app-user-model-id"; + } // namespace switches } // namespace atom diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index aaaa86addb15..e62f3116661a 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -30,6 +30,7 @@ extern const char kAlwaysOnTop[]; extern const char kNodeIntegration[]; extern const char kAcceptFirstMouse[]; extern const char kUseContentSize[]; +extern const char kTitleBarStyle[]; extern const char kWebPreferences[]; extern const char kZoomFactor[]; extern const char kAutoHideMenuBar[]; @@ -41,10 +42,12 @@ extern const char kPpapiFlashPath[]; extern const char kPpapiFlashVersion[]; extern const char kGuestInstanceID[]; extern const char kPreloadScript[]; +extern const char kPreloadUrl[]; extern const char kTransparent[]; extern const char kType[]; extern const char kDisableAutoHideCursor[]; extern const char kStandardWindow[]; +extern const char kClientCertificate[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; @@ -52,8 +55,12 @@ extern const char kSubpixelFontScaling[]; extern const char kOverlayScrollbars[]; extern const char kOverlayFullscreenVideo[]; extern const char kSharedWorker[]; +extern const char kPageVisibility[]; extern const char kDisableHttpCache[]; +extern const char kRegisterStandardSchemes[]; + +extern const char kAppUserModelId[]; } // namespace switches diff --git a/atom/common/platform_util.h b/atom/common/platform_util.h index 7ac150b79211..312942c4f5dc 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.) -void OpenExternal(const GURL& url); +bool OpenExternal(const GURL& url); // 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 f70d16a559a7..aa7439968daa 100644 --- a/atom/common/platform_util_linux.cc +++ b/atom/common/platform_util_linux.cc @@ -37,12 +37,12 @@ bool XDGUtil(const std::string& util, const std::string& arg) { return (exit_code == 0); } -void XDGOpen(const std::string& path) { - XDGUtil("xdg-open", path); +bool XDGOpen(const std::string& path) { + return XDGUtil("xdg-open", path); } -void XDGEmail(const std::string& email) { - XDGUtil("xdg-email", email); +bool XDGEmail(const std::string& email) { + return XDGUtil("xdg-email", email); } } // namespace @@ -64,11 +64,11 @@ void OpenItem(const base::FilePath& full_path) { XDGOpen(full_path.value()); } -void OpenExternal(const GURL& url) { +bool OpenExternal(const GURL& url) { if (url.SchemeIs("mailto")) - XDGEmail(url.spec()); + return XDGEmail(url.spec()); else - XDGOpen(url.spec()); + return XDGOpen(url.spec()); } bool MoveItemToTrash(const base::FilePath& full_path) { diff --git a/atom/common/platform_util_mac.mm b/atom/common/platform_util_mac.mm index 41f3c59916aa..1aa75effd35b 100644 --- a/atom/common/platform_util_mac.mm +++ b/atom/common/platform_util_mac.mm @@ -118,26 +118,34 @@ void OpenItem(const base::FilePath& full_path) { } } -void OpenExternal(const GURL& url) { +bool OpenExternal(const GURL& url) { DCHECK([NSThread isMainThread]); NSString* url_string = base::SysUTF8ToNSString(url.spec()); NSURL* ns_url = [NSURL URLWithString:url_string]; - if (!ns_url || ![[NSWorkspace sharedWorkspace] openURL:ns_url]) - LOG(WARNING) << "NSWorkspace failed to open URL " << url; + if (!ns_url) { + return false; + } + + CFURLRef openingApp = NULL; + OSStatus status = LSGetApplicationForURL((CFURLRef)ns_url, + kLSRolesAll, + NULL, + &openingApp); + if (status != noErr) { + return false; + } + CFRelease(openingApp); // NOT A BUG; LSGetApplicationForURL retains for us + + return [[NSWorkspace sharedWorkspace] openURL:ns_url]; } bool MoveItemToTrash(const base::FilePath& full_path) { - DCHECK([NSThread isMainThread]); NSString* path_string = base::SysUTF8ToNSString(full_path.value()); - NSArray* file_array = - [NSArray arrayWithObject:[path_string lastPathComponent]]; - BOOL status = [[NSWorkspace sharedWorkspace] - performFileOperation:NSWorkspaceRecycleOperation - source:[path_string stringByDeletingLastPathComponent] - destination:@"" - files:file_array - tag:nil]; - if (!path_string || !file_array || !status) + BOOL status = [[NSFileManager defaultManager] + trashItemAtURL:[NSURL fileURLWithPath:path_string] + resultingItemURL:nil + error:nil]; + if (!path_string || !status) LOG(WARNING) << "NSWorkspace failed to move file " << full_path.value() << " to trash"; return status; diff --git a/atom/common/platform_util_win.cc b/atom/common/platform_util_win.cc index 1998b8873189..09ac5aca48f2 100644 --- a/atom/common/platform_util_win.cc +++ b/atom/common/platform_util_win.cc @@ -135,7 +135,7 @@ void OpenItem(const base::FilePath& full_path) { ui::win::OpenFileViaShell(full_path); } -void OpenExternal(const GURL& url) { +bool OpenExternal(const GURL& url) { // 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. @@ -150,12 +150,12 @@ void OpenExternal(const GURL& url) { const size_t kMaxUrlLength = 2048; if (escaped_url.length() > kMaxUrlLength) { NOTREACHED(); - return; + return false; } if (base::win::GetVersion() < base::win::VERSION_WIN7) { if (!ValidateShellCommandForScheme(url.scheme())) - return; + return false; } if (reinterpret_cast(ShellExecuteA(NULL, "open", @@ -164,8 +164,9 @@ void OpenExternal(const GURL& url) { // We fail to execute the call. We could display a message to the user. // TODO(nsylvain): we should also add a dialog to warn on errors. See // bug 1136923. - return; + return false; } + return true; } bool MoveItemToTrash(const base::FilePath& path) { diff --git a/atom/renderer/api/atom_api_renderer_ipc.cc b/atom/renderer/api/atom_api_renderer_ipc.cc index 81209d9cffae..061293e80d03 100644 --- a/atom/renderer/api/atom_api_renderer_ipc.cc +++ b/atom/renderer/api/atom_api_renderer_ipc.cc @@ -5,13 +5,12 @@ #include "atom/common/api/api_messages.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/node_includes.h" #include "content/public/renderer/render_view.h" #include "native_mate/dictionary.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebView.h" -#include "atom/common/node_includes.h" - using content::RenderView; using blink::WebLocalFrame; using blink::WebView; @@ -30,7 +29,9 @@ RenderView* GetCurrentRenderView() { return RenderView::FromWebView(view); } -void Send(const base::string16& channel, const base::ListValue& arguments) { +void Send(mate::Arguments* args, + const base::string16& channel, + const base::ListValue& arguments) { RenderView* render_view = GetCurrentRenderView(); if (render_view == NULL) return; @@ -39,10 +40,11 @@ void Send(const base::string16& channel, const base::ListValue& arguments) { render_view->GetRoutingID(), channel, arguments)); if (!success) - node::ThrowError("Unable to send AtomViewHostMsg_Message"); + args->ThrowError("Unable to send AtomViewHostMsg_Message"); } -base::string16 SendSync(const base::string16& channel, +base::string16 SendSync(mate::Arguments* args, + const base::string16& channel, const base::ListValue& arguments) { base::string16 json; @@ -57,13 +59,13 @@ base::string16 SendSync(const base::string16& channel, bool success = render_view->Send(message); if (!success) - node::ThrowError("Unable to send AtomViewHostMsg_Message_Sync"); + args->ThrowError("Unable to send AtomViewHostMsg_Message_Sync"); return json; } -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("send", &Send); dict.SetMethod("sendSync", &SendSync); diff --git a/atom/renderer/api/atom_api_spell_check_client.cc b/atom/renderer/api/atom_api_spell_check_client.cc index e4ee25ea5eff..25d1e30b0aac 100644 --- a/atom/renderer/api/atom_api_spell_check_client.cc +++ b/atom/renderer/api/atom_api_spell_check_client.cc @@ -41,7 +41,7 @@ bool HasWordCharacters(const base::string16& text, int index) { SpellCheckClient::SpellCheckClient(const std::string& language, bool auto_spell_correct_turned_on, v8::Isolate* isolate, - v8::Handle provider) + v8::Local provider) : auto_spell_correct_turned_on_(auto_spell_correct_turned_on), isolate_(isolate), provider_(isolate, provider) { @@ -160,8 +160,8 @@ bool SpellCheckClient::SpellCheckWord(const base::string16& word_to_check) { return true; v8::HandleScope handle_scope(isolate_); - v8::Handle word = mate::ConvertToV8(isolate_, word_to_check); - v8::Handle result = spell_check_.NewHandle()->Call( + v8::Local word = mate::ConvertToV8(isolate_, word_to_check); + v8::Local result = spell_check_.NewHandle()->Call( provider_.NewHandle(), 1, &word); if (result->IsBoolean()) diff --git a/atom/renderer/api/atom_api_spell_check_client.h b/atom/renderer/api/atom_api_spell_check_client.h index fa616528d482..345cad186ace 100644 --- a/atom/renderer/api/atom_api_spell_check_client.h +++ b/atom/renderer/api/atom_api_spell_check_client.h @@ -22,7 +22,7 @@ class SpellCheckClient : public blink::WebSpellCheckClient { SpellCheckClient(const std::string& language, bool auto_spell_correct_turned_on, v8::Isolate* isolate, - v8::Handle provider); + v8::Local provider); virtual ~SpellCheckClient(); private: diff --git a/atom/renderer/api/atom_api_web_frame.cc b/atom/renderer/api/atom_api_web_frame.cc index ee0d11a0200b..6e2054453a79 100644 --- a/atom/renderer/api/atom_api_web_frame.cc +++ b/atom/renderer/api/atom_api_web_frame.cc @@ -4,12 +4,8 @@ #include "atom/renderer/api/atom_api_web_frame.h" -// This defines are required by SchemeRegistry.h. -#define ALWAYS_INLINE inline -#define OS(WTF_FEATURE) (defined WTF_OS_##WTF_FEATURE && WTF_OS_##WTF_FEATURE) // NOLINT -#define USE(WTF_FEATURE) (defined WTF_USE_##WTF_FEATURE && WTF_USE_##WTF_FEATURE) // NOLINT -#define ENABLE(WTF_FEATURE) (defined ENABLE_##WTF_FEATURE && ENABLE_##WTF_FEATURE) // NOLINT - +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/renderer/api/atom_api_spell_check_client.h" #include "content/public/renderer/render_frame.h" @@ -17,29 +13,11 @@ #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/WebSecurityPolicy.h" #include "third_party/WebKit/public/web/WebView.h" -#include "third_party/WebKit/Source/platform/weborigin/SchemeRegistry.h" #include "atom/common/node_includes.h" -namespace mate { - -template<> -struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Handle val, - WTF::String* out) { - if (!val->IsString()) - return false; - - v8::String::Value s(val); - *out = WTF::String(reinterpret_cast(*s), s.length()); - return true; - } -}; - -} // namespace mate - namespace atom { namespace api { @@ -72,12 +50,24 @@ double WebFrame::GetZoomFactor() const { return blink::WebView::zoomLevelToZoomFactor(GetZoomLevel()); } -v8::Handle WebFrame::RegisterEmbedderCustomElement( - const base::string16& name, v8::Handle options) { +void WebFrame::SetZoomLevelLimits(double min_level, double max_level) { + web_frame_->view()->setDefaultPageScaleLimits(min_level, max_level); +} + +v8::Local WebFrame::RegisterEmbedderCustomElement( + const base::string16& name, v8::Local options) { blink::WebExceptionCode c = 0; return web_frame_->document().registerEmbedderCustomElement(name, options, c); } +void WebFrame::RegisterElementResizeCallback( + int element_instance_id, + const GuestViewContainer::ResizeCallback& callback) { + auto guest_view_container = GuestViewContainer::FromID(element_instance_id); + if (guest_view_container) + guest_view_container->RegisterElementResizeCallback(callback); +} + void WebFrame::AttachGuest(int id) { content::RenderFrame::FromWebFrame(web_frame_)->AttachGuest(id); } @@ -85,7 +75,7 @@ void WebFrame::AttachGuest(int id) { void WebFrame::SetSpellCheckProvider(mate::Arguments* args, const std::string& language, bool auto_spell_correct_turned_on, - v8::Handle provider) { + v8::Local provider) { if (!provider->Has(mate::StringToV8(args->isolate(), "spellCheck"))) { args->ThrowError("\"spellCheck\" has to be defined"); return; @@ -96,6 +86,18 @@ void WebFrame::SetSpellCheckProvider(mate::Arguments* args, web_frame_->view()->setSpellCheckClient(spell_check_client_.get()); } +void WebFrame::RegisterURLSchemeAsSecure(const std::string& scheme) { + // Register scheme to secure list (https, wss, data). + blink::WebSecurityPolicy::registerURLSchemeAsSecure( + blink::WebString::fromUTF8(scheme)); +} + +void WebFrame::RegisterURLSchemeAsBypassingCsp(const std::string& scheme) { + // Register scheme to bypass pages's Content Security Policy. + blink::WebSecurityPolicy::registerURLSchemeAsBypassingContentSecurityPolicy( + blink::WebString::fromUTF8(scheme)); +} + mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( v8::Isolate* isolate) { return mate::ObjectTemplateBuilder(isolate) @@ -104,12 +106,17 @@ mate::ObjectTemplateBuilder WebFrame::GetObjectTemplateBuilder( .SetMethod("getZoomLevel", &WebFrame::GetZoomLevel) .SetMethod("setZoomFactor", &WebFrame::SetZoomFactor) .SetMethod("getZoomFactor", &WebFrame::GetZoomFactor) + .SetMethod("setZoomLevelLimits", &WebFrame::SetZoomLevelLimits) .SetMethod("registerEmbedderCustomElement", &WebFrame::RegisterEmbedderCustomElement) + .SetMethod("registerElementResizeCallback", + &WebFrame::RegisterElementResizeCallback) .SetMethod("attachGuest", &WebFrame::AttachGuest) .SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider) .SetMethod("registerUrlSchemeAsSecure", - &blink::SchemeRegistry::registerURLSchemeAsSecure); + &WebFrame::RegisterURLSchemeAsSecure) + .SetMethod("registerUrlSchemeAsBypassingCsp", + &WebFrame::RegisterURLSchemeAsBypassingCsp); } // static @@ -123,8 +130,8 @@ mate::Handle WebFrame::Create(v8::Isolate* isolate) { namespace { -void Initialize(v8::Handle exports, v8::Handle unused, - v8::Handle context, void* priv) { +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); mate::Dictionary dict(isolate, exports); dict.Set("webFrame", atom::api::WebFrame::Create(isolate)); diff --git a/atom/renderer/api/atom_api_web_frame.h b/atom/renderer/api/atom_api_web_frame.h index aca4d0f0b52b..f3895353b97c 100644 --- a/atom/renderer/api/atom_api_web_frame.h +++ b/atom/renderer/api/atom_api_web_frame.h @@ -7,6 +7,7 @@ #include +#include "atom/renderer/guest_view_container.h" #include "base/memory/scoped_ptr.h" #include "native_mate/handle.h" #include "native_mate/wrappable.h" @@ -40,15 +41,23 @@ class WebFrame : public mate::Wrappable { double SetZoomFactor(double factor); double GetZoomFactor() const; - v8::Handle RegisterEmbedderCustomElement( - const base::string16& name, v8::Handle options); + void SetZoomLevelLimits(double min_level, double max_level); + + v8::Local RegisterEmbedderCustomElement( + const base::string16& name, v8::Local options); + void RegisterElementResizeCallback( + int element_instance_id, + const GuestViewContainer::ResizeCallback& callback); void AttachGuest(int element_instance_id); // Set the provider that will be used by SpellCheckClient for spell check. void SetSpellCheckProvider(mate::Arguments* args, const std::string& language, bool auto_spell_correct_turned_on, - v8::Handle provider); + v8::Local provider); + + void RegisterURLSchemeAsSecure(const std::string& scheme); + void RegisterURLSchemeAsBypassingCsp(const std::string& scheme); // mate::Wrappable: virtual mate::ObjectTemplateBuilder GetObjectTemplateBuilder( diff --git a/atom/renderer/api/lib/remote.coffee b/atom/renderer/api/lib/remote.coffee index 569678476587..abd86e7eee0c 100644 --- a/atom/renderer/api/lib/remote.coffee +++ b/atom/renderer/api/lib/remote.coffee @@ -5,16 +5,31 @@ CallbacksRegistry = require 'callbacks-registry' callbacksRegistry = new CallbacksRegistry +# Check for circular reference. +isCircular = (field, visited) -> + if typeof field is 'object' + if field in visited + return true + visited.push field + return false + # Convert the arguments object into an array of meta data. -wrapArgs = (args) -> +wrapArgs = (args, visited=[]) -> valueToMeta = (value) -> if Array.isArray value - type: 'array', value: wrapArgs(value) + type: 'array', value: wrapArgs(value, visited) + else if Buffer.isBuffer value + type: 'buffer', value: Array::slice.call(value, 0) + else if value? and value.constructor.name is 'Promise' + type: 'promise', then: valueToMeta(value.then.bind(value)) else if value? and typeof value is 'object' and v8Util.getHiddenValue value, 'atomId' type: 'remote-object', id: v8Util.getHiddenValue value, 'atomId' else if value? and typeof value is 'object' ret = type: 'object', name: value.constructor.name, members: [] - ret.members.push(name: prop, value: valueToMeta(field)) for prop, field of value + for prop, field of value + ret.members.push + name: prop + value: valueToMeta(if isCircular(field, visited) then null else field) ret else if typeof value is 'function' and v8Util.getHiddenValue value, 'returnValue' type: 'function-with-return-value', value: valueToMeta(value()) @@ -30,6 +45,8 @@ metaToValue = (meta) -> switch meta.type when 'value' then meta.value when 'array' then (metaToValue(el) for el in meta.members) + when 'buffer' then new Buffer(meta.value) + when 'promise' then Promise.resolve(then: metaToValue(meta.then)) when 'error' throw new Error("#{meta.message}\n#{meta.stack}") else @@ -85,7 +102,7 @@ metaToValue = (meta) -> # Track delegate object's life time, and tell the browser to clean up # when the object is GCed. v8Util.setDestructor ret, -> - ipc.send 'ATOM_BROWSER_DEREFERENCE', meta.storeId + ipc.send 'ATOM_BROWSER_DEREFERENCE', meta.id # Remember object's id. v8Util.setHiddenValue ret, 'atomId', meta.id diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index 2c1166b7e24f..456ca5ba4b24 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -7,48 +7,65 @@ #include #include -#include "atom/common/api/api_messages.h" +// Put this before event_emitter_caller.h to have string16 support. #include "atom/common/native_mate_converters/string16_converter.h" + +#include "atom/common/api/api_messages.h" +#include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/value_converter.h" +#include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "atom/renderer/atom_renderer_client.h" #include "base/command_line.h" #include "base/strings/string_number_conversions.h" #include "content/public/renderer/render_view.h" #include "ipc/ipc_message_macros.h" +#include "net/base/net_module.h" +#include "net/grit/net_resources.h" #include "third_party/WebKit/public/web/WebDraggableRegion.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebKit.h" +#include "third_party/WebKit/public/web/WebScopedUserGesture.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" #include "third_party/WebKit/public/web/WebView.h" - -#include "atom/common/node_includes.h" +#include "ui/base/resource/resource_bundle.h" namespace atom { namespace { bool GetIPCObject(v8::Isolate* isolate, - v8::Handle context, - v8::Handle* ipc) { - v8::Handle key = mate::StringToV8(isolate, "ipc"); - v8::Handle value = context->Global()->GetHiddenValue(key); + v8::Local context, + v8::Local* ipc) { + v8::Local key = mate::StringToV8(isolate, "ipc"); + v8::Local value = context->Global()->GetHiddenValue(key); if (value.IsEmpty() || !value->IsObject()) return false; *ipc = value->ToObject(); return true; } -std::vector> ListValueToVector( +std::vector> ListValueToVector( v8::Isolate* isolate, const base::ListValue& list) { - v8::Handle array = mate::ConvertToV8(isolate, list); - std::vector> result; + v8::Local array = mate::ConvertToV8(isolate, list); + std::vector> result; mate::ConvertFromV8(isolate, array, &result); return result; } +base::StringPiece NetResourceProvider(int key) { + if (key == IDR_DIR_HEADER_HTML) { + base::StringPiece html_data = + ui::ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_DIR_HEADER_HTML); + return html_data; + } + return base::StringPiece(); +} + } // namespace AtomRenderViewObserver::AtomRenderViewObserver( @@ -57,6 +74,8 @@ AtomRenderViewObserver::AtomRenderViewObserver( : content::RenderViewObserver(render_view), renderer_client_(renderer_client), document_created_(false) { + // Initialise resource for directory listing. + net::NetModule::SetResourceProvider(NetResourceProvider); } AtomRenderViewObserver::~AtomRenderViewObserver() { @@ -95,6 +114,8 @@ bool AtomRenderViewObserver::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(AtomRenderViewObserver, message) IPC_MESSAGE_HANDLER(AtomViewMsg_Message, OnBrowserMessage) + IPC_MESSAGE_HANDLER(AtomViewMsg_ExecuteJavaScript, + OnJavaScriptExecuteRequest) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -119,13 +140,28 @@ void AtomRenderViewObserver::OnBrowserMessage(const base::string16& channel, v8::Local context = frame->mainWorldScriptContext(); v8::Context::Scope context_scope(context); - std::vector> arguments = ListValueToVector( - isolate, args); - arguments.insert(arguments.begin(), mate::ConvertToV8(isolate, channel)); + v8::Local ipc; + if (GetIPCObject(isolate, context, &ipc)) { + mate::EmitEvent(isolate, ipc, channel, ListValueToVector(isolate, args)); + } +} - v8::Handle ipc; - if (GetIPCObject(isolate, context, &ipc)) - node::MakeCallback(isolate, ipc, "emit", arguments.size(), &arguments[0]); +void AtomRenderViewObserver::OnJavaScriptExecuteRequest( + const base::string16& code, bool has_user_gesture) { + if (!document_created_) + return; + + if (!render_view()->GetWebView()) + return; + + scoped_ptr gesture( + has_user_gesture ? new blink::WebScopedUserGesture : nullptr); + + v8::Isolate* isolate = blink::mainThreadIsolate(); + v8::HandleScope handle_scope(isolate); + + blink::WebFrame* frame = render_view()->GetWebView()->mainFrame(); + frame->executeScriptAndReturnValue(blink::WebScriptSource(code)); } } // namespace atom diff --git a/atom/renderer/atom_render_view_observer.h b/atom/renderer/atom_render_view_observer.h index 4b9d59f3fa08..85a8c159d97e 100644 --- a/atom/renderer/atom_render_view_observer.h +++ b/atom/renderer/atom_render_view_observer.h @@ -32,6 +32,8 @@ class AtomRenderViewObserver : public content::RenderViewObserver { void OnBrowserMessage(const base::string16& channel, const base::ListValue& args); + void OnJavaScriptExecuteRequest(const base::string16& code, + bool has_user_gesture); // Weak reference to renderer client. AtomRendererClient* renderer_client_; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index d90a90b9415f..b99372bf816d 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -8,22 +8,28 @@ #include "atom/common/api/atom_bindings.h" #include "atom/common/node_bindings.h" +#include "atom/common/node_includes.h" #include "atom/common/options_switches.h" #include "atom/renderer/atom_render_view_observer.h" #include "atom/renderer/guest_view_container.h" +#include "atom/renderer/node_array_buffer_bridge.h" +#include "base/command_line.h" #include "chrome/renderer/pepper/pepper_helper.h" #include "chrome/renderer/printing/print_web_view_helper.h" #include "chrome/renderer/tts_dispatcher.h" #include "content/public/common/content_constants.h" +#include "content/public/renderer/render_frame.h" +#include "content/public/renderer/render_frame_observer.h" #include "content/public/renderer/render_thread.h" -#include "base/command_line.h" #include "third_party/WebKit/public/web/WebCustomElement.h" -#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebPluginParams.h" #include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebRuntimeFeatures.h" -#include "atom/common/node_includes.h" +#if defined(OS_WIN) +#include +#endif namespace atom { @@ -42,16 +48,33 @@ bool IsSwitchEnabled(base::CommandLine* command_line, return true; } -bool IsGuestFrame(blink::WebFrame* frame) { - return frame->uniqueName().utf8() == "ATOM_SHELL_GUEST_WEB_VIEW"; -} +// Helper class to forward the messages to the client. +class AtomRenderFrameObserver : public content::RenderFrameObserver { + public: + AtomRenderFrameObserver(content::RenderFrame* frame, + AtomRendererClient* renderer_client) + : content::RenderFrameObserver(frame), + renderer_client_(renderer_client) {} + + // content::RenderFrameObserver: + void DidCreateScriptContext(v8::Handle context, + int extension_group, + int world_id) { + renderer_client_->DidCreateScriptContext( + render_frame()->GetWebFrame(), context); + } + + private: + AtomRendererClient* renderer_client_; + + DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); +}; } // namespace AtomRendererClient::AtomRendererClient() : node_bindings_(NodeBindings::Create(false)), - atom_bindings_(new AtomBindings), - main_frame_(nullptr) { + atom_bindings_(new AtomBindings) { } AtomRendererClient::~AtomRendererClient() { @@ -63,6 +86,8 @@ void AtomRendererClient::WebKitInitialized() { blink::WebCustomElement::addEmbedderCustomElementName("webview"); blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); + OverrideNodeArrayBuffer(); + node_bindings_->Initialize(); node_bindings_->PrepareMessageLoop(); @@ -78,11 +103,21 @@ void AtomRendererClient::WebKitInitialized() { void AtomRendererClient::RenderThreadStarted() { content::RenderThread::Get()->AddObserver(this); + +#if defined(OS_WIN) + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + base::string16 app_id = + command_line->GetSwitchValueNative(switches::kAppUserModelId); + if (!app_id.empty()) { + SetCurrentProcessExplicitAppUserModelID(app_id.c_str()); + } +#endif } void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { new PepperHelper(render_frame); + new AtomRenderFrameObserver(render_frame, this); } void AtomRendererClient::RenderViewCreated(content::RenderView* render_view) { @@ -109,18 +144,12 @@ bool AtomRendererClient::OverrideCreatePlugin( return true; } -void AtomRendererClient::DidCreateScriptContext(blink::WebFrame* frame, - v8::Handle context, - int extension_group, - int world_id) { - // Only attach node bindings in main frame or guest frame. - if (!IsGuestFrame(frame)) { - if (main_frame_) - return; - - // The first web frame is the main frame. - main_frame_ = frame; - } +void AtomRendererClient::DidCreateScriptContext( + blink::WebFrame* frame, + v8::Handle context) { + // Only insert node integration for the main frame. + if (frame->parent()) + return; // Give the node loop a run to make sure everything is ready. node_bindings_->RunMessageLoop(); @@ -139,20 +168,17 @@ void AtomRendererClient::DidCreateScriptContext(blink::WebFrame* frame, node_bindings_->LoadEnvironment(env); } -bool AtomRendererClient::ShouldFork(blink::WebFrame* frame, +bool AtomRendererClient::ShouldFork(blink::WebLocalFrame* frame, const GURL& url, const std::string& http_method, bool is_initial_navigation, bool is_server_redirect, bool* send_referrer) { - // Never fork renderer process for guests. - if (IsGuestFrame(frame)) - return false; - // Handle all the navigations and reloads in browser. // FIXME We only support GET here because http method will be ignored when // 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"; } @@ -167,6 +193,21 @@ content::BrowserPluginDelegate* AtomRendererClient::CreateBrowserPluginDelegate( } } +bool AtomRendererClient::ShouldOverridePageVisibilityState( + const content::RenderFrame* render_frame, + blink::WebPageVisibilityState* override_state) { + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + bool b; + + if (IsSwitchEnabled(command_line, switches::kPageVisibility, &b) + && b) { + *override_state = blink::WebPageVisibilityStateVisible; + return true; + } + + return false; +} + void AtomRendererClient::EnableWebRuntimeFeatures() { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); bool b; diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index a7096125580c..206ed9f9b9b7 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -21,6 +21,9 @@ class AtomRendererClient : public content::ContentRendererClient, AtomRendererClient(); virtual ~AtomRendererClient(); + void DidCreateScriptContext(blink::WebFrame* frame, + v8::Handle context); + private: enum NodeIntegration { ALL, @@ -42,11 +45,7 @@ class AtomRendererClient : public content::ContentRendererClient, blink::WebLocalFrame* frame, const blink::WebPluginParams& params, blink::WebPlugin** plugin) override; - void DidCreateScriptContext(blink::WebFrame* frame, - v8::Handle context, - int extension_group, - int world_id) override; - bool ShouldFork(blink::WebFrame* frame, + bool ShouldFork(blink::WebLocalFrame* frame, const GURL& url, const std::string& http_method, bool is_initial_navigation, @@ -56,15 +55,15 @@ class AtomRendererClient : public content::ContentRendererClient, content::RenderFrame* render_frame, const std::string& mime_type, const GURL& original_url) override; + bool ShouldOverridePageVisibilityState( + const content::RenderFrame* render_frame, + blink::WebPageVisibilityState* override_state) override; void EnableWebRuntimeFeatures(); scoped_ptr node_bindings_; scoped_ptr atom_bindings_; - // The main frame. - blink::WebFrame* main_frame_; - DISALLOW_COPY_AND_ASSIGN(AtomRendererClient); }; diff --git a/atom/renderer/guest_view_container.cc b/atom/renderer/guest_view_container.cc index f23c36dfd356..f50c3f78685c 100644 --- a/atom/renderer/guest_view_container.cc +++ b/atom/renderer/guest_view_container.cc @@ -4,12 +4,62 @@ #include "atom/renderer/guest_view_container.h" +#include + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/message_loop/message_loop.h" +#include "ui/gfx/geometry/size.h" + namespace atom { -GuestViewContainer::GuestViewContainer(content::RenderFrame* render_frame) { +namespace { + +using GuestViewContainerMap = std::map; +static base::LazyInstance g_guest_view_container_map = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +GuestViewContainer::GuestViewContainer(content::RenderFrame* render_frame) + : render_frame_(render_frame), + weak_ptr_factory_(this) { } GuestViewContainer::~GuestViewContainer() { + if (element_instance_id_ > 0) + g_guest_view_container_map.Get().erase(element_instance_id_); +} + +// static +GuestViewContainer* GuestViewContainer::FromID(int element_instance_id) { + GuestViewContainerMap* guest_view_containers = + g_guest_view_container_map.Pointer(); + auto it = guest_view_containers->find(element_instance_id); + return it == guest_view_containers->end() ? nullptr : it->second; +} + +void GuestViewContainer::RegisterElementResizeCallback( + const ResizeCallback& callback) { + element_resize_callback_ = callback; +} + +void GuestViewContainer::SetElementInstanceID(int element_instance_id) { + element_instance_id_ = element_instance_id; + g_guest_view_container_map.Get().insert( + std::make_pair(element_instance_id, this)); +} + +void GuestViewContainer::DidResizeElement(const gfx::Size& new_size) { + if (element_resize_callback_.is_null()) + return; + + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(element_resize_callback_, new_size)); +} + +base::WeakPtr GuestViewContainer::GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); } } // namespace atom diff --git a/atom/renderer/guest_view_container.h b/atom/renderer/guest_view_container.h index 5a21dad028ff..3771c7adc4e6 100644 --- a/atom/renderer/guest_view_container.h +++ b/atom/renderer/guest_view_container.h @@ -5,17 +5,39 @@ #ifndef ATOM_RENDERER_GUEST_VIEW_CONTAINER_H_ #define ATOM_RENDERER_GUEST_VIEW_CONTAINER_H_ +#include "base/callback.h" #include "content/public/renderer/browser_plugin_delegate.h" -#include "v8/include/v8.h" + +namespace gfx { +class Size; +} namespace atom { class GuestViewContainer : public content::BrowserPluginDelegate { public: + typedef base::Callback ResizeCallback; + explicit GuestViewContainer(content::RenderFrame* render_frame); ~GuestViewContainer() override; + static GuestViewContainer* FromID(int element_instance_id); + + void RegisterElementResizeCallback(const ResizeCallback& callback); + + // content::BrowserPluginDelegate: + void SetElementInstanceID(int element_instance_id) final; + void DidResizeElement(const gfx::Size& new_size) final; + base::WeakPtr GetWeakPtr() final; + private: + int element_instance_id_; + content::RenderFrame* render_frame_; + + ResizeCallback element_resize_callback_; + + base::WeakPtrFactory weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(GuestViewContainer); }; diff --git a/atom/renderer/lib/init.coffee b/atom/renderer/lib/init.coffee index 1e0167d6e154..9825f75be928 100644 --- a/atom/renderer/lib/init.coffee +++ b/atom/renderer/lib/init.coffee @@ -29,8 +29,6 @@ for arg in process.argv if arg.indexOf('--guest-instance-id=') == 0 # This is a guest web view. process.guestInstanceId = parseInt arg.substr(arg.indexOf('=') + 1) - # Set the frame name to make AtomRendererClient recognize this guest. - require('web-frame').setName 'ATOM_SHELL_GUEST_WEB_VIEW' else if arg.indexOf('--node-integration=') == 0 nodeIntegration = arg.substr arg.indexOf('=') + 1 else if arg.indexOf('--preload=') == 0 @@ -77,7 +75,7 @@ if nodeIntegration in ['true', 'all', 'except-iframe', 'manual-enable-iframe'] global.__dirname = __dirname # Redirect window.onerror to uncaughtException. - window.onerror = (error) -> + window.onerror = (message, filename, lineno, colno, error) -> if global.process.listeners('uncaughtException').length > 0 global.process.emit 'uncaughtException', error true @@ -88,15 +86,12 @@ if nodeIntegration in ['true', 'all', 'except-iframe', 'manual-enable-iframe'] window.addEventListener 'unload', -> process.emit 'exit' else - # The Module.runMain will run process._tickCallck() immediately, so we are - # able to delete the symbols in this tick even though we used process.nextTick - # to schedule it. - # It is important that we put this in process.nextTick, if we delete them now - # some code in node.js will complain about "process not defined". - process.nextTick -> + # Delete Node's symbols after the Environment has been loaded. + process.once 'loaded', -> delete global.process delete global.setImmediate delete global.clearImmediate + delete global.global # Load the script specfied by the "preload" attribute. if preloadScript diff --git a/atom/renderer/lib/inspector.coffee b/atom/renderer/lib/inspector.coffee index 569f7e5ec178..126f68f9608e 100644 --- a/atom/renderer/lib/inspector.coffee +++ b/atom/renderer/lib/inspector.coffee @@ -1,11 +1,9 @@ window.onload = -> - inspectorFrame = document.getElementById('inspector-app-iframe').contentWindow - # Use menu API to show context menu. - inspectorFrame.eval 'InspectorFrontendHost.showContextMenuAtPoint = parent.createMenu' + InspectorFrontendHost.showContextMenuAtPoint = createMenu # Use dialog API to override file chooser dialog. - inspectorFrame.eval 'WebInspector.createFileSelectorElement = parent.createFileSelectorElement' + WebInspector.createFileSelectorElement = createFileSelectorElement convertToMenuTemplate = (items) -> template = [] @@ -60,7 +58,3 @@ createFileSelectorElement = (callback) -> fileSelectorElement.style.display = 'none' fileSelectorElement.click = showFileChooserDialog.bind this, callback return fileSelectorElement - -# Exposed for iframe. -window.createMenu = createMenu -window.createFileSelectorElement = createFileSelectorElement diff --git a/atom/renderer/lib/override.coffee b/atom/renderer/lib/override.coffee index 1ca6e692ba21..b60374e04754 100644 --- a/atom/renderer/lib/override.coffee +++ b/atom/renderer/lib/override.coffee @@ -2,9 +2,16 @@ process = global.process ipc = require 'ipc' remote = require 'remote' +# Helper function to resolve relative url. +a = window.top.document.createElement 'a' +resolveUrl = (url) -> + a.href = url + a.href + # Window object returned by "window.open". class BrowserWindowProxy constructor: (@guestId) -> + @closed = false ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', (guestId) => if guestId is @guestId @closed = true @@ -45,12 +52,21 @@ window.open = (url, frameName='', features='') -> value options.x ?= options.left if options.left options.y ?= options.top if options.top - options.title ?= name + options.title ?= frameName options.width ?= 800 options.height ?= 600 + # Resolve relative urls. + url = resolveUrl url + (options[name] = parseInt(options[name], 10) if options[name]?) for name in ints + # Inherit the node-integration option of current window. + unless options['node-integration']? + for arg in process.argv when arg.indexOf('--node-integration=') is 0 + options['node-integration'] = arg.substr(-4) is 'true' + break + guestId = ipc.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, options if guestId new BrowserWindowProxy(guestId) @@ -62,29 +78,49 @@ window.open = (url, frameName='', features='') -> window.alert = (message, title='') -> dialog = remote.require 'dialog' buttons = ['OK'] + message = message.toString() dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons} + # Alert should always return undefined. + return # And the confirm(). window.confirm = (message, title='') -> dialog = remote.require 'dialog' buttons = ['OK', 'Cancel'] - not dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons} + cancelId = 1 + not dialog.showMessageBox remote.getCurrentWindow(), {message, title, buttons, cancelId} # But we do not support prompt(). window.prompt = -> throw new Error('prompt() is and will not be supported.') -# Simple implementation of postMessage. -window.opener = - postMessage: (message, targetOrigin='*') -> - ipc.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', message, targetOrigin +# Implement window.postMessage if current window is a guest window. +guestId = ipc.sendSync 'ATOM_SHELL_GUEST_WINDOW_MANAGER_GET_GUEST_ID' +if guestId? + window.opener = + postMessage: (message, targetOrigin='*') -> + ipc.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', guestId, message, targetOrigin, location.origin -ipc.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (message, targetOrigin) -> - window.postMessage message, targetOrigin +ipc.on 'ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', (guestId, 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 = new BrowserWindowProxy(guestId) + window.dispatchEvent event # Forward history operations to browser. sendHistoryOperation = (args...) -> ipc.send 'ATOM_SHELL_NAVIGATION_CONTROLLER', args... + +getHistoryOperation = (args...) -> + ipc.sendSync 'ATOM_SHELL_SYNC_NAVIGATION_CONTROLLER', args... + window.history.back = -> sendHistoryOperation 'goBack' window.history.forward = -> sendHistoryOperation 'goForward' window.history.go = (offset) -> sendHistoryOperation 'goToOffset', offset +Object.defineProperty window.history, 'length', + get: -> + getHistoryOperation 'length' diff --git a/atom/renderer/lib/web-view/guest-view-internal.coffee b/atom/renderer/lib/web-view/guest-view-internal.coffee index 04aa5c5494f0..b491184fb8d7 100644 --- a/atom/renderer/lib/web-view/guest-view-internal.coffee +++ b/atom/renderer/lib/web-view/guest-view-internal.coffee @@ -4,29 +4,36 @@ webFrame = require 'web-frame' requestId = 0 WEB_VIEW_EVENTS = + 'load-commit': ['url', 'isMainFrame'] 'did-finish-load': [] - 'did-fail-load': ['errorCode', 'errorDescription'] + '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'] + 'httpResponseCode', 'requestMethod', 'referrer', + 'headers'] 'did-get-redirect-request': ['oldUrl', 'newUrl', 'isMainFrame'] 'dom-ready': [] 'console-message': ['level', 'message', 'line', 'sourceId'] 'new-window': ['url', 'frameName', 'disposition'] 'close': [] 'crashed': [] + 'gpu-crashed': [] + 'plugin-crashed': ['name', 'version'] 'destroyed': [] 'page-title-set': ['title', 'explicitSet'] 'page-favicon-updated': ['favicons'] + 'enter-html-full-screen': [] + 'leave-html-full-screen': [] dispatchEvent = (webView, event, args...) -> - throw new Error("Unkown event #{event}") unless WEB_VIEW_EVENTS[event]? + throw new Error("Unknown event #{event}") unless WEB_VIEW_EVENTS[event]? domEvent = new Event(event) for f, i in WEB_VIEW_EVENTS[event] domEvent[f] = args[i] webView.dispatchEvent domEvent + webView.onLoadCommit domEvent if event == 'load-commit' module.exports = registerEvents: (webView, viewInstanceId) -> @@ -50,9 +57,9 @@ module.exports = ipc.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_IPC_MESSAGE-#{viewInstanceId}" ipc.removeAllListeners "ATOM_SHELL_GUEST_VIEW_INTERNAL_SIZE_CHANGED-#{viewInstanceId}" - createGuest: (type, params, callback) -> + createGuest: (params, callback) -> requestId++ - ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', type, params, requestId + ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_CREATE_GUEST', params, requestId ipc.once "ATOM_SHELL_RESPONSE_#{requestId}", callback attachGuest: (elementInstanceId, guestInstanceId, params) -> @@ -62,8 +69,8 @@ module.exports = destroyGuest: (guestInstanceId) -> ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_DESTROY_GUEST', guestInstanceId - setAutoSize: (guestInstanceId, params) -> - ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_AUTO_SIZE', guestInstanceId, params + setSize: (guestInstanceId, params) -> + ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_SIZE', guestInstanceId, params setAllowTransparency: (guestInstanceId, allowtransparency) -> ipc.send 'ATOM_SHELL_GUEST_VIEW_MANAGER_SET_ALLOW_TRANSPARENCY', guestInstanceId, allowtransparency diff --git a/atom/renderer/lib/web-view/web-view-attributes.coffee b/atom/renderer/lib/web-view/web-view-attributes.coffee index 05bd6141203f..acca826a9e3d 100644 --- a/atom/renderer/lib/web-view/web-view-attributes.coffee +++ b/atom/renderer/lib/web-view/web-view-attributes.coffee @@ -28,7 +28,7 @@ class WebViewAttribute # Changes the attribute's value without triggering its mutation handler. setValueIgnoreMutation: (value) -> @ignoreMutation = true - @webViewImpl.webviewNode.setAttribute(@name, value || '') + @setValue value @ignoreMutation = false # Defines this attribute as a property on the webview node. @@ -72,7 +72,7 @@ class AutosizeDimensionAttribute extends WebViewAttribute handleMutation: (oldValue, newValue) -> return unless @webViewImpl.guestInstanceId - guestViewInternal.setAutoSize @webViewImpl.guestInstanceId, + guestViewInternal.setSize @webViewImpl.guestInstanceId, enableAutoSize: @webViewImpl.attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() min: width: parseInt @webViewImpl.attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() || 0 @@ -119,6 +119,14 @@ class SrcAttribute extends WebViewAttribute else '' + setValueIgnoreMutation: (value) -> + WebViewAttribute::setValueIgnoreMutation.call(this, 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. + @observer.takeRecords() + 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 @@ -138,7 +146,10 @@ class SrcAttribute extends WebViewAttribute setupMutationObserver: -> @observer = new MutationObserver (mutations) => for mutation in mutations - @handleMutation mutation.oldValue, @getValue() + oldValue = mutation.oldValue + newValue = @getValue() + return if oldValue isnt newValue + @handleMutation oldValue, newValue params = attributes: true, attributeOldValue: true, @@ -158,15 +169,26 @@ class SrcAttribute extends WebViewAttribute return # Navigate to |this.src|. + opts = {} httpreferrer = @webViewImpl.attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER].getValue() - urlOptions = if httpreferrer then {httpreferrer} else {} - remote.getGuestWebContents(@webViewImpl.guestInstanceId).loadUrl @getValue(), urlOptions + if httpreferrer then opts.httpReferrer = httpreferrer + + useragent = @webViewImpl.attributes[webViewConstants.ATTRIBUTE_USERAGENT].getValue() + if useragent then opts.userAgent = useragent + + guestContents = remote.getGuestWebContents(@webViewImpl.guestInstanceId) + guestContents.loadUrl @getValue(), opts # Attribute specifies HTTP referrer. class HttpReferrerAttribute extends WebViewAttribute constructor: (webViewImpl) -> super webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl +# Attribute specifies user agent +class UserAgentAttribute extends WebViewAttribute + constructor: (webViewImpl) -> + super webViewConstants.ATTRIBUTE_USERAGENT, webViewImpl + # Attribute that set preload script. class PreloadAttribute extends WebViewAttribute constructor: (webViewImpl) -> @@ -190,6 +212,7 @@ WebViewImpl::setupWebViewAttributes = -> @attributes[webViewConstants.ATTRIBUTE_PARTITION] = new PartitionAttribute(this) @attributes[webViewConstants.ATTRIBUTE_SRC] = new SrcAttribute(this) @attributes[webViewConstants.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this) + @attributes[webViewConstants.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this) @attributes[webViewConstants.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(webViewConstants.ATTRIBUTE_NODEINTEGRATION, this) @attributes[webViewConstants.ATTRIBUTE_PLUGINS] = new BooleanAttribute(webViewConstants.ATTRIBUTE_PLUGINS, this) @attributes[webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(webViewConstants.ATTRIBUTE_DISABLEWEBSECURITY, this) diff --git a/atom/renderer/lib/web-view/web-view-constants.coffee b/atom/renderer/lib/web-view/web-view-constants.coffee index cda715423dd4..deb678e6a1e8 100644 --- a/atom/renderer/lib/web-view/web-view-constants.coffee +++ b/atom/renderer/lib/web-view/web-view-constants.coffee @@ -14,6 +14,7 @@ module.exports = ATTRIBUTE_PLUGINS: 'plugins' ATTRIBUTE_DISABLEWEBSECURITY: 'disablewebsecurity' ATTRIBUTE_PRELOAD: 'preload' + ATTRIBUTE_USERAGENT: 'useragent' # Internal attribute. ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid' diff --git a/atom/renderer/lib/web-view/web-view.coffee b/atom/renderer/lib/web-view/web-view.coffee index bf163c0cfb5c..65e4501975fe 100644 --- a/atom/renderer/lib/web-view/web-view.coffee +++ b/atom/renderer/lib/web-view/web-view.coffee @@ -13,7 +13,6 @@ class WebViewImpl constructor: (@webviewNode) -> v8Util.setHiddenValue @webviewNode, 'internal', this @attached = false - @pendingGuestCreation = false @elementAttached = false @beforeFirstNavigation = true @@ -87,9 +86,12 @@ class WebViewImpl @browserPluginNode.removeAttribute webViewConstants.ATTRIBUTE_INTERNALINSTANCEID @internalInstanceId = parseInt newValue + # Track when the element resizes using the element resize callback. + webFrame.registerElementResizeCallback @internalInstanceId, @onElementResize.bind(this) + return unless @guestInstanceId - guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildAttachParams() + guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildParams() onSizeChanged: (webViewEvent) -> newWidth = webViewEvent.newWidth @@ -107,6 +109,9 @@ class WebViewImpl minWidth = @attributes[webViewConstants.ATTRIBUTE_MINWIDTH].getValue() | width minHeight = @attributes[webViewConstants.ATTRIBUTE_MINHEIGHT].getValue() | width + minWidth = Math.min minWidth, maxWidth + minHeight = Math.min minHeight, maxHeight + if not @attributes[webViewConstants.ATTRIBUTE_AUTOSIZE].getValue() or (newWidth >= minWidth and newWidth <= maxWidth and @@ -118,17 +123,19 @@ class WebViewImpl # changed. @dispatchEvent webViewEvent + onElementResize: (newSize) -> + # Dispatch the 'resize' event. + resizeEvent = new Event('resize', bubbles: true) + resizeEvent.newWidth = newSize.width + resizeEvent.newHeight = newSize.height + @dispatchEvent resizeEvent + + if @guestInstanceId + guestViewInternal.setSize @guestInstanceId, normal: newSize + createGuest: -> - return if @pendingGuestCreation - params = - storagePartitionId: @attributes[webViewConstants.ATTRIBUTE_PARTITION].getValue() - guestViewInternal.createGuest 'webview', params, (guestInstanceId) => - @pendingGuestCreation = false - unless @elementAttached - guestViewInternal.destroyGuest guestInstanceId - return + guestViewInternal.createGuest @buildParams(), (guestInstanceId) => @attachWindow guestInstanceId - @pendingGuestCreation = true dispatchEvent: (webViewEvent) -> @webviewNode.dispatchEvent webViewEvent @@ -148,10 +155,10 @@ class WebViewImpl enumerable: true # Updates state upon loadcommit. - onLoadCommit: (@baseUrlForDataUrl, @currentEntryIndex, @entryCount, @processId, url, isTopLevel) -> + onLoadCommit: (webViewEvent) -> oldValue = @webviewNode.getAttribute webViewConstants.ATTRIBUTE_SRC - newValue = url - if isTopLevel and (oldValue != newValue) + newValue = webViewEvent.url + if webViewEvent.isMainFrame and (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 @@ -160,21 +167,30 @@ class WebViewImpl onAttach: (storagePartitionId) -> @attributes[webViewConstants.ATTRIBUTE_PARTITION].setValue storagePartitionId - buildAttachParams: -> + buildParams: -> params = instanceId: @viewInstanceId userAgentOverride: @userAgentOverride - for attributeName, attribute of @attributes + for own attributeName, attribute of @attributes 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 @webviewNode, null + elementRect = @webviewNode.getBoundingClientRect() + params.elementWidth = parseInt(elementRect.width) || + parseInt(css.getPropertyValue('width')) + params.elementHeight = parseInt(elementRect.height) || + parseInt(css.getPropertyValue('height')) params attachWindow: (guestInstanceId) -> @guestInstanceId = guestInstanceId - params = @buildAttachParams() - return true unless @internalInstanceId - guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, params + guestViewInternal.attachGuest @internalInstanceId, @guestInstanceId, @buildParams() # Registers browser plugin custom element. registerBrowserPluginElement = -> @@ -244,23 +260,28 @@ registerWebViewElement = -> "canGoBack" "canGoForward" "canGoToOffset" + "clearHistory" "goBack" "goForward" "goToIndex" "goToOffset" "isCrashed" "setUserAgent" + "getUserAgent" "executeJavaScript" "insertCSS" "openDevTools" "closeDevTools" "isDevToolsOpened" "inspectElement" + "setAudioMuted" + "isAudioMuted" "undo" "redo" "cut" "copy" "paste" + "pasteAndMatchStyle" "delete" "selectAll" "unselect" @@ -268,6 +289,10 @@ registerWebViewElement = -> "replaceMisspelling" "send" "getId" + "inspectServiceWorker" + "print" + "printToPDF" + "sendInputEvent" ] # Forward proto.foo* method calls to WebViewImpl.foo*. diff --git a/atom/renderer/node_array_buffer_bridge.cc b/atom/renderer/node_array_buffer_bridge.cc new file mode 100644 index 000000000000..80f2530524d2 --- /dev/null +++ b/atom/renderer/node_array_buffer_bridge.cc @@ -0,0 +1,66 @@ +// 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 "atom/renderer/node_array_buffer_bridge.h" + +#include "base/basictypes.h" +#include "atom/common/node_includes.h" +#include "native_mate/converter.h" +#include "third_party/WebKit/public/web/WebArrayBuffer.h" +#include "third_party/WebKit/public/web/WebArrayBufferConverter.h" + +namespace atom { + +namespace { + +// global.Uint8Array; +v8::Local GetUint8ArrayConstructor( + v8::Isolate* isolate, v8::Local context) { + v8::Local constructor = context->Global()->Get( + mate::StringToV8(isolate, "Uint8Array")); + return v8::Local::Cast(constructor); +} + +// new ArrayBuffer(size); +v8::Local BlinkArrayBufferNew( + v8::Isolate* isolate, size_t size) { + blink::WebArrayBuffer buffer = blink::WebArrayBuffer::create(size, 1); + return v8::Local::Cast( + blink::WebArrayBufferConverter::toV8Value( + &buffer, isolate->GetCurrentContext()->Global(), isolate)); +} + +// new ArrayBuffer(data, size); +v8::Local BlinkArrayBufferNewWith( + v8::Isolate* isolate, void* data, size_t size) { + blink::WebArrayBuffer buffer = blink::WebArrayBuffer::createExternal( + data, size); + return v8::Local::Cast( + blink::WebArrayBufferConverter::toV8Value( + &buffer, isolate->GetCurrentContext()->Global(), isolate)); +} + +// new Uint8Array(array_buffer, offset, size); +v8::Local BlinkUint8ArrayNew( + v8::Local ab, size_t offset, size_t size) { + // Use the DOM's Uint8Array constructor to create Uint8Array. + v8::Local context = ab->CreationContext(); + v8::Isolate* isolate = context->GetIsolate(); + v8::Local constructor = + GetUint8ArrayConstructor(isolate, context); + v8::Local args[] = { + ab, mate::ConvertToV8(isolate, offset), mate::ConvertToV8(isolate, size) + }; + return v8::Local::Cast(constructor->NewInstance( + context, arraysize(args), args).ToLocalChecked()); +} + +} // namespace + +void OverrideNodeArrayBuffer() { + node::Buffer::SetArrayBufferCreator( + BlinkArrayBufferNew, BlinkArrayBufferNewWith, BlinkUint8ArrayNew); +} + +} // namespace atom diff --git a/atom/renderer/node_array_buffer_bridge.h b/atom/renderer/node_array_buffer_bridge.h new file mode 100644 index 000000000000..61d180699312 --- /dev/null +++ b/atom/renderer/node_array_buffer_bridge.h @@ -0,0 +1,15 @@ +// 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_RENDERER_NODE_ARRAY_BUFFER_BRIDGE_H_ +#define ATOM_RENDERER_NODE_ARRAY_BUFFER_BRIDGE_H_ + +namespace atom { + +// Override Node's ArrayBuffer with DOM's ArrayBuffer. +void OverrideNodeArrayBuffer(); + +} // namespace atom + +#endif // ATOM_RENDERER_NODE_ARRAY_BUFFER_BRIDGE_H_ diff --git a/atom/utility/atom_content_utility_client.cc b/atom/utility/atom_content_utility_client.cc index cc739227aad8..2e591f2c6a8d 100644 --- a/atom/utility/atom_content_utility_client.cc +++ b/atom/utility/atom_content_utility_client.cc @@ -17,7 +17,7 @@ #if defined(OS_WIN) -#include "chrome/utility/printing_handler.h" +#include "chrome/utility/printing_handler_win.h" #endif @@ -37,7 +37,7 @@ int64_t AtomContentUtilityClient::max_ipc_message_size_ = AtomContentUtilityClient::AtomContentUtilityClient() : filter_messages_(false) { #if defined(OS_WIN) - handlers_.push_back(new PrintingHandler()); + handlers_.push_back(new PrintingHandlerWin()); #endif } @@ -71,4 +71,11 @@ void AtomContentUtilityClient::OnStartupPing() { // Don't release the process, we assume further messages are on the way. } +// static +void AtomContentUtilityClient::PreSandboxStartup() { +#if defined(OS_WIN) + PrintingHandlerWin::PreSandboxStartup(); +#endif +} + } // namespace atom diff --git a/atom/utility/atom_content_utility_client.h b/atom/utility/atom_content_utility_client.h index 2c245b62f61e..756193d6d44c 100644 --- a/atom/utility/atom_content_utility_client.h +++ b/atom/utility/atom_content_utility_client.h @@ -31,6 +31,7 @@ class AtomContentUtilityClient : public content::ContentUtilityClient { void UtilityThreadStarted() override; bool OnMessageReceived(const IPC::Message& message) override; + static void PreSandboxStartup(); static void set_max_ipc_message_size_for_test(int64_t max_message_size) { max_ipc_message_size_ = max_message_size; diff --git a/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc b/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc index 248ec6b890ef..8ed234d5e81b 100644 --- a/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc +++ b/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.cc @@ -4,11 +4,14 @@ #include "chrome/browser/extensions/global_shortcut_listener_win.h" +#include "base/bind.h" +#include "base/bind_helpers.h" #include "base/win/win_util.h" #include "content/public/browser/browser_thread.h" #include "ui/base/accelerators/accelerator.h" #include "ui/events/event_constants.h" #include "ui/events/keycodes/keyboard_code_conversion_win.h" +#include "ui/gfx/win/singleton_hwnd.h" using content::BrowserThread; @@ -35,14 +38,17 @@ GlobalShortcutListenerWin::~GlobalShortcutListenerWin() { void GlobalShortcutListenerWin::StartListening() { DCHECK(!is_listening_); // Don't start twice. DCHECK(!hotkey_ids_.empty()); // Also don't start if no hotkey is registered. - gfx::SingletonHwnd::GetInstance()->AddObserver(this); + singleton_hwnd_observer_.reset(new gfx::SingletonHwndObserver( + base::Bind( + &GlobalShortcutListenerWin::OnWndProc, base::Unretained(this)))); + is_listening_ = true; } void GlobalShortcutListenerWin::StopListening() { DCHECK(is_listening_); // No point if we are not already listening. DCHECK(hotkey_ids_.empty()); // Make sure the map is clean before ending. - gfx::SingletonHwnd::GetInstance()->RemoveObserver(this); + singleton_hwnd_observer_.reset(nullptr); is_listening_ = false; } diff --git a/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h b/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h index a155d8f8991f..b3917e9a160e 100644 --- a/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h +++ b/chromium_src/chrome/browser/extensions/global_shortcut_listener_win.h @@ -7,26 +7,24 @@ #include +#include "base/memory/scoped_ptr.h" #include "chrome/browser/extensions/global_shortcut_listener.h" #include "ui/gfx/win/singleton_hwnd.h" +#include "ui/gfx/win/singleton_hwnd_observer.h" namespace extensions { // Windows-specific implementation of the GlobalShortcutListener class that // listens for global shortcuts. Handles setting up a keyboard hook and // forwarding its output to the base class for processing. -class GlobalShortcutListenerWin : public GlobalShortcutListener, - public gfx::SingletonHwnd::Observer { +class GlobalShortcutListenerWin : public GlobalShortcutListener { public: GlobalShortcutListenerWin(); virtual ~GlobalShortcutListenerWin(); private: - // The implementation of our Window Proc, called by SingletonHwnd. - virtual void OnWndProc(HWND hwnd, - UINT message, - WPARAM wparam, - LPARAM lparam) override; + // The implementation of our Window Proc, called by SingletonHwndObserver. + void OnWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); // GlobalShortcutListener implementation. virtual void StartListening() override; @@ -43,6 +41,8 @@ class GlobalShortcutListenerWin : public GlobalShortcutListener, typedef std::map HotkeyIdMap; HotkeyIdMap hotkey_ids_; + scoped_ptr singleton_hwnd_observer_; + DISALLOW_COPY_AND_ASSIGN(GlobalShortcutListenerWin); }; 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 89bac3b68dca..9064cd961bd5 100644 --- a/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc +++ b/chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc @@ -296,8 +296,7 @@ void PdfToEmfUtilityProcessHostClient::Start( // generate when sent to a metafile DC. utility_process_host_ = content::UtilityProcessHost::Create( - this, base::MessageLoop::current()->message_loop_proxy()) - ->AsWeakPtr(); + this, base::MessageLoop::current()->task_runner())->AsWeakPtr(); if (!utility_process_host_) return OnFailed(); // Should reply with OnProcessStarted(). diff --git a/chromium_src/chrome/browser/printing/print_job.cc b/chromium_src/chrome/browser/printing/print_job.cc index 6bcc58322124..65449ef121e3 100644 --- a/chromium_src/chrome/browser/printing/print_job.cc +++ b/chromium_src/chrome/browser/printing/print_job.cc @@ -220,64 +220,6 @@ PrintedDocument* PrintJob::document() const { return document_.get(); } -void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) { - if (document_.get() == new_document) - return; - - document_ = new_document; - - if (document_.get()) { - settings_ = document_->settings(); - } - - if (worker_) { - DCHECK(!is_job_pending_); - // Sync the document with the worker. - worker_->PostTask(FROM_HERE, - base::Bind(&HoldRefCallback, - make_scoped_refptr(this), - base::Bind(&PrintJobWorker::OnDocumentChanged, - base::Unretained(worker_.get()), - document_))); - } -} - -void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) { - switch (event_details.type()) { - case JobEventDetails::FAILED: { - settings_.Clear(); - // No need to cancel since the worker already canceled itself. - Stop(); - break; - } - case JobEventDetails::USER_INIT_DONE: - case JobEventDetails::DEFAULT_INIT_DONE: - case JobEventDetails::USER_INIT_CANCELED: { - DCHECK_EQ(event_details.document(), document_.get()); - break; - } - case JobEventDetails::NEW_DOC: - case JobEventDetails::NEW_PAGE: - case JobEventDetails::JOB_DONE: - case JobEventDetails::ALL_PAGES_REQUESTED: { - // Don't care. - break; - } - case JobEventDetails::DOC_DONE: { - // This will call Stop() and broadcast a JOB_DONE message. - base::MessageLoop::current()->PostTask( - FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this)); - break; - } - case JobEventDetails::PAGE_DONE: - break; - default: { - NOTREACHED(); - break; - } - } -} - #if defined(OS_WIN) class PrintJob::PdfToEmfState { @@ -375,6 +317,68 @@ void PrintJob::OnPdfToEmfPageConverted(int page_number, #endif // OS_WIN +void PrintJob::UpdatePrintedDocument(PrintedDocument* new_document) { + if (document_.get() == new_document) + return; + + document_ = new_document; + + if (document_.get()) { + settings_ = document_->settings(); + } + + if (worker_) { + DCHECK(!is_job_pending_); + // Sync the document with the worker. + worker_->PostTask(FROM_HERE, + base::Bind(&HoldRefCallback, + make_scoped_refptr(this), + base::Bind(&PrintJobWorker::OnDocumentChanged, + base::Unretained(worker_.get()), + document_))); + } +} + +void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) { + switch (event_details.type()) { + case JobEventDetails::FAILED: { + settings_.Clear(); + // No need to cancel since the worker already canceled itself. + Stop(); + break; + } + case JobEventDetails::USER_INIT_DONE: + case JobEventDetails::DEFAULT_INIT_DONE: + case JobEventDetails::USER_INIT_CANCELED: { + DCHECK_EQ(event_details.document(), document_.get()); + break; + } + case JobEventDetails::NEW_DOC: + case JobEventDetails::NEW_PAGE: + case JobEventDetails::JOB_DONE: + case JobEventDetails::ALL_PAGES_REQUESTED: { + // Don't care. + break; + } + case JobEventDetails::DOC_DONE: { + // This will call Stop() and broadcast a JOB_DONE message. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&PrintJob::OnDocumentDone, this)); + break; + } + case JobEventDetails::PAGE_DONE: +#if defined(OS_WIN) + ptd_to_emf_state_->OnPageProcessed( + base::Bind(&PrintJob::OnPdfToEmfPageConverted, this)); +#endif // OS_WIN + break; + default: { + NOTREACHED(); + break; + } + } +} + void PrintJob::OnDocumentDone() { // Be sure to live long enough. The instance could be destroyed by the // JOB_DONE broadcast. diff --git a/chromium_src/chrome/browser/printing/print_preview_message_handler.cc b/chromium_src/chrome/browser/printing/print_preview_message_handler.cc new file mode 100644 index 000000000000..613f3f2343ce --- /dev/null +++ b/chromium_src/chrome/browser/printing/print_preview_message_handler.cc @@ -0,0 +1,140 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/printing/print_preview_message_handler.h" + +#include "base/bind.h" +#include "base/memory/shared_memory.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/printing/print_job_manager.h" +#include "chrome/browser/printing/printer_query.h" +#include "chrome/common/print_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "printing/page_size_margins.h" +#include "printing/print_job_constants.h" +#include "printing/pdf_metafile_skia.h" + +#include "atom/common/node_includes.h" + +using content::BrowserThread; +using content::WebContents; + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(printing::PrintPreviewMessageHandler); + +namespace { + +void StopWorker(int document_cookie) { + if (document_cookie <= 0) + return; + scoped_refptr queue = + g_browser_process->print_job_manager()->queue(); + scoped_refptr printer_query = + queue->PopPrinterQuery(document_cookie); + if (printer_query.get()) { + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&printing::PrinterQuery::StopWorker, + printer_query)); + } +} + +char* CopyPDFDataOnIOThread( + const PrintHostMsg_DidPreviewDocument_Params& params) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + scoped_ptr shared_buf( + new base::SharedMemory(params.metafile_data_handle, true)); + if (!shared_buf->Map(params.data_size)) + return nullptr; + char* memory_pdf_data = static_cast(shared_buf->memory()); + char* pdf_data = new char[params.data_size]; + memcpy(pdf_data, memory_pdf_data, params.data_size); + return pdf_data; +} + +} // namespace + +namespace printing { + + +PrintPreviewMessageHandler::PrintPreviewMessageHandler( + WebContents* web_contents) + : content::WebContentsObserver(web_contents) { + DCHECK(web_contents); +} + +PrintPreviewMessageHandler::~PrintPreviewMessageHandler() { +} + +void PrintPreviewMessageHandler::OnMetafileReadyForPrinting( + const PrintHostMsg_DidPreviewDocument_Params& params) { + // Always try to stop the worker. + StopWorker(params.document_cookie); + + if (params.expected_pages_count <= 0) { + NOTREACHED(); + return; + } + + BrowserThread::PostTaskAndReplyWithResult( + BrowserThread::IO, + FROM_HERE, + base::Bind(&CopyPDFDataOnIOThread, params), + base::Bind(&PrintPreviewMessageHandler::RunPrintToPDFCallback, + base::Unretained(this), + params.preview_request_id, + params.data_size)); +} + +void PrintPreviewMessageHandler::OnPrintPreviewFailed(int document_cookie, + int request_id) { + StopWorker(document_cookie); + RunPrintToPDFCallback(request_id, 0, nullptr); +} + +bool PrintPreviewMessageHandler::OnMessageReceived( + const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PrintPreviewMessageHandler, message) + IPC_MESSAGE_HANDLER(PrintHostMsg_MetafileReadyForPrinting, + OnMetafileReadyForPrinting) + IPC_MESSAGE_HANDLER(PrintHostMsg_PrintPreviewFailed, + OnPrintPreviewFailed) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void PrintPreviewMessageHandler::PrintToPDF( + const base::DictionaryValue& options, + const atom::api::WebContents::PrintToPDFCallback& callback) { + int request_id; + options.GetInteger(printing::kPreviewRequestID, &request_id); + print_to_pdf_callback_map_[request_id] = callback; + + content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); + rvh->Send(new PrintMsg_PrintPreview(rvh->GetRoutingID(), options)); +} + +void PrintPreviewMessageHandler::RunPrintToPDFCallback( + int request_id, uint32 data_size, char* data) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + if (data) { + v8::Local buffer = node::Buffer::New(isolate, + data, static_cast(data_size)).ToLocalChecked(); + print_to_pdf_callback_map_[request_id].Run(v8::Null(isolate), buffer); + } else { + v8::Local error_message = v8::String::NewFromUtf8(isolate, + "Fail to generate PDF"); + print_to_pdf_callback_map_[request_id].Run( + v8::Exception::Error(error_message), v8::Null(isolate)); + } + print_to_pdf_callback_map_.erase(request_id); +} + +} // namespace printing diff --git a/chromium_src/chrome/browser/printing/print_preview_message_handler.h b/chromium_src/chrome/browser/printing/print_preview_message_handler.h new file mode 100644 index 000000000000..453d78761bbe --- /dev/null +++ b/chromium_src/chrome/browser/printing/print_preview_message_handler.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_PRINTING_PRINT_PREVIEW_MESSAGE_HANDLER_H_ +#define CHROME_BROWSER_PRINTING_PRINT_PREVIEW_MESSAGE_HANDLER_H_ + +#include + +#include "atom/browser/api/atom_api_web_contents.h" +#include "base/compiler_specific.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" + +struct PrintHostMsg_DidPreviewDocument_Params; + +namespace content { +class WebContents; +} + +namespace printing { + +struct PageSizeMargins; + +// Manages the print preview handling for a WebContents. +class PrintPreviewMessageHandler + : public content::WebContentsObserver, + public content::WebContentsUserData { + public: + ~PrintPreviewMessageHandler() override; + + // content::WebContentsObserver implementation. + bool OnMessageReceived(const IPC::Message& message) override; + + void PrintToPDF(const base::DictionaryValue& options, + const atom::api::WebContents::PrintToPDFCallback& callback); + + private: + typedef std::map + PrintToPDFCallbackMap; + + explicit PrintPreviewMessageHandler(content::WebContents* web_contents); + friend class content::WebContentsUserData; + + // Message handlers. + void OnMetafileReadyForPrinting( + const PrintHostMsg_DidPreviewDocument_Params& params); + void OnPrintPreviewFailed(int document_cookie, int request_id); + + void RunPrintToPDFCallback(int request_id, uint32 data_size, char* data); + + PrintToPDFCallbackMap print_to_pdf_callback_map_; + + DISALLOW_COPY_AND_ASSIGN(PrintPreviewMessageHandler); +}; + +} // namespace printing + +#endif // CHROME_BROWSER_PRINTING_PRINT_PREVIEW_MESSAGE_HANDLER_H_ diff --git a/chromium_src/chrome/browser/printing/printing_message_filter.cc b/chromium_src/chrome/browser/printing/printing_message_filter.cc index 15ca50fba53f..6fd536ef68c3 100644 --- a/chromium_src/chrome/browser/printing/printing_message_filter.cc +++ b/chromium_src/chrome/browser/printing/printing_message_filter.cc @@ -128,6 +128,8 @@ bool PrintingMessageFilter::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_GetDefaultPrintSettings, OnGetDefaultPrintSettings) IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_ScriptedPrint, OnScriptedPrint) + IPC_MESSAGE_HANDLER_DELAY_REPLY(PrintHostMsg_UpdatePrintSettings, + OnUpdatePrintSettings) #if defined(ENABLE_FULL_PRINTING) IPC_MESSAGE_HANDLER(PrintHostMsg_CheckForCancel, OnCheckForCancel) #endif @@ -372,4 +374,57 @@ void PrintingMessageFilter::UpdateFileDescriptor(int render_view_id, int fd) { } #endif +void PrintingMessageFilter::OnUpdatePrintSettings( + int document_cookie, const base::DictionaryValue& job_settings, + IPC::Message* reply_msg) { + scoped_ptr new_settings(job_settings.DeepCopy()); + + scoped_refptr printer_query; + printer_query = queue_->PopPrinterQuery(document_cookie); + if (!printer_query.get()) { + int host_id = render_process_id_; + int routing_id = reply_msg->routing_id(); + if (!new_settings->GetInteger(printing::kPreviewInitiatorHostId, + &host_id) || + !new_settings->GetInteger(printing::kPreviewInitiatorRoutingId, + &routing_id)) { + host_id = content::ChildProcessHost::kInvalidUniqueID; + routing_id = content::ChildProcessHost::kInvalidUniqueID; + } + printer_query = queue_->CreatePrinterQuery(host_id, routing_id); + } + printer_query->SetSettings( + new_settings.Pass(), + base::Bind(&PrintingMessageFilter::OnUpdatePrintSettingsReply, this, + printer_query, reply_msg)); +} + +void PrintingMessageFilter::OnUpdatePrintSettingsReply( + scoped_refptr printer_query, + IPC::Message* reply_msg) { + PrintMsg_PrintPages_Params params; + if (!printer_query.get() || + printer_query->last_status() != PrintingContext::OK) { + params.Reset(); + } else { + RenderParamsFromPrintSettings(printer_query->settings(), ¶ms.params); + params.params.document_cookie = printer_query->cookie(); + params.pages = PageRange::GetPages(printer_query->settings().ranges()); + } + PrintHostMsg_UpdatePrintSettings::WriteReplyParams( + reply_msg, + params, + printer_query.get() && + (printer_query->last_status() == printing::PrintingContext::CANCEL)); + Send(reply_msg); + // If user hasn't cancelled. + if (printer_query.get()) { + if (printer_query->cookie() && printer_query->settings().dpi()) { + queue_->QueuePrinterQuery(printer_query.get()); + } else { + printer_query->StopWorker(); + } + } +} + } // namespace printing diff --git a/chromium_src/chrome/browser/printing/printing_message_filter.h b/chromium_src/chrome/browser/printing/printing_message_filter.h index 410b6245786d..624b28fd35dd 100644 --- a/chromium_src/chrome/browser/printing/printing_message_filter.h +++ b/chromium_src/chrome/browser/printing/printing_message_filter.h @@ -96,6 +96,15 @@ class PrintingMessageFilter : public content::BrowserMessageFilter { void OnScriptedPrintReply(scoped_refptr printer_query, IPC::Message* reply_msg); + // Modify the current print settings based on |job_settings|. The task is + // handled by the print worker thread and the UI thread. The reply occurs on + // the IO thread. + void OnUpdatePrintSettings(int document_cookie, + const base::DictionaryValue& job_settings, + IPC::Message* reply_msg); + void OnUpdatePrintSettingsReply(scoped_refptr printer_query, + IPC::Message* reply_msg); + #if defined(ENABLE_FULL_PRINTING) // Check to see if print preview has been cancelled. void OnCheckForCancel(int32 preview_ui_id, 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 4499b21aefe3..fdc054f59fbd 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 @@ -46,7 +46,8 @@ ui::ClipboardType ConvertClipboardType(uint32_t type) { // assume all data that is placed on the clipboard is UTF16 and pepper allows // arbitrary data so this change would require some reworking of the chrome // clipboard interface for custom data. -bool JumpToFormatInPickle(const base::string16& format, PickleIterator* iter) { +bool JumpToFormatInPickle(const base::string16& format, + base::PickleIterator* iter) { size_t size = 0; if (!iter->ReadSizeT(&size)) return false; @@ -66,22 +67,22 @@ bool JumpToFormatInPickle(const base::string16& format, PickleIterator* iter) { } bool IsFormatAvailableInPickle(const base::string16& format, - const Pickle& pickle) { - PickleIterator iter(pickle); + const base::Pickle& pickle) { + base::PickleIterator iter(pickle); return JumpToFormatInPickle(format, &iter); } std::string ReadDataFromPickle(const base::string16& format, - const Pickle& pickle) { + const base::Pickle& pickle) { std::string result; - PickleIterator iter(pickle); + base::PickleIterator iter(pickle); if (!JumpToFormatInPickle(format, &iter) || !iter.ReadString(&result)) return std::string(); return result; } bool WriteDataToPickle(const std::map& data, - Pickle* pickle) { + base::Pickle* pickle) { pickle->WriteSizeT(data.size()); for (std::map::const_iterator it = data.begin(); it != data.end(); @@ -187,7 +188,7 @@ int32_t PepperFlashClipboardMessageFilter::OnMsgIsFormatAvailable( std::string clipboard_data; clipboard->ReadData(ui::Clipboard::GetPepperCustomDataFormatType(), &clipboard_data); - Pickle pickle(clipboard_data.data(), clipboard_data.size()); + base::Pickle pickle(clipboard_data.data(), clipboard_data.size()); available = IsFormatAvailableInPickle(base::UTF8ToUTF16(format_name), pickle); } @@ -265,7 +266,7 @@ int32_t PepperFlashClipboardMessageFilter::OnMsgReadData( std::string clipboard_data; clipboard->ReadData(ui::Clipboard::GetPepperCustomDataFormatType(), &clipboard_data); - Pickle pickle(clipboard_data.data(), clipboard_data.size()); + base::Pickle pickle(clipboard_data.data(), clipboard_data.size()); if (IsFormatAvailableInPickle(format_name, pickle)) { result = PP_OK; clipboard_string = ReadDataFromPickle(format_name, pickle); @@ -340,7 +341,7 @@ int32_t PepperFlashClipboardMessageFilter::OnMsgWriteData( } if (custom_data_map.size() > 0) { - Pickle pickle; + base::Pickle pickle; if (WriteDataToPickle(custom_data_map, &pickle)) { scw.WritePickledData(pickle, ui::Clipboard::GetPepperCustomDataFormatType()); diff --git a/chromium_src/chrome/browser/speech/tts_controller_impl.cc b/chromium_src/chrome/browser/speech/tts_controller_impl.cc index 272cafddb947..6b66b6a61960 100644 --- a/chromium_src/chrome/browser/speech/tts_controller_impl.cc +++ b/chromium_src/chrome/browser/speech/tts_controller_impl.cc @@ -7,7 +7,6 @@ #include #include -#include "base/float_util.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/speech/tts_platform.h" @@ -461,4 +460,4 @@ void TtsControllerImpl::SetTtsEngineDelegate( TtsEngineDelegate* TtsControllerImpl::GetTtsEngineDelegate() { return tts_engine_delegate_; -} \ No newline at end of file +} diff --git a/chromium_src/chrome/common/chrome_utility_messages.h b/chromium_src/chrome/common/chrome_utility_messages.h index 1de0756c0d58..f146e1823e81 100644 --- a/chromium_src/chrome/common/chrome_utility_messages.h +++ b/chromium_src/chrome/common/chrome_utility_messages.h @@ -27,7 +27,7 @@ #if defined(OS_WIN) // A vector of filters, each being a Tuple containing a display string (i.e. // "Text Files") and a filter pattern (i.e. "*.txt"). -typedef std::vector> +typedef std::vector> GetOpenFileNameFilter; #endif // OS_WIN diff --git a/chromium_src/chrome/common/pref_names.cc b/chromium_src/chrome/common/pref_names.cc new file mode 100644 index 000000000000..3e3a73b99837 --- /dev/null +++ b/chromium_src/chrome/common/pref_names.cc @@ -0,0 +1,12 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/pref_names.h" + +namespace prefs { + +const char kSelectFileLastDirectory[] = "selectfile.last_directory"; +const char kDownloadDefaultDirectory[] = "download.default_directory"; + +} // namespace prefs diff --git a/chromium_src/chrome/common/pref_names.h b/chromium_src/chrome/common/pref_names.h index e69de29bb2d1..542a2d2c733f 100644 --- a/chromium_src/chrome/common/pref_names.h +++ b/chromium_src/chrome/common/pref_names.h @@ -0,0 +1,12 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Constants for the names of various preferences, for easier changing. + +namespace prefs { + +extern const char kSelectFileLastDirectory[]; +extern const char kDownloadDefaultDirectory[]; + +} // namespace prefs diff --git a/chromium_src/chrome/common/print_messages.h b/chromium_src/chrome/common/print_messages.h index 4a54546b69d7..cd775d0478b7 100644 --- a/chromium_src/chrome/common/print_messages.h +++ b/chromium_src/chrome/common/print_messages.h @@ -46,7 +46,9 @@ struct PrintMsg_Print_Params { int document_cookie; bool selection_only; bool supports_alpha_blend; + int preview_request_id; blink::WebPrintScalingOption print_scaling_option; + bool print_to_pdf; base::string16 title; base::string16 url; bool should_print_backgrounds; @@ -185,6 +187,27 @@ IPC_STRUCT_BEGIN(PrintHostMsg_ScriptedPrint_Params) IPC_STRUCT_MEMBER(printing::MarginType, margin_type) IPC_STRUCT_END() +// Parameters to describe a rendered document. +IPC_STRUCT_BEGIN(PrintHostMsg_DidPreviewDocument_Params) + // A shared memory handle to metafile data. + IPC_STRUCT_MEMBER(base::SharedMemoryHandle, metafile_data_handle) + + // Size of metafile data. + IPC_STRUCT_MEMBER(uint32, data_size) + + // Cookie for the document to ensure correctness. + IPC_STRUCT_MEMBER(int, document_cookie) + + // Store the expected pages count. + IPC_STRUCT_MEMBER(int, expected_pages_count) + + // Whether the preview can be modified. + IPC_STRUCT_MEMBER(bool, modifiable) + + // The id of the preview request. + IPC_STRUCT_MEMBER(int, preview_request_id) +IPC_STRUCT_END() + // Messages sent from the browser to the renderer. @@ -198,6 +221,12 @@ IPC_MESSAGE_ROUTED2(PrintMsg_PrintPages, IPC_MESSAGE_ROUTED1(PrintMsg_PrintingDone, bool /* success */) +// Tells the render view to switch the CSS to print media type, renders every +// requested pages for print preview using the given |settings|. This gets +// called multiple times as the user updates settings. +IPC_MESSAGE_ROUTED1(PrintMsg_PrintPreview, + base::DictionaryValue /* settings */) + // Messages sent from the renderer to the browser. #if defined(OS_WIN) @@ -231,6 +260,14 @@ IPC_MESSAGE_ROUTED1(PrintHostMsg_DidPrintPage, IPC_SYNC_MESSAGE_ROUTED0_1(PrintHostMsg_GetDefaultPrintSettings, PrintMsg_Print_Params /* default_settings */) +// The renderer wants to update the current print settings with new +// |job_settings|. +IPC_SYNC_MESSAGE_ROUTED2_2(PrintHostMsg_UpdatePrintSettings, + int /* document_cookie */, + base::DictionaryValue /* job_settings */, + PrintMsg_PrintPages_Params /* current_settings */, + bool /* canceled */) + // It's the renderer that controls the printing process when it is generated // by javascript. This step is about showing UI to the user to select the // final print settings. The output parameter is the same as @@ -247,6 +284,15 @@ IPC_MESSAGE_ROUTED0(PrintHostMsg_ShowInvalidPrinterSettingsError) IPC_MESSAGE_ROUTED1(PrintHostMsg_PrintingFailed, int /* document cookie */) +// Sends back to the browser the complete rendered document (non-draft mode, +// used for printing) that was requested by a PrintMsg_PrintPreview message. +// The memory handle in this message is already valid in the browser process. +IPC_MESSAGE_ROUTED1(PrintHostMsg_MetafileReadyForPrinting, + PrintHostMsg_DidPreviewDocument_Params /* params */) + +IPC_MESSAGE_ROUTED2(PrintHostMsg_PrintPreviewFailed, + int /* document cookie */, + int /* request_id */); #if defined(OS_WIN) // Tell the utility process to start rendering the given PDF into a metafile. 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 e01aea741fc8..3ef6dff0c8bc 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 @@ -56,17 +56,9 @@ void PepperSharedMemoryMessageFilter::OnHostMsgCreateSharedMemory( ->GetVarTracker() ->TrackSharedMemoryHandle(instance, host_shm_handle, size); - base::PlatformFile host_handle = -#if defined(OS_WIN) - host_shm_handle; -#elif defined(OS_POSIX) - host_shm_handle.fd; -#else -#error Not implemented. -#endif // We set auto_close to false since we need our file descriptor to // actually be duplicated on linux. The shared memory destructor will // close the original handle for us. - plugin_handle->set_shmem(host_->ShareHandleWithRemote(host_handle, false), - size); + plugin_handle->set_shmem( + host_->ShareSharedMemoryHandleWithRemote(host_shm_handle), size); } 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 60158f6c1d4c..20ac1fdc9b4f 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper.cc +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper.cc @@ -427,9 +427,10 @@ class PrepareFrameAndViewForPrint : public blink::WebViewClient, // blink::WebFrameClient override: virtual blink::WebFrame* createChildFrame( blink::WebLocalFrame* parent, + blink::WebTreeScopeType scope, const blink::WebString& name, blink::WebSandboxFlags sandboxFlags); - virtual void frameDetached(blink::WebFrame* frame); + virtual void frameDetached(blink::WebFrame* frame, DetachType type); private: void CallOnReady(); @@ -548,7 +549,8 @@ void PrepareFrameAndViewForPrint::CopySelection( blink::WebView* web_view = blink::WebView::create(this); owns_web_view_ = true; content::RenderView::ApplyWebPreferences(prefs, web_view); - web_view->setMainFrame(blink::WebLocalFrame::create(this)); + web_view->setMainFrame( + blink::WebLocalFrame::create(blink::WebTreeScopeType::Document, this)); frame_.Reset(web_view->mainFrame()->toWebLocalFrame()); node_to_print_.reset(); @@ -573,14 +575,17 @@ void PrepareFrameAndViewForPrint::didStopLoading() { blink::WebFrame* PrepareFrameAndViewForPrint::createChildFrame( blink::WebLocalFrame* parent, + blink::WebTreeScopeType scope, const blink::WebString& name, blink::WebSandboxFlags sandboxFlags) { - blink::WebFrame* frame = blink::WebLocalFrame::create(this); + blink::WebFrame* frame = blink::WebLocalFrame::create(scope, this); parent->appendChild(frame); return frame; } -void PrepareFrameAndViewForPrint::frameDetached(blink::WebFrame* frame) { +void PrepareFrameAndViewForPrint::frameDetached(blink::WebFrame* frame, + DetachType type) { + DCHECK(type == DetachType::Remove); if (frame->parent()) frame->parent()->removeChild(frame); frame->close(); @@ -650,6 +655,7 @@ bool PrintWebViewHelper::OnMessageReceived(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(PrintWebViewHelper, message) IPC_MESSAGE_HANDLER(PrintMsg_PrintPages, OnPrintPages) IPC_MESSAGE_HANDLER(PrintMsg_PrintingDone, OnPrintingDone) + IPC_MESSAGE_HANDLER(PrintMsg_PrintPreview, OnPrintPreview) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; @@ -712,6 +718,128 @@ void PrintWebViewHelper::OnPrintingDone(bool success) { DidFinishPrinting(success ? OK : FAIL_PRINT); } +void PrintWebViewHelper::OnPrintPreview(const base::DictionaryValue& settings) { + blink::WebLocalFrame* frame; + if (GetPrintFrame(&frame)) { + print_preview_context_.InitWithFrame(frame); + if (!print_preview_context_.source_frame()) { + DidFinishPrinting(FAIL_PREVIEW); + return; + } + + if (!UpdatePrintSettings(print_preview_context_.source_frame(), + print_preview_context_.source_node(), settings)) { + DidFinishPrinting(FAIL_PREVIEW); + return; + } + is_print_ready_metafile_sent_ = false; + PrepareFrameForPreviewDocument(); + } +} + +void PrintWebViewHelper::PrepareFrameForPreviewDocument() { + reset_prep_frame_view_ = false; + + if (!print_pages_params_) { + DidFinishPrinting(FAIL_PREVIEW); + return; + } + + // Don't reset loading frame or WebKit will fail assert. Just retry when + // current selection is loaded. + if (prep_frame_view_ && prep_frame_view_->IsLoadingSelection()) { + reset_prep_frame_view_ = true; + return; + } + + const PrintMsg_Print_Params& print_params = print_pages_params_->params; + prep_frame_view_.reset(new PrepareFrameAndViewForPrint( + print_params, print_preview_context_.source_frame(), + print_preview_context_.source_node(), ignore_css_margins_)); + prep_frame_view_->CopySelectionIfNeeded( + render_view()->GetWebkitPreferences(), + base::Bind(&PrintWebViewHelper::OnFramePreparedForPreviewDocument, + base::Unretained(this))); +} + +void PrintWebViewHelper::OnFramePreparedForPreviewDocument() { + if (reset_prep_frame_view_) { + PrepareFrameForPreviewDocument(); + return; + } + DidFinishPrinting(CreatePreviewDocument() ? OK : FAIL_PREVIEW); +} + +bool PrintWebViewHelper::CreatePreviewDocument() { + if (!print_pages_params_) + return false; + + const PrintMsg_Print_Params& print_params = print_pages_params_->params; + const std::vector& pages = print_pages_params_->pages; + + if (!print_preview_context_.CreatePreviewDocument(prep_frame_view_.release(), + pages)) { + return false; + } + + while (!print_preview_context_.IsFinalPageRendered()) { + int page_number = print_preview_context_.GetNextPageNumber(); + DCHECK_GE(page_number, 0); + if (!RenderPreviewPage(page_number, print_params)) + return false; + + // We must call PrepareFrameAndViewForPrint::FinishPrinting() (by way of + // print_preview_context_.AllPagesRendered()) before calling + // FinalizePrintReadyDocument() when printing a PDF because the plugin + // code does not generate output until we call FinishPrinting(). We do not + // generate draft pages for PDFs, so IsFinalPageRendered() and + // IsLastPageOfPrintReadyMetafile() will be true in the same iteration of + // the loop. + if (print_preview_context_.IsFinalPageRendered()) + print_preview_context_.AllPagesRendered(); + + if (print_preview_context_.IsLastPageOfPrintReadyMetafile()) { + DCHECK(print_preview_context_.IsModifiable() || + print_preview_context_.IsFinalPageRendered()); + if (!FinalizePrintReadyDocument()) + return false; + } + } + print_preview_context_.Finished(); + return true; +} + +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; + preview_params.document_cookie = print_pages_params_->params.document_cookie; + preview_params.expected_pages_count = + print_preview_context_.total_page_count(); + preview_params.modifiable = print_preview_context_.IsModifiable(); + 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)); + return true; +} + void PrintWebViewHelper::PrintNode(const blink::WebNode& node) { if (node.isNull() || !node.document().frame()) { // This can occur when the context menu refers to an invalid WebNode. @@ -786,6 +914,15 @@ void PrintWebViewHelper::DidFinishPrinting(PrintingResult result) { Send(new PrintHostMsg_PrintingFailed(routing_id(), cookie)); } break; + + case FAIL_PREVIEW: + LOG(ERROR) << "PREVIEW FAILED."; + if (print_pages_params_) { + Send(new PrintHostMsg_PrintPreviewFailed(routing_id(), + print_pages_params_->params.document_cookie, + print_pages_params_->params.preview_request_id)); + } + break; } prep_frame_view_.reset(); print_pages_params_.reset(); @@ -916,6 +1053,68 @@ bool PrintWebViewHelper::CalculateNumberOfPages(blink::WebLocalFrame* frame, return true; } +bool PrintWebViewHelper::UpdatePrintSettings( + blink::WebLocalFrame* frame, + const blink::WebNode& node, + const base::DictionaryValue& passed_job_settings) { + const base::DictionaryValue* job_settings = &passed_job_settings; + base::DictionaryValue modified_job_settings; + if (job_settings->empty()) { + if (!print_for_preview_) + print_preview_context_.set_error(PREVIEW_ERROR_BAD_SETTING); + return false; + } + + bool source_is_html = true; + if (print_for_preview_) { + if (!job_settings->GetBoolean(kSettingPreviewModifiable, &source_is_html)) { + NOTREACHED(); + } + } else { + source_is_html = !PrintingNodeOrPdfFrame(frame, node); + } + + if (print_for_preview_ || !source_is_html) { + modified_job_settings.MergeDictionary(job_settings); + modified_job_settings.SetBoolean(kSettingHeaderFooterEnabled, false); + modified_job_settings.SetInteger(kSettingMarginsType, NO_MARGINS); + job_settings = &modified_job_settings; + } + + // Send the cookie so that UpdatePrintSettings can reuse PrinterQuery when + // possible. + int cookie = + print_pages_params_ ? print_pages_params_->params.document_cookie : 0; + PrintMsg_PrintPages_Params settings; + bool canceled = false; + Send(new PrintHostMsg_UpdatePrintSettings(routing_id(), cookie, *job_settings, + &settings, &canceled)); + if (canceled) { + notify_browser_of_print_failure_ = false; + return false; + } + + if (!print_for_preview_) { + job_settings->GetInteger(kPreviewRequestID, + &settings.params.preview_request_id); + settings.params.print_to_pdf = true; + UpdateFrameMarginsCssInfo(*job_settings); + settings.params.print_scaling_option = + blink::WebPrintScalingOptionSourceSize; + } + + SetPrintPagesParams(settings); + + if (!PrintMsg_Print_Params_IsValid(settings.params)) { + if (!print_for_preview_) + print_preview_context_.set_error(PREVIEW_ERROR_INVALID_PRINTER_SETTINGS); + return false; + } + + return true; +} + + bool PrintWebViewHelper::GetPrintSettingsFromUser(blink::WebFrame* frame, const blink::WebNode& node, int expected_pages_count) { @@ -987,4 +1186,264 @@ void PrintWebViewHelper::SetPrintPagesParams( print_pages_params_.reset(new PrintMsg_PrintPages_Params(settings)); } +bool PrintWebViewHelper::PreviewPageRendered(int page_number, + PdfMetafileSkia* metafile) { + DCHECK_GE(page_number, FIRST_PAGE_INDEX); + + // For non-modifiable files, |metafile| should be NULL, so do not bother + // sending a message. If we don't generate draft metafiles, |metafile| is + // NULL. + if (!print_preview_context_.IsModifiable() || + !print_preview_context_.generate_draft_pages()) { + DCHECK(!metafile); + return true; + } + + if (!metafile) { + NOTREACHED(); + print_preview_context_.set_error( + PREVIEW_ERROR_PAGE_RENDERED_WITHOUT_METAFILE); + return false; + } + + return true; +} + +PrintWebViewHelper::PrintPreviewContext::PrintPreviewContext() + : total_page_count_(0), + current_page_index_(0), + generate_draft_pages_(true), + print_ready_metafile_page_count_(0), + error_(PREVIEW_ERROR_NONE), + state_(UNINITIALIZED) { +} + +PrintWebViewHelper::PrintPreviewContext::~PrintPreviewContext() { +} + +void PrintWebViewHelper::PrintPreviewContext::InitWithFrame( + blink::WebLocalFrame* web_frame) { + DCHECK(web_frame); + DCHECK(!IsRendering()); + state_ = INITIALIZED; + source_frame_.Reset(web_frame); + source_node_.reset(); +} + +void PrintWebViewHelper::PrintPreviewContext::InitWithNode( + const blink::WebNode& web_node) { + DCHECK(!web_node.isNull()); + DCHECK(web_node.document().frame()); + DCHECK(!IsRendering()); + state_ = INITIALIZED; + source_frame_.Reset(web_node.document().frame()); + source_node_ = web_node; +} + +void PrintWebViewHelper::PrintPreviewContext::OnPrintPreview() { + DCHECK_EQ(INITIALIZED, state_); + ClearContext(); +} + +bool PrintWebViewHelper::PrintPreviewContext::CreatePreviewDocument( + PrepareFrameAndViewForPrint* prepared_frame, + const std::vector& pages) { + DCHECK_EQ(INITIALIZED, state_); + state_ = RENDERING; + + // Need to make sure old object gets destroyed first. + prep_frame_view_.reset(prepared_frame); + prep_frame_view_->StartPrinting(); + + total_page_count_ = prep_frame_view_->GetExpectedPageCount(); + if (total_page_count_ == 0) { + LOG(ERROR) << "CreatePreviewDocument got 0 page count"; + set_error(PREVIEW_ERROR_ZERO_PAGES); + return false; + } + + metafile_.reset(new PdfMetafileSkia); + if (!metafile_->Init()) { + set_error(PREVIEW_ERROR_METAFILE_INIT_FAILED); + LOG(ERROR) << "PdfMetafileSkia Init failed"; + return false; + } + + current_page_index_ = 0; + pages_to_render_ = pages; + // Sort and make unique. + std::sort(pages_to_render_.begin(), pages_to_render_.end()); + pages_to_render_.resize( + std::unique(pages_to_render_.begin(), pages_to_render_.end()) - + pages_to_render_.begin()); + // Remove invalid pages. + pages_to_render_.resize(std::lower_bound(pages_to_render_.begin(), + pages_to_render_.end(), + total_page_count_) - + pages_to_render_.begin()); + print_ready_metafile_page_count_ = pages_to_render_.size(); + if (pages_to_render_.empty()) { + print_ready_metafile_page_count_ = total_page_count_; + // Render all pages. + for (int i = 0; i < total_page_count_; ++i) + pages_to_render_.push_back(i); + } else if (generate_draft_pages_) { + int pages_index = 0; + for (int i = 0; i < total_page_count_; ++i) { + if (pages_index < print_ready_metafile_page_count_ && + i == pages_to_render_[pages_index]) { + pages_index++; + continue; + } + pages_to_render_.push_back(i); + } + } + + document_render_time_ = base::TimeDelta(); + begin_time_ = base::TimeTicks::Now(); + + return true; +} + +void PrintWebViewHelper::PrintPreviewContext::RenderedPreviewPage( + const base::TimeDelta& page_time) { + DCHECK_EQ(RENDERING, state_); + document_render_time_ += page_time; + UMA_HISTOGRAM_TIMES("PrintPreview.RenderPDFPageTime", page_time); +} + +void PrintWebViewHelper::PrintPreviewContext::AllPagesRendered() { + DCHECK_EQ(RENDERING, state_); + state_ = DONE; + prep_frame_view_->FinishPrinting(); +} + +void PrintWebViewHelper::PrintPreviewContext::FinalizePrintReadyDocument() { + DCHECK(IsRendering()); + + base::TimeTicks begin_time = base::TimeTicks::Now(); + metafile_->FinishDocument(); + + if (print_ready_metafile_page_count_ <= 0) { + NOTREACHED(); + return; + } + + UMA_HISTOGRAM_MEDIUM_TIMES("PrintPreview.RenderToPDFTime", + document_render_time_); + base::TimeDelta total_time = + (base::TimeTicks::Now() - begin_time) + document_render_time_; + UMA_HISTOGRAM_MEDIUM_TIMES("PrintPreview.RenderAndGeneratePDFTime", + total_time); + UMA_HISTOGRAM_MEDIUM_TIMES("PrintPreview.RenderAndGeneratePDFTimeAvgPerPage", + total_time / pages_to_render_.size()); +} + +void PrintWebViewHelper::PrintPreviewContext::Finished() { + DCHECK_EQ(DONE, state_); + state_ = INITIALIZED; + ClearContext(); +} + +void PrintWebViewHelper::PrintPreviewContext::Failed(bool report_error) { + DCHECK(state_ == INITIALIZED || state_ == RENDERING); + state_ = INITIALIZED; + if (report_error) { + DCHECK_NE(PREVIEW_ERROR_NONE, error_); + UMA_HISTOGRAM_ENUMERATION("PrintPreview.RendererError", error_, + PREVIEW_ERROR_LAST_ENUM); + } + ClearContext(); +} + +int PrintWebViewHelper::PrintPreviewContext::GetNextPageNumber() { + DCHECK_EQ(RENDERING, state_); + if (IsFinalPageRendered()) + return -1; + return pages_to_render_[current_page_index_++]; +} + +bool PrintWebViewHelper::PrintPreviewContext::IsRendering() const { + return state_ == RENDERING || state_ == DONE; +} + +bool PrintWebViewHelper::PrintPreviewContext::IsModifiable() { + // The only kind of node we can print right now is a PDF node. + return !PrintingNodeOrPdfFrame(source_frame(), source_node_); +} + +bool PrintWebViewHelper::PrintPreviewContext::HasSelection() { + return IsModifiable() && source_frame()->hasSelection(); +} + +bool PrintWebViewHelper::PrintPreviewContext::IsLastPageOfPrintReadyMetafile() + const { + DCHECK(IsRendering()); + return current_page_index_ == print_ready_metafile_page_count_; +} + +bool PrintWebViewHelper::PrintPreviewContext::IsFinalPageRendered() const { + DCHECK(IsRendering()); + return static_cast(current_page_index_) == pages_to_render_.size(); +} + +void PrintWebViewHelper::PrintPreviewContext::set_generate_draft_pages( + bool generate_draft_pages) { + DCHECK_EQ(INITIALIZED, state_); + generate_draft_pages_ = generate_draft_pages; +} + +void PrintWebViewHelper::PrintPreviewContext::set_error( + enum PrintPreviewErrorBuckets error) { + error_ = error; +} + +blink::WebLocalFrame* PrintWebViewHelper::PrintPreviewContext::source_frame() { + DCHECK(state_ != UNINITIALIZED); + return source_frame_.GetFrame(); +} + +const blink::WebNode& + PrintWebViewHelper::PrintPreviewContext::source_node() const { + DCHECK(state_ != UNINITIALIZED); + return source_node_; +} + +blink::WebLocalFrame* +PrintWebViewHelper::PrintPreviewContext::prepared_frame() { + DCHECK(state_ != UNINITIALIZED); + return prep_frame_view_->frame(); +} + +const blink::WebNode& + PrintWebViewHelper::PrintPreviewContext::prepared_node() const { + DCHECK(state_ != UNINITIALIZED); + return prep_frame_view_->node(); +} + +int PrintWebViewHelper::PrintPreviewContext::total_page_count() const { + DCHECK(state_ != UNINITIALIZED); + return total_page_count_; +} + +bool PrintWebViewHelper::PrintPreviewContext::generate_draft_pages() const { + return generate_draft_pages_; +} + +PdfMetafileSkia* PrintWebViewHelper::PrintPreviewContext::metafile() { + DCHECK(IsRendering()); + return metafile_.get(); +} + +int PrintWebViewHelper::PrintPreviewContext::last_error() const { + return error_; +} + +void PrintWebViewHelper::PrintPreviewContext::ClearContext() { + prep_frame_view_.reset(); + metafile_.reset(); + pages_to_render_.clear(); + error_ = PREVIEW_ERROR_NONE; +} + } // namespace printing 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 498a01b53da4..bfe9cb612d16 100644 --- a/chromium_src/chrome/renderer/printing/print_web_view_helper.h +++ b/chromium_src/chrome/renderer/printing/print_web_view_helper.h @@ -76,6 +76,19 @@ class PrintWebViewHelper OK, FAIL_PRINT_INIT, FAIL_PRINT, + FAIL_PREVIEW, + }; + + enum PrintPreviewErrorBuckets { + PREVIEW_ERROR_NONE, // Always first. + PREVIEW_ERROR_BAD_SETTING, + PREVIEW_ERROR_METAFILE_COPY_FAILED, + PREVIEW_ERROR_METAFILE_INIT_FAILED, + PREVIEW_ERROR_ZERO_PAGES, + PREVIEW_ERROR_MAC_DRAFT_METAFILE_INIT_FAILED, + PREVIEW_ERROR_PAGE_RENDERED_WITHOUT_METAFILE, + PREVIEW_ERROR_INVALID_PRINTER_SETTINGS, + PREVIEW_ERROR_LAST_ENUM // Always last. }; // RenderViewObserver implementation. @@ -88,6 +101,8 @@ class PrintWebViewHelper void OnPrintPages(bool silent, bool print_background); void OnPrintingDone(bool success); #endif // !DISABLE_BASIC_PRINTING + void OnPrintPreview(const base::DictionaryValue& settings); + // Get |page_size| and |content_area| information from // |page_layout_in_points|. @@ -99,6 +114,24 @@ class PrintWebViewHelper // Update |ignore_css_margins_| based on settings. void UpdateFrameMarginsCssInfo(const base::DictionaryValue& settings); + // Prepare frame for creating preview document. + void PrepareFrameForPreviewDocument(); + + // Continue creating preview document. + void OnFramePreparedForPreviewDocument(); + + // Finalize the print ready preview document. + bool FinalizePrintReadyDocument(); + + // Renders a print preview page. |page_number| is 0-based. + // Returns true if print preview should continue, false on failure. + bool RenderPreviewPage(int page_number, + const PrintMsg_Print_Params& print_params); + + + // Initialize the print preview document. + bool CreatePreviewDocument(); + // Main printing code ------------------------------------------------------- void Print(blink::WebLocalFrame* frame, @@ -120,6 +153,14 @@ class PrintWebViewHelper const blink::WebNode& node, int* number_of_pages); + // Update the current print settings with new |passed_job_settings|. + // |passed_job_settings| dictionary contains print job details such as printer + // name, number of copies, page range, etc. + bool UpdatePrintSettings(blink::WebLocalFrame* frame, + const blink::WebNode& node, + const base::DictionaryValue& passed_job_settings); + + // Get final print settings from the user. // Return false if the user cancels or on error. bool GetPrintSettingsFromUser(blink::WebFrame* frame, @@ -193,6 +234,13 @@ class PrintWebViewHelper // Script Initiated Printing ------------------------------------------------ + // Notifies the browser a print preview page has been rendered. + // |page_number| is 0-based. + // For a valid |page_number| with modifiable content, + // |metafile| is the rendered page. Otherwise |metafile| is NULL. + // Returns true if print preview should continue, false on failure. + bool PreviewPageRendered(int page_number, PdfMetafileSkia* metafile); + void SetPrintPagesParams(const PrintMsg_PrintPages_Params& settings); // WebView used only to print the selection. @@ -213,10 +261,121 @@ class PrintWebViewHelper // True, when printing from print preview. bool print_for_preview_; + // Keeps track of the state of print preview between messages. + // TODO(vitalybuka): Create PrintPreviewContext when needed and delete after + // use. Now it's interaction with various messages is confusing. + class PrintPreviewContext { + public: + PrintPreviewContext(); + ~PrintPreviewContext(); + + // Initializes the print preview context. Need to be called to set + // the |web_frame| / |web_node| to generate the print preview for. + void InitWithFrame(blink::WebLocalFrame* web_frame); + void InitWithNode(const blink::WebNode& web_node); + + // Does bookkeeping at the beginning of print preview. + void OnPrintPreview(); + + // Create the print preview document. |pages| is empty to print all pages. + // Takes ownership of |prepared_frame|. + bool CreatePreviewDocument(PrepareFrameAndViewForPrint* prepared_frame, + const std::vector& pages); + + // Called after a page gets rendered. |page_time| is how long the + // rendering took. + void RenderedPreviewPage(const base::TimeDelta& page_time); + + // Updates the print preview context when the required pages are rendered. + void AllPagesRendered(); + + // Finalizes the print ready preview document. + void FinalizePrintReadyDocument(); + + // Cleanup after print preview finishes. + void Finished(); + + // Cleanup after print preview fails. + void Failed(bool report_error); + + // Helper functions + int GetNextPageNumber(); + bool IsRendering() const; + bool IsModifiable(); + bool HasSelection(); + bool IsLastPageOfPrintReadyMetafile() const; + bool IsFinalPageRendered() const; + + // Setters + void set_generate_draft_pages(bool generate_draft_pages); + void set_error(enum PrintPreviewErrorBuckets error); + + // Getters + // Original frame for which preview was requested. + blink::WebLocalFrame* source_frame(); + // Original node for which preview was requested. + const blink::WebNode& source_node() const; + + // Frame to be use to render preview. May be the same as source_frame(), or + // generated from it, e.g. copy of selected block. + blink::WebLocalFrame* prepared_frame(); + // Node to be use to render preview. May be the same as source_node(), or + // generated from it, e.g. copy of selected block. + const blink::WebNode& prepared_node() const; + + int total_page_count() const; + bool generate_draft_pages() const; + PdfMetafileSkia* metafile(); + int last_error() const; + + private: + enum State { + UNINITIALIZED, // Not ready to render. + INITIALIZED, // Ready to render. + RENDERING, // Rendering. + DONE // Finished rendering. + }; + + // Reset some of the internal rendering context. + void ClearContext(); + + // Specifies what to render for print preview. + FrameReference source_frame_; + blink::WebNode source_node_; + + scoped_ptr prep_frame_view_; + scoped_ptr metafile_; + + // Total page count in the renderer. + int total_page_count_; + + // The current page to render. + int current_page_index_; + + // List of page indices that need to be rendered. + std::vector pages_to_render_; + + // True, when draft pages needs to be generated. + bool generate_draft_pages_; + + // Specifies the total number of pages in the print ready metafile. + int print_ready_metafile_page_count_; + + base::TimeDelta document_render_time_; + base::TimeTicks begin_time_; + + enum PrintPreviewErrorBuckets error_; + + State state_; + }; + + bool print_node_in_progress_; bool is_loading_; bool is_scripted_preview_delayed_; + PrintPreviewContext print_preview_context_; + // Used to fix a race condition where the source is a PDF and print preview // hangs because RequestPrintPreview is called before DidStopLoading() is // called. This is a store for the RequestPrintPreview() call and its 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 38bd9114d244..82d7779d0266 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 @@ -24,6 +24,36 @@ namespace printing { using blink::WebFrame; +bool PrintWebViewHelper::RenderPreviewPage( + int page_number, + const PrintMsg_Print_Params& print_params) { + PrintMsg_PrintPage_Params page_params; + page_params.params = print_params; + page_params.page_number = page_number; + scoped_ptr draft_metafile; + PdfMetafileSkia* initial_render_metafile = print_preview_context_.metafile(); + if (print_preview_context_.IsModifiable() && is_print_ready_metafile_sent_) { + draft_metafile.reset(new PdfMetafileSkia); + initial_render_metafile = draft_metafile.get(); + } + + base::TimeTicks begin_time = base::TimeTicks::Now(); + PrintPageInternal(page_params, + print_preview_context_.prepared_frame(), + initial_render_metafile); + print_preview_context_.RenderedPreviewPage( + base::TimeTicks::Now() - begin_time); + if (draft_metafile.get()) { + draft_metafile->FinishDocument(); + } else if (print_preview_context_.IsModifiable() && + print_preview_context_.generate_draft_pages()) { + DCHECK(!draft_metafile.get()); + draft_metafile = + print_preview_context_.metafile()->GetMetafileForCurrentPage(); + } + return PreviewPageRendered(page_number, draft_metafile.get()); +} + bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, int page_count) { PdfMetafileSkia metafile; 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 93fd06464b37..0785e30a9cfb 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 @@ -50,6 +50,47 @@ void PrintWebViewHelper::PrintPageInternal( Send(new PrintHostMsg_DidPrintPage(routing_id(), page_params)); } +bool PrintWebViewHelper::RenderPreviewPage( + int page_number, + const PrintMsg_Print_Params& print_params) { + PrintMsg_Print_Params printParams = print_params; + scoped_ptr draft_metafile; + PdfMetafileSkia* initial_render_metafile = print_preview_context_.metafile(); + + bool render_to_draft = print_preview_context_.IsModifiable() && + is_print_ready_metafile_sent_; + + if (render_to_draft) { + draft_metafile.reset(new PdfMetafileSkia()); + if (!draft_metafile->Init()) { + print_preview_context_.set_error( + PREVIEW_ERROR_MAC_DRAFT_METAFILE_INIT_FAILED); + LOG(ERROR) << "Draft PdfMetafileSkia Init failed"; + return false; + } + initial_render_metafile = draft_metafile.get(); + } + + base::TimeTicks begin_time = base::TimeTicks::Now(); + gfx::Size page_size; + RenderPage(printParams, page_number, print_preview_context_.prepared_frame(), + true, initial_render_metafile, &page_size, NULL); + print_preview_context_.RenderedPreviewPage( + base::TimeTicks::Now() - begin_time); + + if (draft_metafile.get()) { + draft_metafile->FinishDocument(); + } else { + if (print_preview_context_.IsModifiable() && + print_preview_context_.generate_draft_pages()) { + DCHECK(!draft_metafile.get()); + draft_metafile = + print_preview_context_.metafile()->GetMetafileForCurrentPage(); + } + } + return PreviewPageRendered(page_number, draft_metafile.get()); +} + void PrintWebViewHelper::RenderPage(const PrintMsg_Print_Params& params, int page_number, WebFrame* frame, 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 f98ee5097cca..dcd388fd1111 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 @@ -21,7 +21,6 @@ namespace printing { using blink::WebFrame; -#if 0 bool PrintWebViewHelper::RenderPreviewPage( int page_number, const PrintMsg_Print_Params& print_params) { @@ -53,7 +52,6 @@ bool PrintWebViewHelper::RenderPreviewPage( } return PreviewPageRendered(page_number, draft_metafile.get()); } -#endif bool PrintWebViewHelper::PrintPagesNative(blink::WebFrame* frame, int page_count) { diff --git a/chromium_src/chrome/utility/printing_handler.cc b/chromium_src/chrome/utility/printing_handler.cc deleted file mode 100644 index db6d9533cf51..000000000000 --- a/chromium_src/chrome/utility/printing_handler.cc +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/utility/printing_handler.h" - -#include "base/files/file_util.h" -#include "base/lazy_instance.h" -#include "base/path_service.h" -#include "base/scoped_native_library.h" -#include "chrome/common/print_messages.h" -#include "content/public/utility/utility_thread.h" -#include "pdf/pdf.h" -#include "printing/page_range.h" -#include "printing/pdf_render_settings.h" - -#if defined(OS_WIN) -#include "printing/emf_win.h" -#include "ui/gfx/gdi_util.h" -#endif - - -namespace { - -bool Send(IPC::Message* message) { - return content::UtilityThread::Get()->Send(message); -} - -void ReleaseProcessIfNeeded() { - content::UtilityThread::Get()->ReleaseProcessIfNeeded(); -} - -} // namespace - -PrintingHandler::PrintingHandler() {} - -PrintingHandler::~PrintingHandler() {} - -bool PrintingHandler::OnMessageReceived(const IPC::Message& message) { - bool handled = true; - IPC_BEGIN_MESSAGE_MAP(PrintingHandler, message) -#if defined(OS_WIN) - IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles, - OnRenderPDFPagesToMetafile) - IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage, - OnRenderPDFPagesToMetafileGetPage) - IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop, - OnRenderPDFPagesToMetafileStop) -#endif // OS_WIN - IPC_MESSAGE_UNHANDLED(handled = false) - IPC_END_MESSAGE_MAP() - return handled; -} - -#if defined(OS_WIN) -void PrintingHandler::OnRenderPDFPagesToMetafile( - IPC::PlatformFileForTransit pdf_transit, - const printing::PdfRenderSettings& settings) { - pdf_rendering_settings_ = settings; - base::File pdf_file = IPC::PlatformFileForTransitToFile(pdf_transit); - int page_count = LoadPDF(pdf_file.Pass()); - //int page_count = 1; - Send( - new ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount(page_count)); -} - -void PrintingHandler::OnRenderPDFPagesToMetafileGetPage( - int page_number, - IPC::PlatformFileForTransit output_file) { - base::File emf_file = IPC::PlatformFileForTransitToFile(output_file); - float scale_factor = 1.0f; - bool success = - RenderPdfPageToMetafile(page_number, emf_file.Pass(), &scale_factor); - Send(new ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone( - success, scale_factor)); -} - -void PrintingHandler::OnRenderPDFPagesToMetafileStop() { - ReleaseProcessIfNeeded(); -} - -int PrintingHandler::LoadPDF(base::File pdf_file) { - int64 length64 = pdf_file.GetLength(); - if (length64 <= 0 || length64 > std::numeric_limits::max()) - return 0; - int length = static_cast(length64); - - pdf_data_.resize(length); - if (length != pdf_file.Read(0, pdf_data_.data(), pdf_data_.size())) - return 0; - - int total_page_count = 0; - if (!chrome_pdf::GetPDFDocInfo( - &pdf_data_.front(), pdf_data_.size(), &total_page_count, NULL)) { - return 0; - } - return total_page_count; -} - -bool PrintingHandler::RenderPdfPageToMetafile(int page_number, - base::File output_file, - float* scale_factor) { - printing::Emf metafile; - metafile.Init(); - - // We need to scale down DC to fit an entire page into DC available area. - // Current metafile is based on screen DC and have current screen size. - // Writing outside of those boundaries will result in the cut-off output. - // On metafiles (this is the case here), scaling down will still record - // original coordinates and we'll be able to print in full resolution. - // Before playback we'll need to counter the scaling up that will happen - // in the service (print_system_win.cc). - *scale_factor = - gfx::CalculatePageScale(metafile.context(), - pdf_rendering_settings_.area().right(), - pdf_rendering_settings_.area().bottom()); - gfx::ScaleDC(metafile.context(), *scale_factor); - - // The underlying metafile is of type Emf and ignores the arguments passed - // to StartPage. - metafile.StartPage(gfx::Size(), gfx::Rect(), 1); - if (!chrome_pdf::RenderPDFPageToDC( - &pdf_data_.front(), - pdf_data_.size(), - page_number, - metafile.context(), - pdf_rendering_settings_.dpi(), - pdf_rendering_settings_.area().x(), - pdf_rendering_settings_.area().y(), - pdf_rendering_settings_.area().width(), - pdf_rendering_settings_.area().height(), - true, - false, - true, - true, - pdf_rendering_settings_.autorotate())) { - return false; - } - metafile.FinishPage(); - metafile.FinishDocument(); - return metafile.SaveTo(&output_file); -} - -#endif // OS_WIN diff --git a/chromium_src/chrome/utility/printing_handler_win.cc b/chromium_src/chrome/utility/printing_handler_win.cc new file mode 100644 index 000000000000..ec908d19fc51 --- /dev/null +++ b/chromium_src/chrome/utility/printing_handler_win.cc @@ -0,0 +1,239 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/utility/printing_handler_win.h" + +#include "base/files/file_util.h" +#include "base/lazy_instance.h" +#include "base/path_service.h" +#include "base/scoped_native_library.h" +#include "chrome/common/print_messages.h" +#include "content/public/utility/utility_thread.h" +#include "printing/emf_win.h" +#include "printing/page_range.h" +#include "printing/pdf_render_settings.h" +#include "ui/gfx/gdi_util.h" + +namespace { + +bool Send(IPC::Message* message) { + return content::UtilityThread::Get()->Send(message); +} + +void ReleaseProcessIfNeeded() { + content::UtilityThread::Get()->ReleaseProcessIfNeeded(); +} + +class PdfFunctions { + public: + PdfFunctions() : get_pdf_doc_info_func_(NULL), + render_pdf_to_dc_func_(NULL) {} + + bool Init() { + base::FilePath module_path; + if (!PathService::Get(base::DIR_MODULE, &module_path)) + return false; + base::FilePath::StringType name(FILE_PATH_LITERAL("pdf.dll")); + pdf_lib_.Reset(base::LoadNativeLibrary(module_path.Append(name), NULL)); + if (!pdf_lib_.is_valid()) { + LOG(WARNING) << "Couldn't load PDF plugin"; + return false; + } + + get_pdf_doc_info_func_ = + reinterpret_cast( + pdf_lib_.GetFunctionPointer("GetPDFDocInfo")); + LOG_IF(WARNING, !get_pdf_doc_info_func_) << "Missing GetPDFDocInfo"; + + render_pdf_to_dc_func_ = + reinterpret_cast( + pdf_lib_.GetFunctionPointer("RenderPDFPageToDC")); + LOG_IF(WARNING, !render_pdf_to_dc_func_) << "Missing RenderPDFPageToDC"; + + if (!get_pdf_doc_info_func_ || !render_pdf_to_dc_func_) { + Reset(); + } + + return IsValid(); + } + + bool IsValid() const { + return pdf_lib_.is_valid(); + } + + void Reset() { + pdf_lib_.Reset(NULL); + } + + bool GetPDFDocInfo(const void* pdf_buffer, + int buffer_size, + int* page_count, + double* max_page_width) { + if (!get_pdf_doc_info_func_) + return false; + return get_pdf_doc_info_func_(pdf_buffer, buffer_size, page_count, + max_page_width); + } + + bool RenderPDFPageToDC(const void* pdf_buffer, + int buffer_size, + int page_number, + HDC dc, + int dpi, + int bounds_origin_x, + int bounds_origin_y, + int bounds_width, + int bounds_height, + bool fit_to_bounds, + bool stretch_to_bounds, + bool keep_aspect_ratio, + bool center_in_bounds, + bool autorotate) { + if (!render_pdf_to_dc_func_) + return false; + return render_pdf_to_dc_func_(pdf_buffer, buffer_size, page_number, + dc, dpi, bounds_origin_x, + bounds_origin_y, bounds_width, bounds_height, + fit_to_bounds, stretch_to_bounds, + keep_aspect_ratio, center_in_bounds, + autorotate); + } + + private: + // Exported by PDF plugin. + typedef bool (*GetPDFDocInfoProc)(const void* pdf_buffer, + int buffer_size, int* page_count, + double* max_page_width); + typedef bool (*RenderPDFPageToDCProc)( + const void* pdf_buffer, int buffer_size, int page_number, HDC dc, + int dpi, int bounds_origin_x, int bounds_origin_y, + int bounds_width, int bounds_height, bool fit_to_bounds, + bool stretch_to_bounds, bool keep_aspect_ratio, bool center_in_bounds, + bool autorotate); + + RenderPDFPageToDCProc render_pdf_to_dc_func_; + GetPDFDocInfoProc get_pdf_doc_info_func_; + + base::ScopedNativeLibrary pdf_lib_; + + DISALLOW_COPY_AND_ASSIGN(PdfFunctions); +}; + +base::LazyInstance g_pdf_lib = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +PrintingHandlerWin::PrintingHandlerWin() {} + +PrintingHandlerWin::~PrintingHandlerWin() {} + +// static +void PrintingHandlerWin::PreSandboxStartup() { + g_pdf_lib.Get().Init(); +} + +bool PrintingHandlerWin::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PrintingHandlerWin, message) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles, + OnRenderPDFPagesToMetafile) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage, + OnRenderPDFPagesToMetafileGetPage) + IPC_MESSAGE_HANDLER(ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop, + OnRenderPDFPagesToMetafileStop) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void PrintingHandlerWin::OnRenderPDFPagesToMetafile( + IPC::PlatformFileForTransit pdf_transit, + const printing::PdfRenderSettings& settings) { + pdf_rendering_settings_ = settings; + base::File pdf_file = IPC::PlatformFileForTransitToFile(pdf_transit); + int page_count = LoadPDF(pdf_file.Pass()); + //int page_count = 1; + Send( + new ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount(page_count)); +} + +void PrintingHandlerWin::OnRenderPDFPagesToMetafileGetPage( + int page_number, + IPC::PlatformFileForTransit output_file) { + base::File emf_file = IPC::PlatformFileForTransitToFile(output_file); + float scale_factor = 1.0f; + bool success = + RenderPdfPageToMetafile(page_number, emf_file.Pass(), &scale_factor); + Send(new ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone( + success, scale_factor)); +} + +void PrintingHandlerWin::OnRenderPDFPagesToMetafileStop() { + ReleaseProcessIfNeeded(); +} + +int PrintingHandlerWin::LoadPDF(base::File pdf_file) { + if (!g_pdf_lib.Get().IsValid()) + return 0; + + int64 length64 = pdf_file.GetLength(); + if (length64 <= 0 || length64 > std::numeric_limits::max()) + return 0; + int length = static_cast(length64); + + pdf_data_.resize(length); + if (length != pdf_file.Read(0, pdf_data_.data(), pdf_data_.size())) + return 0; + + int total_page_count = 0; + if (!g_pdf_lib.Get().GetPDFDocInfo( + &pdf_data_.front(), pdf_data_.size(), &total_page_count, NULL)) { + return 0; + } + return total_page_count; +} + +bool PrintingHandlerWin::RenderPdfPageToMetafile(int page_number, + base::File output_file, + float* scale_factor) { + printing::Emf metafile; + metafile.Init(); + + // We need to scale down DC to fit an entire page into DC available area. + // Current metafile is based on screen DC and have current screen size. + // Writing outside of those boundaries will result in the cut-off output. + // On metafiles (this is the case here), scaling down will still record + // original coordinates and we'll be able to print in full resolution. + // Before playback we'll need to counter the scaling up that will happen + // in the service (print_system_win.cc). + *scale_factor = + gfx::CalculatePageScale(metafile.context(), + pdf_rendering_settings_.area().right(), + pdf_rendering_settings_.area().bottom()); + gfx::ScaleDC(metafile.context(), *scale_factor); + + // The underlying metafile is of type Emf and ignores the arguments passed + // to StartPage. + metafile.StartPage(gfx::Size(), gfx::Rect(), 1); + if (!g_pdf_lib.Get().RenderPDFPageToDC( + &pdf_data_.front(), + pdf_data_.size(), + page_number, + metafile.context(), + pdf_rendering_settings_.dpi(), + pdf_rendering_settings_.area().x(), + pdf_rendering_settings_.area().y(), + pdf_rendering_settings_.area().width(), + pdf_rendering_settings_.area().height(), + true, + false, + true, + true, + pdf_rendering_settings_.autorotate())) { + return false; + } + metafile.FinishPage(); + metafile.FinishDocument(); + return metafile.SaveTo(&output_file); +} diff --git a/chromium_src/chrome/utility/printing_handler.h b/chromium_src/chrome/utility/printing_handler_win.h similarity index 70% rename from chromium_src/chrome/utility/printing_handler.h rename to chromium_src/chrome/utility/printing_handler_win.h index b1f09acb9cc9..5b8c5e970f15 100644 --- a/chromium_src/chrome/utility/printing_handler.h +++ b/chromium_src/chrome/utility/printing_handler_win.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_UTILITY_PRINTING_HANDLER_H_ -#define CHROME_UTILITY_PRINTING_HANDLER_H_ +#ifndef CHROME_UTILITY_PRINTING_HANDLER_WIN_H_ +#define CHROME_UTILITY_PRINTING_HANDLER_WIN_H_ #include "base/compiler_specific.h" #include "base/macros.h" @@ -11,10 +11,6 @@ #include "ipc/ipc_platform_file.h" #include "printing/pdf_render_settings.h" -#if !defined(ENABLE_PRINT_PREVIEW) && !defined(OS_WIN) -#error "Windows or full printing must be enabled" -#endif - namespace printing { class PdfRenderSettings; struct PwgRasterSettings; @@ -22,38 +18,34 @@ struct PageRange; } // Dispatches IPCs for printing. -class PrintingHandler : public UtilityMessageHandler { +class PrintingHandlerWin : public UtilityMessageHandler { public: - PrintingHandler(); - ~PrintingHandler() override; + PrintingHandlerWin(); + ~PrintingHandlerWin() override; // IPC::Listener: bool OnMessageReceived(const IPC::Message& message) override; + static void PrintingHandlerWin::PreSandboxStartup(); + private: // IPC message handlers. -#if defined(OS_WIN) void OnRenderPDFPagesToMetafile(IPC::PlatformFileForTransit pdf_transit, const printing::PdfRenderSettings& settings); void OnRenderPDFPagesToMetafileGetPage( int page_number, IPC::PlatformFileForTransit output_file); void OnRenderPDFPagesToMetafileStop(); -#endif // OS_WIN -#if defined(OS_WIN) int LoadPDF(base::File pdf_file); bool RenderPdfPageToMetafile(int page_number, base::File output_file, float* scale_factor); -#endif // OS_WIN -#if defined(OS_WIN) std::vector pdf_data_; printing::PdfRenderSettings pdf_rendering_settings_; -#endif - DISALLOW_COPY_AND_ASSIGN(PrintingHandler); + DISALLOW_COPY_AND_ASSIGN(PrintingHandlerWin); }; -#endif // CHROME_UTILITY_PRINTING_HANDLER_H_ +#endif // CHROME_UTILITY_PRINTING_HANDLER_WIN_H_ diff --git a/chromium_src/library_loaders/libspeechd.h b/chromium_src/library_loaders/libspeechd.h index 11afa3307588..0d62f2c5da6e 100644 --- a/chromium_src/library_loaders/libspeechd.h +++ b/chromium_src/library_loaders/libspeechd.h @@ -20,19 +20,19 @@ class LibSpeechdLoader { bool loaded() const { return loaded_; } - typeof(&::spd_open) spd_open; - typeof(&::spd_say) spd_say; - typeof(&::spd_stop) spd_stop; - typeof(&::spd_close) spd_close; - typeof(&::spd_pause) spd_pause; - typeof(&::spd_resume) spd_resume; - typeof(&::spd_set_notification_on) spd_set_notification_on; - typeof(&::spd_set_voice_rate) spd_set_voice_rate; - typeof(&::spd_set_voice_pitch) spd_set_voice_pitch; - typeof(&::spd_list_synthesis_voices) spd_list_synthesis_voices; - typeof(&::spd_set_synthesis_voice) spd_set_synthesis_voice; - typeof(&::spd_list_modules) spd_list_modules; - typeof(&::spd_set_output_module) spd_set_output_module; + decltype(&::spd_open) spd_open; + decltype(&::spd_say) spd_say; + decltype(&::spd_stop) spd_stop; + decltype(&::spd_close) spd_close; + decltype(&::spd_pause) spd_pause; + decltype(&::spd_resume) spd_resume; + decltype(&::spd_set_notification_on) spd_set_notification_on; + decltype(&::spd_set_voice_rate) spd_set_voice_rate; + decltype(&::spd_set_voice_pitch) spd_set_voice_pitch; + decltype(&::spd_list_synthesis_voices) spd_list_synthesis_voices; + decltype(&::spd_set_synthesis_voice) spd_set_synthesis_voice; + decltype(&::spd_list_modules) spd_list_modules; + decltype(&::spd_set_output_module) spd_set_output_module; private: diff --git a/chromium_src/library_loaders/libspeechd_loader.cc b/chromium_src/library_loaders/libspeechd_loader.cc index 84547fbc5749..606661000528 100644 --- a/chromium_src/library_loaders/libspeechd_loader.cc +++ b/chromium_src/library_loaders/libspeechd_loader.cc @@ -34,7 +34,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_open = - reinterpret_castspd_open)>( + reinterpret_castspd_open)>( dlsym(library_, "spd_open")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -47,7 +47,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_say = - reinterpret_castspd_say)>( + reinterpret_castspd_say)>( dlsym(library_, "spd_say")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -60,7 +60,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_stop = - reinterpret_castspd_stop)>( + reinterpret_castspd_stop)>( dlsym(library_, "spd_stop")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -73,7 +73,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_close = - reinterpret_castspd_close)>( + reinterpret_castspd_close)>( dlsym(library_, "spd_close")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -86,7 +86,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_pause = - reinterpret_castspd_pause)>( + reinterpret_castspd_pause)>( dlsym(library_, "spd_pause")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -99,7 +99,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_resume = - reinterpret_castspd_resume)>( + reinterpret_castspd_resume)>( dlsym(library_, "spd_resume")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -112,7 +112,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_set_notification_on = - reinterpret_castspd_set_notification_on)>( + reinterpret_castspd_set_notification_on)>( dlsym(library_, "spd_set_notification_on")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -125,7 +125,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_set_voice_rate = - reinterpret_castspd_set_voice_rate)>( + reinterpret_castspd_set_voice_rate)>( dlsym(library_, "spd_set_voice_rate")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -138,7 +138,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_set_voice_pitch = - reinterpret_castspd_set_voice_pitch)>( + reinterpret_castspd_set_voice_pitch)>( dlsym(library_, "spd_set_voice_pitch")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -151,7 +151,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_list_synthesis_voices = - reinterpret_castspd_list_synthesis_voices)>( + reinterpret_castspd_list_synthesis_voices)>( dlsym(library_, "spd_list_synthesis_voices")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -164,7 +164,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_set_synthesis_voice = - reinterpret_castspd_set_synthesis_voice)>( + reinterpret_castspd_set_synthesis_voice)>( dlsym(library_, "spd_set_synthesis_voice")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -177,7 +177,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_list_modules = - reinterpret_castspd_list_modules)>( + reinterpret_castspd_list_modules)>( dlsym(library_, "spd_list_modules")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) @@ -190,7 +190,7 @@ bool LibSpeechdLoader::Load(const std::string& library_name) { #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DLOPEN) spd_set_output_module = - reinterpret_castspd_set_output_module)>( + reinterpret_castspd_set_output_module)>( dlsym(library_, "spd_set_output_module")); #endif #if defined(LIBRARY_LOADER_OUT_RELEASE_GEN_LIBRARY_LOADERS_LIBSPEECHD_H_DT_NEEDED) 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 new file mode 100644 index 000000000000..1056983a8e2f --- /dev/null +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.cc @@ -0,0 +1,330 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/test/embedded_test_server/stream_listen_socket.h" + +#if defined(OS_WIN) +// winsock2.h must be included first in order to ensure it is included before +// windows.h. +#include +#elif defined(OS_POSIX) +#include +#include +#include +#include +#include +#include "net/base/net_errors.h" +#endif + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/sys_byteorder.h" +#include "base/threading/platform_thread.h" +#include "build/build_config.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/socket/socket_descriptor.h" + +using std::string; + +#if defined(OS_WIN) +typedef int socklen_t; +#endif // defined(OS_WIN) + +namespace net { + +namespace test_server { + +namespace { + +const int kReadBufSize = 4096; + +} // namespace + +#if defined(OS_WIN) +const int StreamListenSocket::kSocketError = SOCKET_ERROR; +#elif defined(OS_POSIX) +const int StreamListenSocket::kSocketError = -1; +#endif + +StreamListenSocket::StreamListenSocket(SocketDescriptor s, + StreamListenSocket::Delegate* del) + : socket_delegate_(del), + socket_(s), + reads_paused_(false), + has_pending_reads_(false) { +#if defined(OS_WIN) + socket_event_ = WSACreateEvent(); + // TODO(ibrar): error handling in case of socket_event_ == WSA_INVALID_EVENT. + WatchSocket(NOT_WAITING); +#elif defined(OS_POSIX) + wait_state_ = NOT_WAITING; +#endif +} + +StreamListenSocket::~StreamListenSocket() { + CloseSocket(); +#if defined(OS_WIN) + if (socket_event_) { + WSACloseEvent(socket_event_); + socket_event_ = WSA_INVALID_EVENT; + } +#endif +} + +void StreamListenSocket::Send(const char* bytes, + int len, + bool append_linefeed) { + SendInternal(bytes, len); + if (append_linefeed) + SendInternal("\r\n", 2); +} + +void StreamListenSocket::Send(const string& str, bool append_linefeed) { + Send(str.data(), static_cast(str.length()), append_linefeed); +} + +int StreamListenSocket::GetLocalAddress(IPEndPoint* address) const { + SockaddrStorage storage; + if (getsockname(socket_, storage.addr, &storage.addr_len)) { +#if defined(OS_WIN) + int err = WSAGetLastError(); +#else + int err = errno; +#endif + return MapSystemError(err); + } + if (!address->FromSockAddr(storage.addr, storage.addr_len)) + return ERR_ADDRESS_INVALID; + return OK; +} + +int StreamListenSocket::GetPeerAddress(IPEndPoint* address) const { + SockaddrStorage storage; + if (getpeername(socket_, storage.addr, &storage.addr_len)) { +#if defined(OS_WIN) + int err = WSAGetLastError(); +#else + int err = errno; +#endif + return MapSystemError(err); + } + + if (!address->FromSockAddr(storage.addr, storage.addr_len)) + return ERR_ADDRESS_INVALID; + + return OK; +} + +SocketDescriptor StreamListenSocket::AcceptSocket() { + SocketDescriptor conn = HANDLE_EINTR(accept(socket_, NULL, NULL)); + if (conn == kInvalidSocket) + LOG(ERROR) << "Error accepting connection."; + else + SetNonBlocking(conn); + return conn; +} + +void StreamListenSocket::SendInternal(const char* bytes, int len) { + char* send_buf = const_cast(bytes); + int len_left = len; + while (true) { + int sent = HANDLE_EINTR(send(socket_, send_buf, len_left, 0)); + if (sent == len_left) { // A shortcut to avoid extraneous checks. + break; + } + if (sent == kSocketError) { +#if defined(OS_WIN) + if (WSAGetLastError() != WSAEWOULDBLOCK) { + LOG(ERROR) << "send failed: WSAGetLastError()==" << WSAGetLastError(); +#elif defined(OS_POSIX) + if (errno != EWOULDBLOCK && errno != EAGAIN) { + LOG(ERROR) << "send failed: errno==" << errno; +#endif + break; + } + // Otherwise we would block, and now we have to wait for a retry. + // Fall through to PlatformThread::YieldCurrentThread() + } else { + // sent != len_left according to the shortcut above. + // Shift the buffer start and send the remainder after a short while. + send_buf += sent; + len_left -= sent; + } + base::PlatformThread::YieldCurrentThread(); + } +} + +void StreamListenSocket::Listen() { + int backlog = 10; // TODO(erikkay): maybe don't allow any backlog? + if (listen(socket_, backlog) == -1) { + // TODO(erikkay): error handling. + LOG(ERROR) << "Could not listen on socket."; + return; + } +#if defined(OS_POSIX) + WatchSocket(WAITING_ACCEPT); +#endif +} + +void StreamListenSocket::Read() { + char buf[kReadBufSize + 1]; // +1 for null termination. + int len; + do { + len = HANDLE_EINTR(recv(socket_, buf, kReadBufSize, 0)); + if (len == kSocketError) { +#if defined(OS_WIN) + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { +#elif defined(OS_POSIX) + if (errno == EWOULDBLOCK || errno == EAGAIN) { +#endif + break; + } else { + // TODO(ibrar): some error handling required here. + break; + } + } else if (len == 0) { +#if defined(OS_POSIX) + // In Windows, Close() is called by OnObjectSignaled. In POSIX, we need + // to call it here. + Close(); +#endif + } else { + // TODO(ibrar): maybe change DidRead to take a length instead. + DCHECK_GT(len, 0); + DCHECK_LE(len, kReadBufSize); + buf[len] = 0; // Already create a buffer with +1 length. + socket_delegate_->DidRead(this, buf, len); + } + } while (len == kReadBufSize); +} + +void StreamListenSocket::Close() { +#if defined(OS_POSIX) + if (wait_state_ == NOT_WAITING) + return; + wait_state_ = NOT_WAITING; +#endif + UnwatchSocket(); + socket_delegate_->DidClose(this); +} + +void StreamListenSocket::CloseSocket() { + if (socket_ != kInvalidSocket) { + UnwatchSocket(); +#if defined(OS_WIN) + closesocket(socket_); +#elif defined(OS_POSIX) + close(socket_); +#endif + } +} + +void StreamListenSocket::WatchSocket(WaitState state) { +#if defined(OS_WIN) + WSAEventSelect(socket_, socket_event_, FD_ACCEPT | FD_CLOSE | FD_READ); + watcher_.StartWatching(socket_event_, this); +#elif defined(OS_POSIX) + // Implicitly calls StartWatchingFileDescriptor(). + base::MessageLoopForIO::current()->WatchFileDescriptor( + socket_, true, base::MessageLoopForIO::WATCH_READ, &watcher_, this); + wait_state_ = state; +#endif +} + +void StreamListenSocket::UnwatchSocket() { +#if defined(OS_WIN) + watcher_.StopWatching(); +#elif defined(OS_POSIX) + watcher_.StopWatchingFileDescriptor(); +#endif +} + +// TODO(ibrar): We can add these functions into OS dependent files. +#if defined(OS_WIN) +// MessageLoop watcher callback. +void StreamListenSocket::OnObjectSignaled(HANDLE object) { + WSANETWORKEVENTS ev; + if (kSocketError == WSAEnumNetworkEvents(socket_, socket_event_, &ev)) { + // TODO + return; + } + + // If both FD_CLOSE and FD_READ are set we only call Read(). + // This will cause OnObjectSignaled to be called immediately again + // unless this socket is destroyed in Read(). + if ((ev.lNetworkEvents & (FD_CLOSE | FD_READ)) == FD_CLOSE) { + Close(); + // Close might have deleted this object. We should return immediately. + return; + } + // The object was reset by WSAEnumNetworkEvents. Watch for the next signal. + watcher_.StartWatching(object, this); + + if (ev.lNetworkEvents == 0) { + // Occasionally the event is set even though there is no new data. + // The net seems to think that this is ignorable. + return; + } + if (ev.lNetworkEvents & FD_ACCEPT) { + Accept(); + } + if (ev.lNetworkEvents & FD_READ) { + if (reads_paused_) { + has_pending_reads_ = true; + } else { + Read(); + // Read might have deleted this object. We should return immediately. + } + } +} +#elif defined(OS_POSIX) +void StreamListenSocket::OnFileCanReadWithoutBlocking(int fd) { + switch (wait_state_) { + case WAITING_ACCEPT: + Accept(); + break; + case WAITING_READ: + if (reads_paused_) { + has_pending_reads_ = true; + } else { + Read(); + } + break; + default: + // Close() is called by Read() in the Linux case. + NOTREACHED(); + break; + } +} + +void StreamListenSocket::OnFileCanWriteWithoutBlocking(int fd) { + // MessagePumpLibevent callback, we don't listen for write events + // so we shouldn't ever reach here. + NOTREACHED(); +} + +#endif + +void StreamListenSocket::PauseReads() { + DCHECK(!reads_paused_); + reads_paused_ = true; +} + +void StreamListenSocket::ResumeReads() { + DCHECK(reads_paused_); + reads_paused_ = false; + if (has_pending_reads_) { + has_pending_reads_ = false; + Read(); + } +} + +} // namespace test_server + +} // namespace net 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 new file mode 100644 index 000000000000..02a8b9827a2e --- /dev/null +++ b/chromium_src/net/test/embedded_test_server/stream_listen_socket.h @@ -0,0 +1,151 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Stream-based listen socket implementation that handles reading and writing +// to the socket, but does not handle creating the socket nor connecting +// sockets, which are handled by subclasses on creation and in Accept, +// respectively. + +// StreamListenSocket handles IO asynchronously in the specified MessageLoop. +// This class is NOT thread safe. It uses WSAEVENT handles to monitor activity +// in a given MessageLoop. This means that callbacks will happen in that loop's +// thread always and that all other methods (including constructor and +// destructor) should also be called from the same thread. + +#ifndef NET_TEST_EMBEDDED_TEST_SERVER_STREAM_LISTEN_SOCKET_H_ +#define NET_TEST_EMBEDDED_TEST_SERVER_STREAM_LISTEN_SOCKET_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include +#endif +#include +#if defined(OS_WIN) +#include "base/win/object_watcher.h" +#elif defined(OS_POSIX) +#include "base/message_loop/message_loop.h" +#endif + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/net_export.h" +#include "net/socket/socket_descriptor.h" + +namespace net { + +class IPEndPoint; + +namespace test_server { + +class StreamListenSocket : +#if defined(OS_WIN) + public base::win::ObjectWatcher::Delegate { +#elif defined(OS_POSIX) + public base::MessageLoopForIO::Watcher { +#endif + + public: + ~StreamListenSocket() override; + + // TODO(erikkay): this delegate should really be split into two parts + // to split up the listener from the connected socket. Perhaps this class + // should be split up similarly. + class Delegate { + public: + // |server| is the original listening Socket, connection is the new + // Socket that was created. + virtual void DidAccept(StreamListenSocket* server, + scoped_ptr connection) = 0; + virtual void DidRead(StreamListenSocket* connection, + const char* data, + int len) = 0; + virtual void DidClose(StreamListenSocket* sock) = 0; + + protected: + virtual ~Delegate() {} + }; + + // Send data to the socket. + void Send(const char* bytes, int len, bool append_linefeed = false); + void Send(const std::string& str, bool append_linefeed = false); + + // Copies the local address to |address|. Returns a network error code. + // This method is virtual to support unit testing. + virtual int GetLocalAddress(IPEndPoint* address) const; + // Copies the peer address to |address|. Returns a network error code. + // This method is virtual to support unit testing. + virtual int GetPeerAddress(IPEndPoint* address) const; + + static const int kSocketError; + + protected: + enum WaitState { NOT_WAITING = 0, WAITING_ACCEPT = 1, WAITING_READ = 2 }; + + StreamListenSocket(SocketDescriptor s, Delegate* del); + + SocketDescriptor AcceptSocket(); + virtual void Accept() = 0; + + void Listen(); + void Read(); + void Close(); + void CloseSocket(); + + // Pass any value in case of Windows, because in Windows + // we are not using state. + void WatchSocket(WaitState state); + void UnwatchSocket(); + + Delegate* const socket_delegate_; + + private: + friend class TransportClientSocketTest; + + void SendInternal(const char* bytes, int len); + +#if defined(OS_WIN) + // ObjectWatcher delegate. + void OnObjectSignaled(HANDLE object) override; + base::win::ObjectWatcher watcher_; + HANDLE socket_event_; +#elif defined(OS_POSIX) + // Called by MessagePumpLibevent when the socket is ready to do I/O. + void OnFileCanReadWithoutBlocking(int fd) override; + void OnFileCanWriteWithoutBlocking(int fd) override; + WaitState wait_state_; + // The socket's libevent wrapper. + base::MessageLoopForIO::FileDescriptorWatcher watcher_; +#endif + + // NOTE: This is for unit test use only! + // Pause/Resume calling Read(). Note that ResumeReads() will also call + // Read() if there is anything to read. + void PauseReads(); + void ResumeReads(); + + const SocketDescriptor socket_; + bool reads_paused_; + bool has_pending_reads_; + + DISALLOW_COPY_AND_ASSIGN(StreamListenSocket); +}; + +// Abstract factory that must be subclassed for each subclass of +// StreamListenSocket. +class StreamListenSocketFactory { + public: + virtual ~StreamListenSocketFactory() {} + + // Returns a new instance of StreamListenSocket or NULL if an error occurred. + virtual scoped_ptr CreateAndListen( + StreamListenSocket::Delegate* delegate) const = 0; +}; + +} // namespace test_server + +} // namespace net + +#endif // NET_TEST_EMBEDDED_TEST_SERVER_STREAM_LISTEN_SOCKET_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 new file mode 100644 index 000000000000..418f34592127 --- /dev/null +++ b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/test/embedded_test_server/tcp_listen_socket.h" + +#if defined(OS_WIN) +// winsock2.h must be included first in order to ensure it is included before +// windows.h. +#include +#elif defined(OS_POSIX) +#include +#include +#include +#include +#include +#include "net/base/net_errors.h" +#endif + +#include "base/logging.h" +#include "base/sys_byteorder.h" +#include "base/threading/platform_thread.h" +#include "build/build_config.h" +#include "net/base/net_util.h" +#include "net/base/winsock_init.h" +#include "net/socket/socket_descriptor.h" + +using std::string; + +namespace net { + +namespace test_server { + +// static +scoped_ptr TCPListenSocket::CreateAndListen( + const string& ip, + uint16 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(); +} + +TCPListenSocket::TCPListenSocket(SocketDescriptor s, + StreamListenSocket::Delegate* del) + : StreamListenSocket(s, del) { +} + +TCPListenSocket::~TCPListenSocket() { +} + +SocketDescriptor TCPListenSocket::CreateAndBind(const string& ip, uint16 port) { + SocketDescriptor s = CreatePlatformSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s != kInvalidSocket) { +#if defined(OS_POSIX) + // Allow rapid reuse. + static const int kOn = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); +#endif + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(ip.c_str()); + addr.sin_port = base::HostToNet16(port); + if (bind(s, reinterpret_cast(&addr), sizeof(addr))) { +#if defined(OS_WIN) + closesocket(s); +#elif defined(OS_POSIX) + close(s); +#endif + LOG(ERROR) << "Could not bind socket to " << ip << ":" << port; + s = kInvalidSocket; + } + } + return s; +} + +SocketDescriptor TCPListenSocket::CreateAndBindAnyPort(const string& ip, + uint16* port) { + SocketDescriptor s = CreateAndBind(ip, 0); + if (s == kInvalidSocket) + return kInvalidSocket; + sockaddr_in addr; + socklen_t addr_size = sizeof(addr); + bool failed = getsockname(s, reinterpret_cast(&addr), + &addr_size) != 0; + if (addr_size != sizeof(addr)) + failed = true; + if (failed) { + LOG(ERROR) << "Could not determine bound port, getsockname() failed"; +#if defined(OS_WIN) + closesocket(s); +#elif defined(OS_POSIX) + close(s); +#endif + return kInvalidSocket; + } + *port = base::NetToHost16(addr.sin_port); + return s; +} + +void TCPListenSocket::Accept() { + SocketDescriptor conn = AcceptSocket(); + if (conn == kInvalidSocket) + return; + scoped_ptr sock(new TCPListenSocket(conn, socket_delegate_)); +#if defined(OS_POSIX) + sock->WatchSocket(WAITING_READ); +#endif + socket_delegate_->DidAccept(this, sock.Pass()); +} + +} // namespace test_server + +} // namespace net 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 new file mode 100644 index 000000000000..12b3fa40745e --- /dev/null +++ b/chromium_src/net/test/embedded_test_server/tcp_listen_socket.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TEST_EMBEDDED_TEST_SERVER_TCP_LISTEN_SOCKET_H_ +#define NET_TEST_EMBEDDED_TEST_SERVER_TCP_LISTEN_SOCKET_H_ + +#include + +#include "base/basictypes.h" +#include "net/base/net_export.h" +#include "net/socket/socket_descriptor.h" +#include "net/test/embedded_test_server/stream_listen_socket.h" + +namespace net { + +namespace test_server { + +// Implements a TCP socket. +class TCPListenSocket : public StreamListenSocket { + public: + ~TCPListenSocket() override; + + // Listen on port for the specified IP address. Use 127.0.0.1 to only + // accept local connections. + static scoped_ptr CreateAndListen( + const std::string& ip, + uint16 port, + StreamListenSocket::Delegate* del); + + protected: + TCPListenSocket(SocketDescriptor s, StreamListenSocket::Delegate* del); + + // Implements StreamListenSocket::Accept. + void Accept() override; + + private: + friend class EmbeddedTestServer; + friend class TCPListenSocketTester; + + // Get raw TCP socket descriptor bound to ip:port. + static SocketDescriptor CreateAndBind(const std::string& ip, uint16 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); + + DISALLOW_COPY_AND_ASSIGN(TCPListenSocket); +}; + +} // namespace test_server + +} // namespace net + +#endif // NET_TEST_EMBEDDED_TEST_SERVER_TCP_LISTEN_SOCKET_H_ diff --git a/common.gypi b/common.gypi index c6f3320c0a5b..7c41c3616dfb 100644 --- a/common.gypi +++ b/common.gypi @@ -1,14 +1,20 @@ { 'includes': [ + 'toolchain.gypi', 'vendor/brightray/brightray.gypi', ], 'variables': { # Required by breakpad. 'os_bsd': 0, + 'chromeos': 0, # Reflects node's config.gypi. 'component%': 'static_library', 'python': 'python', + 'openssl_fips': '', 'openssl_no_asm': 1, + 'node_release_urlbase': 'https://atom.io/download/atom-shell', + 'node_byteorder': '`](../../docs/api/web-view-tag.md) +* [Función `window.open`](../../docs/api/window-open.md) + +Módulos del proceso principal: + +* [app](../../docs/api/app.md) +* [auto-updater](../../docs/api/auto-updater.md) +* [browser-window](../../docs/api/browser-window.md) +* [content-tracing](../../docs/api/content-tracing.md) +* [dialog](../../docs/api/dialog.md) +* [global-shortcut](../../docs/api/global-shortcut.md) +* [ipc (main process)](../../docs/api/ipc-main-process.md) +* [menu](../../docs/api/menu.md) +* [menu-item](../../docs/api/menu-item.md) +* [power-monitor](../../docs/api/power-monitor.md) +* [power-save-blocker](../../docs/api/power-save-blocker.md) +* [protocol](../../docs/api/protocol.md) +* [tray](../../docs/api/tray.md) + +Módulos del renderer (página web): + +* [ipc (renderer)](../../docs/api/ipc-renderer.md) +* [remote](../../docs/api/remote.md) +* [web-frame](../../docs/api/web-frame.md) + +Módulos de ambos procesos: + +* [clipboard](../../docs/api/clipboard.md) +* [crash-reporter](../../docs/api/crash-reporter.md) +* [native-image](../../docs/api/native-image.md) +* [screen](../../docs/api/screen.md) +* [shell](../../docs/api/shell.md) + +## Desarrollo + +* [Guía de estilo](../../docs/development/coding-style.md) +* [Estructura de directorio](../../docs/development/source-code-directory-structure.md) +* [Diferencias técnicas con NW.js (anteriormente conocido como node-webkit)](../../docs/development/atom-shell-vs-node-webkit.md) +* [Sistema de compilación](../../docs/development/build-system-overview.md) +* [Instrucciones de compilación (Mac)](../../docs/development/build-instructions-osx.md) +* [Instrucciones de compilación (Windows)](../../docs/development/build-instructions-windows.md) +* [Instrucciones de compilación (Linux)](../../docs/development/build-instructions-linux.md) +* [Configurando un servidor de símbolos en el depurador](../../docs/development/setting-up-symbol-server.md) diff --git a/docs-translations/es/tutorial/application-distribution.md b/docs-translations/es/tutorial/application-distribution.md new file mode 100644 index 000000000000..644da8c3c87b --- /dev/null +++ b/docs-translations/es/tutorial/application-distribution.md @@ -0,0 +1,114 @@ +# Distribución de aplicaciones + +Para distribuir tu aplicación con Electron, debes nombrar al directorio de tu aplicación +como `app`, y ponerlo bajo el directorio de recursos de Electron (en OSX es `Electron.app/Contents/Resources/`, +en Linux y Windows es `resources/`): + +En OSX: + +```text +electron/Electron.app/Contents/Resources/app/ +├── package.json +├── main.js +└── index.html +``` + +En Windows y Linux: + +```text +electron/resources/app +├── package.json +├── main.js +└── index.html +``` + +Posteriormente ejecutas `Electron.app` (o `electron` en Linux, `electron.exe` en Windows), +y Electron iniciará la aplicación. El directorio `electron` será la distribución que recibirán los usuarios finales. + +## Empaquetando tu aplicación como un archivo + +Además de copiar todos tus archivos fuente para la distribución, también puedes +empaquetar tu aplicación como un archivo [asar](https://github.com/atom/asar) +y de esta forma evitar la exposición del código fuente de tu aplicación a los usuarios. + +Para usar un archivo `asar` en reemplazo de la carpeta `app`, debes renombrar +el archivo a `app.asar`, y ponerlo bajo el directorio de recursos de Electron (como arriba), +Electron intentará leer el archivo y ejecutar la aplicación desde él. + +En OS X: + +```text +electron/Electron.app/Contents/Resources/ +└── app.asar +``` + +En Windows y Linux: + +```text +electron/resources/ +└── app.asar +``` + +Más detalles en [Empaquetamiento de aplicaciones](application-packaging-es.md). + +## Rebranding con binarios descargados + +Luego de empaquetar tu aplicación con Electron, podría ser útil agregar tu marca +antes de realizar la distribución. + +### Windows + +Puedes renombrar `electron.exe` a cualquier nombre que desees, y editar su ícono y otras informaciones +con herramientas como [rcedit](https://github.com/atom/rcedit) o [ResEdit](http://www.resedit.net). + +### OS X + +Puedes renombrar `Electron.app` a cualquier nombre que desees. También debes modificar los campos +`CFBundleDisplayName`, `CFBundleIdentifier` y `CFBundleName` en los siguientes archivos: + +* `Electron.app/Contents/Info.plist` +* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist` + +También puedes renombrar el helper de la aplicación para evitar que aparezca como `Electron Helper` +en el Monitor de Actividades. + +La estructura de una aplicación renombrada sería así: + +``` +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 + +Puedes renombrar el ejectuable `electron` a cualquier nombre que desees. + +## Rebranding desde el código fuente de Electron + +También es posible agregar tu marca a Electron mediante un build personalizado. +Para realizar esto debes modificar el archivo `atom.gyp`. + +### grunt-build-atom-shell + +La modificación del código de Electron para agregar tu marca puede resultar complicada, una tarea Grunt +se ha creado para manejar esto de forma automatizada: + +[grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). + +Esta tarea se encargará de modificar el archivo `.gyp`, compilar el código +y reconstruir los módulos nativos de la aplicación para que coincidan con el nuevo nombre. diff --git a/docs-translations/es/tutorial/application-packaging.md b/docs-translations/es/tutorial/application-packaging.md new file mode 100644 index 000000000000..56698c1aaecb --- /dev/null +++ b/docs-translations/es/tutorial/application-packaging.md @@ -0,0 +1,157 @@ +# Empaquetamiento de aplicaciones + +Para proteger los recursos y el código fuente de tu aplicación, puedes optar por empaquetar +tu aplicación en un paquete [asar][asar]. + +## Generando un archivo `asar` + +Un paquete [asar][asar] es un formato sencillo similar a tar, este formato concatena todos los archivos en uno, +Electron puede leer el contenido sin desempaquetar todos los archivos. + +A continuación, los pasos para empaquetar tu aplicación con `asar`: + +### 1. Instalar asar + +```bash +$ npm install -g asar +``` + +### 2. Empaquetar utilizando `asar pack` + +```bash +$ asar pack your-app app.asar +``` + +## Utilizando los paquetes `asar` + +En Electron existen dos tipos de APIs: las APIs de Node, proveídas por Node.js, +y las APIs Web, proveídas por Chromium. Ambas APIs soportan la lecutra de paquetes `asar`. + +### API Node + +Con parches especiales en Electron, las APIs de Node como `fs.readFile` and `require` +tratan los paquetes `asar` como directorios virtuales, y el contenido es accesible como si se tratara +de archivos normales en el sistema de archivos. + +Por ejemplo, supongamos que tenemos un paquete `example.asar` bajo `/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 +``` + +Leer un archivo de nuestro paquete `asar`: + +```javascript +var fs = require('fs'); +fs.readFileSync('/path/to/example.asar/file.txt'); +``` + +Listar todos los archivos de la raíz: + +```javascript +var fs = require('fs'); +fs.readdirSync('/path/to/example.asar'); +``` + +Utilizar un módulo que se encuentra dentro del archivo: + +```javascript +require('/path/to/example.asar/dir/module.js'); +``` + +También puedes mostrar una página web contenida en un `asar` utilizando `BrowserWindow`. + +```javascript +var BrowserWindow = require('browser-window'); +var win = new BrowserWindow({width: 800, height: 600}); +win.loadUrl('file:///path/to/example.asar/static/index.html'); +``` + +### API Web + +En una págin web, los archivos que se encuentran en el paquete son accesibles a través del protocolo `file:`. +Al igual que la API Node, los paquetes `asar` son tratados como directorios. + +Por ejemplo, para obtener un archivo con `$.get`: + +```html + +``` + +### Utilizando un paquete `asar` como un archivo normal + +En algunas situaciones necesitaremos acceder al paquete `asar` como archivo, por ejemplo, +si necesitaramos verificar la integridad del archivo con un checksum. +Para casos así es posible utilizar el módulo `original-fs`, que provee la API `fs` original: + +```javascript +var originalFs = require('original-fs'); +originalFs.readFileSync('/path/to/example.asar'); +``` + +## Limitaciones de la API Node: + +A pesar de que hemos intentado que los paquetes `asar` funcionen como directorios de la mejor forma posible, +aún existen limitaciones debido a la naturaleza de bajo nivel de la API Node. + +### Los paquetes son de sólo lecutra + +Los paquetes `asar` no pueden ser modificados, por lo cual todas las funciones que modifiquen archivos +no funcionarán. + +## Los directorios del paquete no pueden establecerse como working directories + +A pesar de que los paquetes `asar` son manejados virtualmente como directorios, +estos directorios no existen en el sistema de archivos, por lo cual no es posible establecerlos +como working directory, el uso de la opción `cwd` en algunas APIs podría causar errores. + +### Desempaquetamiento adicional en algunas APIs + +La mayoría de las APIs `fs` pueden leer u obtener información sobre un archivo en un paquete `asar` sin +la necesidad de desempaquetarlo, pero algunas APIs requieren la ruta real. En estos casos Electron extraerá +el archivo a una ruta temporal. Esto agrega un overhead a algunas APIs. + +Las APIs que requieren el desempaquetamiento adicional son: + +* `child_process.execFile` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - Utilizado po `require` en los módulos nativos + +### Información falsa en `fs.stat` + +El objeto `Stats` retornado por `fs.stat` y otras funciones relacionadas, +no es preciso, ya que los archivos del paquete `asar` no existen el sistema de archivos. +La utilización del objeto `Stats` sólo es recomendable para obtener el tamaño del archivo y/o +comprobar el tipo de archivo. + + +## Agregando archivos al paquete `asar` + +Como se menciona arriba, algunas APIs de Node desempaquetarán archivos cuando exista una llamada +que los referencie, además de los problemas de rendimiento que esto podría ocasionar, también +podría accionar alertas falsas en software antivirus. + +Para lidiar con esto, puedes desempaquetar algunos archivos utilizando la opción `--unpack`, +a continuación un ejemplo que excluye las librerías compartidas de los módulos nativos: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +Después de ejecutar este comando, además del archivo `app.asar`, también se creará +un directorio `app.asar.unpacked`, que contendrá los archivos desempaquetados. +Este directorio deberá copiarse junto con el archivo `app.asar` a la hora de distribuir la aplicación. + +[asar]: https://github.com/atom/asar diff --git a/docs-translations/es/tutorial/debugging-main-process.md b/docs-translations/es/tutorial/debugging-main-process.md new file mode 100644 index 000000000000..1a764036e37a --- /dev/null +++ b/docs-translations/es/tutorial/debugging-main-process.md @@ -0,0 +1,45 @@ +# Depurando el proceso principal + +Los devtools sólo pueden depurar las páginas web (el código del proceso renderer). +Para depurar el código del proceso principal, Electron provee dos opciones para la línea de comandos: `--debug` y `--debug-brk`. + +## Opciones para la línea de comandos + +### `--debug=[port]` + +Esta opción escuchará mensajes del protocolo de depuración V8 en `port`, por defecto `port` es `5858`. + +### `--debug-brk=[port]` + +Similar a `--debug` pero realiza una pausa en la primera línea del script. + +## Utilizando node-inspector para depuración + +__Nota:__ Electron utiliza node v0.11.13, esta versión aún no funciona bien con node-inspector, +el proceso principal podría fallar al inspeccionar el objeto `process`. + +### 1. Iniciar [node-inspector][node-inspector] + +```bash +$ node-inspector +``` + +### 2. Activar el modo de depuración en Electron + +Es posible iniciar Electron con la opción de depuración: + +```bash +$ electron --debug=5858 your/app +``` + +o, pausar el script en la primera línea: + +```bash +$ electron --debug-brk=5858 your/app +``` + +### 3. Cargar la interfaz del depurador + +Abre http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 en Chrome. + +[node-inspector]: https://github.com/node-inspector/node-inspector diff --git a/docs-translations/es/tutorial/desktop-environment-integration.md b/docs-translations/es/tutorial/desktop-environment-integration.md new file mode 100644 index 000000000000..f19f36326f3e --- /dev/null +++ b/docs-translations/es/tutorial/desktop-environment-integration.md @@ -0,0 +1,171 @@ +# Integración con el entorno de escritorio + +Los sistemas operativos proveen diferentes características para integrar aplicaciones +en sus entornos de escritorio. Por ejemplo, en Windows, las aplicaciones pueden agregar accesos directos +en la JumpList de la barra de tareas, y en Mac, las aplicaciones pueden agregar un menú personalizado en el dock. + +Esta guía explica cómo integrar tu aplicación en esos entornos de escritorio a través de las APIs de Electron. + +## Documentos recientes (Windows y OS X) + +Windows y OS X proveen un acceso sencillo a la lista de documentos recientes. + +__JumpList:__ + +![JumpList, Archivos recientes](http://i.msdn.microsoft.com/dynimg/IC420538.png) + +__Menú Dock:__ + + + +Para agregar un archivo a la lista de documentos recientes, puedes utilizar: +[app.addRecentDocument][addrecentdocument] API: + +```javascript +var app = require('app'); +app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); +``` + +También puedes utilizar [app.clearRecentDocuments](clearrecentdocuments) para vaciar la lista de documentos recientes: + +```javascript +app.clearRecentDocuments(); +``` + +### Notas sobre Windows + +Para activar esta característica en Windows, tu aplicación debe registrar un handler +para el tipo de archivo que quieres utilizar, de lo contrario el archivo no aparecerá +en la JumpList, aún después de agregarlo. Puedes encontrar más información sobre el proceso de +registrar tu aplicación en [Application Registration][app-registration]. + +Cuando un usuario haga click en un archivo de la JumpList, una nueva instancia de tu aplicación +se iniciará, la ruta del archivo se agregará como un argumento de la línea de comandos. + +### Notas sobre OS X + +Cuando un archivo es solicitado desde el menú de documentos recientes, el evento `open-file` +del módulo `app` será emitido. + +## Menú dock personalizado (OS X) + +OS X permite a los desarrolladores definir un menú personalizado para el dock, +el cual usualmente contiene algunos accesos directos a las características más comunes +de tu aplicación: + +__Menú dock de Terminal.app:__ + + + +Para establecer tu menú dock, puedes utilizar la API `app.dock.setMenu`, la cual sólo está disponible para OSX: + +```javascript +var app = require('app'); +var Menu = require('menu'); +var dockMenu = Menu.buildFromTemplate([ + { label: 'New Window', click: function() { console.log('New Window'); } }, + { label: 'New Window with Settings', submenu: [ + { label: 'Basic' }, + { label: 'Pro'} + ]}, + { label: 'New Command...'} +]); +app.dock.setMenu(dockMenu); +``` + +## Tareas de usuario (Windows) + +En Windows puedes especificar acciones personalizadas en la categoría `Tasks` del JumpList, +tal como menciona MSDN: + + +> Las aplicaciones definen tareas basadas en las características del programa +> y las acciones clave que se esperan de un usuario. Las tareas deben ser +> libres de contexto, es decir, la aplicación no debe encontrarse en ejecución +> para que estas acciones funcionen. También deberían ser las acciones estadísticamente +> más comunes que un usuario normal realizaría en tu aplicación, como por ejemplo, +> redactar un mensaje de correo electrónico, crear un documento en el procesador de textos, +> ejecutar una aplicación en cierto modo, o ejecutar alguno de sus subcomandos. Una aplicación +> no debería popular el menú con características avanzadas que el usuario estándar no necesita +> ni con acciones que sólo se realizan una vez, como por ejemplo, el registro. No utilices +> las tareas para mostrar elementos promocionales como actualizaciones u ofertas especiales. +> +> Es recomendable que la lista de tareas sea estática. Debe mantenerse a pesar +> de los cambios de estado de la aplicación. Aunque exista la posibilidad de variar +> el contenido de la lista dinámicamente, debes considerar que podría ser confuso +> para un usuario que no espera que el destino de la lista cambie. + +__Tareas de Internet Explorer:__ + +![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) + +A diferencia del menú dock en OS X, el cual es un menú real, las tareas de usuario en Windows +funcionan como accesos directos de aplicación, que al ser clickeados, lanzan el programa +con argumentos específicos. + +Para establecer las tareas de usuario en tu aplicación, puedes utilizar: +[app.setUserTasks][setusertaskstasks] API: + +```javascript +var app = require('app'); +app.setUserTasks([ + { + program: process.execPath, + arguments: '--new-window', + iconPath: process.execPath, + iconIndex: 0, + title: 'New Window', + description: 'Create a new window' + } +]); +``` + +Para purgar la lista de tareas, puedes llamar a `app.setUserTasks` con un array vacío: + +```javascript +app.setUserTasks([]); +``` + +Las tareas de usuario aún serán visibles después de cerrar tu aplicación, por lo cual +el ícono y la ruta del programa deben existir hasta que la aplicación sea desinstalada. + +## Accesos directos en el lanzador Unity (Linux) + +En Unity, es posible agregar algunas entradas personalizadas, modificando el archivo `.desktop`, +ver [Adding shortcuts to a launcher][unity-launcher]. + +__Accesos directos de Audacious:__ + +![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) + +## Barra de progreso en la barra de tareas (Windows y Unity) + +En Windows, un botón en la barra de tareas puede utilizarse para mostrar una barra de progreso. Esto permite +que una ventana muestre la información de progreso al usuario, sin que el usuario tenga la ventana de la aplicación activa. + +El entorno de escritorio Unity también posee una característica similar que permite mostrar una barra de progreso en el lanzador. + +__Barra de progreso en un botón de la barra de herramientas:__ + +![Taskbar Progress Bar](https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png) + +__Barra de progreso en el lanzador Unity:__ + +![Unity Launcher](https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png) + +Para establecer la barra de progreso de una ventana, puedes utilizar +[BrowserWindow.setProgressBar][setprogressbar] API: + +```javascript +var window = new BrowserWindow({...}); +window.setProgressBar(0.5); +``` + +[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 +[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 diff --git a/docs-translations/es/tutorial/devtools-extension.md b/docs-translations/es/tutorial/devtools-extension.md new file mode 100644 index 000000000000..f54c3e0eaa83 --- /dev/null +++ b/docs-translations/es/tutorial/devtools-extension.md @@ -0,0 +1,49 @@ +# Extensión DevTools + +Para facilitar la depuración, Electron provee un soporte básico para la extensión +[Chrome DevTools Extension][devtools-extension]. + +Para la mayoría de las extensiones devtools, simplemente puedes descargar el código fuente +y utilizar `BrowserWindow.addDevToolsExtension` para cargarlas, las extensiones cargadas +serán recordadas para que no sea necesario llamar a la función cada vez que creas una ventana. + +Por ejemplo, para usar la extensión [React DevTools Extension](https://github.com/facebook/react-devtools), primero debes descargar el código fuente: + +```bash +$ cd /some-directory +$ git clone --recursive https://github.com/facebook/react-devtools.git +``` + +Luego cargas la aplicación en Electron, abriendo devtools en cualquier ventana, +y ejecutando este código en la consola devtools: + +```javascript +require('remote').require('browser-window').addDevToolsExtension('/some-directory/react-devtools'); +``` + +Para remover una extensión, puedes utilizar `BrowserWindow.removeDevToolsExtension` +especificando el nombre, y esta ya no se cargará la siguiente vez que abras devtools: + +```javascript +require('remote').require('browser-window').removeDevToolsExtension('React Developer Tools'); +``` + +## Formato de las extensiones devtools + +Idealmente todas las extensiones devtools escritas para Chrome pueden ser cargadas por Electron, +pero para ello deben estar en un directorio plano, las extensiones empaquetadas como `crx` +no pueden ser cargadas por Chrome a no ser que halles una forma de extraerlas a un directorio. + +## Páginas en segundo plano (background) + +Electron no soporta la característica de páginas en segundo plano de las extensiones de Chrome, +las extensiones que utilizan esta característica podrían no funcionar. + +## APIs `chrome.*` + +Algunas extensiones utilizan las APIs `chrome.*`, hemos realizado un esfuerzo +para implementar esas APIs en Electron, sin embargo no han sido implementadas en su totalidad. + +Dado que no todas las funciones `chrome.*` han sido implementadas, si la extensión devtools está utilizando otras APIs más allá de `chrome.devtools.*`, es muy probable que no funcione. Puedes reportar fallos en el issue tracker para que podamos agregar soporte a esas APIs. + +[devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs-translations/es/tutorial/online-offline-events.md b/docs-translations/es/tutorial/online-offline-events.md new file mode 100644 index 000000000000..0e43f9b16109 --- /dev/null +++ b/docs-translations/es/tutorial/online-offline-events.md @@ -0,0 +1,80 @@ +# Detección del evento en línea/fuera de línea + +La detección de estos eventos puede ser implementada en el proceso renderer utilizando las APIs HTML5 estándar, +como en este ejemplo: + +_main.js_ + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` + +Existen casos en donde necesitas responder a estos eventos desde el proceso principal. +El proceso principal no posee un objeto `navigator`, por lo tanto no puede detectar estos eventos directamente. +Es posible reenviar el evento al proceso principal mediante la utilidad de intercomunicación entre procesos (ipc): + +_main.js_ + +```javascript +var app = require('app'); +var ipc = require('ipc'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); + +ipc.on('online-status-changed', function(event, status) { + console.log(status); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` diff --git a/docs-translations/es/tutorial/quick-start.md b/docs-translations/es/tutorial/quick-start.md new file mode 100644 index 000000000000..5c3095deb3ef --- /dev/null +++ b/docs-translations/es/tutorial/quick-start.md @@ -0,0 +1,154 @@ +# Intro + +## Introducción + +Electron permite la creación de aplicaciones de escritorio utilizando JavaScript puro, a través de un runtime con APIs nativas. Puedes verlo como una variante de io.js, enfocado en aplicaciones de escritorio, en vez de servidores web. + +Esto no significa que Electron sea un binding de librerías GUI para JavaScript. +Electron utiliza páginas web como su GUI, por lo cual puedes verlo como un navegador Chromium mínimo, +controlado por JavaScript. + +### El proceso principal (main process) + +En Electron, el proceso que ejecuta el script `main` del `package.json` se llama __el proceso principal__. +El script que corre en el proceso principal puede crear páginas para mostrar la GUI. + +### El proceso renderer (renderer process) + +Dado que Electron utiliza Chromium para mostrar las páginas web, +también es utilizada la arquitectura multiproceso de Chromium. +Cada página web en Electron se ejecuta en su propio proceso, +el cual es llamado __el proceso renderer__. + +En los navegadores normales, las páginas web usualmente se ejecutan en un entorno +sandbox y no tienen acceso a los recursos nativos. Los usuarios de Electron tienen el poder +de utilizar las APIs de io.js en las páginas web, permitiendo interacciones de bajo nivel con el sistema operativo. + +### Diferencias entre el proceso principal y el proceso renderer + +El proceso principal crea páginas web mediante instancias de `BrowserWindow`. Cada instancia de `BrowserWindow` ejecuta su propia página web y su propio proceso renderer. +Cuando una instancia de `BrowserWindow` es destruida, también su proceso renderer correspondiente acaba. + +El proceso principal gestiona las páginas web y sus correspondientes procesos renderer. +Cada proceso renderer es aislado y sólo considera relevante la página web que corre en él. + +En las páginas web, no está permitido llamar a APIs relacionadas a la GUI nativa +porque la gestión de los recursos GUI nativos es peligrosa, y tiende a que ocurran leaks de memoria. +Si deseas realizar operaciones GUI en una página web, el proceso renderer de la página web debe comunicarse +con el proceso principal, y solicitar a este que realice esas operaciones. + +En Electron, hemos proveído el módulo [ipc](../api/ipc-renderer.md) para la comunicación +entre el proceso principal y el proceso renderer. Y también hay un módulo [remote](../api/remote.md) +para comunicación al estilo RPC. + +## Escribe tu primera aplicación Electron + +Generalmente, una aplicación Electron tendrá la siguiente estructura: + +```text +your-app/ +├── package.json +├── main.js +└── index.html +``` + +El formato de `package.json` es exactamente el mismo que cualquier módulo Node, +y el script especificado en el campo `main` será el script de arranque de tu aplicación, +a ser ejecutado por el proceso principal. Un ejemplo de `package.json` podría verse así: + +```json +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +El `main.js` debería crear las ventanas y gestionar los eventos del sistema, un ejemplo típico sería: +example being: + +```javascript +var app = require('app'); // Módulo para controlar el ciclo de vida de la aplicación. +var BrowserWindow = require('browser-window'); // Módulo para crear uan ventana de navegador. + +// Reportar crashes a nuestro servidor. +require('crash-reporter').start(); + +// Mantener una referencia global al objeto window, si no lo haces, esta ventana +// se cerrará automáticamente cuando el objeto JavaScript sea recolectado (garbage collected): +var mainWindow = null; + +// Salir de todas las ventanas cuando se cierren. +app.on('window-all-closed', function() { + // En OS X es común que las aplicaciones y su barra de menú + // se mantengan activas hasta que el usuario cierre la aplicación + // explícitamente utilizando Cmd + Q + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// Este método será llamado cuando Electron haya finalizado la inicialización +// y esté listo para crear ventanas de navegador. +app.on('ready', function() { + // Crear la ventana. + mainWindow = new BrowserWindow({width: 800, height: 600}); + + // cargar el index.html de nuestra aplicación. + mainWindow.loadUrl('file://' + __dirname + '/index.html'); + + // Desplegar devtools. + mainWindow.openDevTools(); + + // Evento emitido cuando se cierra la ventana. + mainWindow.on('closed', function() { + // Eliminar la referencia del objeto window. + // En el caso de soportar multiples ventanas, es usual almacenar + // los objetos window en un array, este es el momento en el que debes eliminar el elemento correspondiente. + mainWindow = null; + }); +}); +``` + +Finalmente el `index.html` es la página web que mostraremos: + +```html + + + + Hello World! + + +

Hello World!

+ We are using io.js + and Electron . + + +``` + +## Ejecutar la aplicación + +Cuando termines de escribir tu aplicación, puedes distribuirla +siguiendo la [guía de distribución](./application-distribution-es.md) +y luego ejecutar la aplicación empaquetada. También puedes utilizar el binario de Electron +para ejecutar tu aplicación de forma directa. + +En Windows: + +```bash +$ .\electron\electron.exe your-app\ +``` + +En Linux: + +```bash +$ ./electron/electron your-app/ +``` + +En OS X: + +```bash +$ ./Electron.app/Contents/MacOS/Electron your-app/ +``` + +`Electron.app` es parte del paquete de release de Electron, puedes descargarlo [aquí](https://github.com/atom/electron/releases). diff --git a/docs-translations/es/tutorial/using-native-node-modules.md b/docs-translations/es/tutorial/using-native-node-modules.md new file mode 100644 index 000000000000..78409049ad93 --- /dev/null +++ b/docs-translations/es/tutorial/using-native-node-modules.md @@ -0,0 +1,57 @@ +# Utilizando módulos Node nativos + +Los módulos Node nativos son soportados por Electron, pero dado que Electron +está utilizando una versión distinta de V8, debes especificar manualmente la +ubicación de las cabeceras de Electron a la hora de compilar módulos nativos. + +## Compatibilidad de módulos nativos + +A partir de Node v0.11.x han habido cambios vitales en la API de V8. +Es de esperar que los módulos escritos para Node v0.10.x no funcionen con Node v0.11.x. +Electron utiliza Node v.0.11.13 internamente, y por este motivo tiene el mismo problema. + +Para resolver esto, debes usar módulos que soporten Node v0.11.x, +[muchos módulos](https://www.npmjs.org/browse/depended/nan) soportan ambas versiones. +En el caso de los módulos antiguos que sólo soportan Node v0.10.x, debes usar el módulo +[nan](https://github.com/rvagg/nan) para portarlos a v0.11.x. + +## ¿Cómo instalar módulos nativos? + +### La forma fácil + +La forma más sencilla de recompilar módulos nativos es a través del paquete +[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild), +el cual abstrae y maneja los pasos de descargar las cabeceras y compilar los módulos nativos: + +```sh +npm install --save-dev electron-rebuild + +# Ejecuta esto cada vez que ejecutes npm install +./node_modules/.bin/electron-rebuild +``` + +### La forma node-gyp + +Para compilar módulos Node con las cabeceras de Electron, debes indicar a `node-gyp` +desde dónde descargar las cabeceras y cuál versión usar: + +```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 +``` + +Los cambios en `HOME=~/.electron-gyp` fueron para especificar la ruta de las cabeceras. +La opción `--target=0.29.1` es la versión de Electron. La opción `--dist-url=...` especifica +dónde descargar las cabeceras. `--arch=x64` indica que el módulo será compilado para un sistema de 64bit. + +### La forma npm + +También puedes usar `npm` para instalar módulos, los pasos son exactamente igual a otros módulos Node, +con la excepción de que necesitas establecer algunas variables de entorno primero: + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.29.1 +export npm_config_arch=x64 +HOME=~/.electron-gyp npm install module-name +``` diff --git a/docs-translations/es/tutorial/using-pepper-flash-plugin.md b/docs-translations/es/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 000000000000..fbb2b6f83aa0 --- /dev/null +++ b/docs-translations/es/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,58 @@ +# Utilizando el plugin Pepper Flash + +El plugin Pepper Flash es soportado ahora. Para utilizar pepper flash en Electron, debes especificar la ubicación del plugin manualmente y activarlo en tu aplicación. + +## Preparar una copia del plugin Flash + +En OSX y Linux, el detalle del plugin puede encontrarse accediendo a `chrome://plugins` en el navegador. Su ubicación y versión son útiles para el soporte. También puedes copiarlo a otro lugar. + +## Agrega la opción a Electron + +Puedes agregar la opción `--ppapi-flash-path` y `ppapi-flash-version` o utilizar el método `app.commandLine.appendSwitch` antes del evento ready de la aplicación. +También puedes agregar la opción `plugins` de `browser-window`. Por ejemplo, + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +// Report crashes to our server. +require('crash-reporter').start(); + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the javascript object is GCed. +var mainWindow = null; + +// Quit when all windows are closed. +app.on('window-all-closed', function() { + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// Specify flash path. +// On Windows, it might be /path/to/pepflashplayer.dll +// On Mac, /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 +}); +``` + +## Activar el plugin flash en una etiqueta `` +Agrega el atributo `plugins`. +```html + +``` diff --git a/docs-translations/es/tutorial/using-selenium-and-webdriver.md b/docs-translations/es/tutorial/using-selenium-and-webdriver.md new file mode 100644 index 000000000000..7d9989057056 --- /dev/null +++ b/docs-translations/es/tutorial/using-selenium-and-webdriver.md @@ -0,0 +1,72 @@ +# Utilizando Selenium y WebDriver + +De [ChromeDriver - WebDriver for Chrome][chrome-driver]: + +> WebDriver es una herramienta de código abierto para automatizar el testing de aplicaciones web +> en varios navegadores. WebDriver provee funciones de navegación, entrada de usuario, +> ejecución de JavaScript, y más. ChromeDriver es un servidor standalone que implementa +> el protocolo de WebDriver para Chromium. Se encuentra en desarrollo por los miembros de +> Chromium y WebDriver. + +En la página de [lanzamientos](https://github.com/atom/electron/releases) de Electron encontrarás paquetes de `chromedriver`. + +## Ajustando parámetros con WebDriverJs + +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) provee +un paquete Node para realizar testing con web driver, lo usaremos como ejemplo. + +### 1. Inicia chrome driver + +Primero necesitas descargar el binario `chromedriver` y ejecutarlo: + +```bash +$ ./chromedriver +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +Recuerda el puerto `9515`, lo utilizaremos después. + +### 2. Instala WebDriverJS + +```bash +$ npm install selenium-webdriver +``` + +### 3. Conecta chrome driver + +El uso de `selenium-webdriver` junto con Electron es básicamente el mismo que el original, +excepto que necesitas especificar manualmente cómo se conectará el chrome driver +y dónde encontrará el binario de Electron: + +```javascript +var webdriver = require('selenium-webdriver'); + +var driver = new webdriver.Builder() + // El puerto "9515" es que abre chrome driver. + .usingServer('http://localhost:9515') + .withCapabilities({chromeOptions: { + // Aquí especificamos la ruta a Electron + 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(); +``` + +## Workflow + +Para probar tu aplicación sin recompilar Electron, simplemente [copia](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) las fuentes de tu aplicación en el directorio de recursos de Electron. + +[chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ + + diff --git a/docs-translations/jp/quick-start.md b/docs-translations/jp/quick-start.md new file mode 100644 index 000000000000..aa26a8a55ab0 --- /dev/null +++ b/docs-translations/jp/quick-start.md @@ -0,0 +1,131 @@ +# クイックスタート + +## 導入 + +ElectronではリッチなネイティブAPIを持ったランタイムを提供することによってピュアなJavaScriptでデスクトップアプリケーションをつくることができます。ウェブサーバーの代わりにデスクトップアプリケーションに焦点をあてたio.jsランタイムであるといえばわかりやすいかもしれません。 + +ElectronはJavaScriptをGUIライブラリにバインディングしません。その代わりに、ElectronはウェブページをGUIとして使用します。なのでElectronはJavaScriptによってコントロールされる最小のChromiumブラウザでもあるともいえます。 + +### メインプロセス + +Electronでは、`package.json` の `main`で実行されるプロセスを __メインプロセス__ と呼びます。メインスクリプトではGUIにウェブページを表示することができるプロセスを実行します。 + +### レンダラープロセス + +Electronはウェブページを表示させるためにChromiumを使用しているので、Chromiumのマルチプロセスアーキテクチャが使用されることになります。Electronで実行されるウェブページはそれぞれ自身のプロセスで実行されます。それを __レンダラープロセス__ と呼びます。 + +通常、ブラウザのウェブページはサンドボックス環境で実行されネイティブなリソースへのアクセスができません。Electronではウェブページからio.jsのAPIを使って、ネイティブリソースへの権限が与えられます。そのおかげでウェブページの中からJavaScriptを使って低レベルなオペレーティングシステムとのインタラクションが可能になります。 + +### メインプロセスとレンダラープロセスの違い + +メインプロセスは `BrowserWindow` インスタンスを作ることによってウェブページをつくります。それぞれの `BrowserWindow` インスタンスはそれ自身の レンダラープロセス上でウェブページを実行します。`BrowserWindow` インスタンスが破棄されると、対応するレンダラープロセスも終了されます。 + +メインプロセスはすべてのウェブページとそれに対応するレンダラープロセスを管理しています。それぞれのレンダラープロセスは分離しているのでウェブページで実行されていることだけを気に留めておいてください。 + +ウェブページでは、GUI関連のAPIを呼ぶことはできません。なぜならば、ウェブページで管理しているネイティブのGUIリソースは非常に危険で簡単にリークしてしまうからです。もしウェブページ内でGUIを操作したい場合には、メインプロセスと通信をする必要があります。 + +Electronでは、メインプロセスとレンダラープロセスとのコミュニケーションをするために[ipc](../api/ipc-renderer.md)モジュールを提供しています。またそれと、RPC形式の通信を行う[remote](../api/remote.md)モジュールもあります。 + +## Electronアプリを作成する + +一般的に Electronアプリの構成は次のようになります: + +```text +your-app/ +├── package.json +├── main.js +└── index.html +``` + +`package.json`の形式はNodeモジュールとまったく同じです。 `main` フィールドでアプリを起動するためのスクリプトを特定し、メインプロセスで実行します。 `package.json`の例は次のようになります: + +```json +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +`main.js` ではウィンドウを作成してシステムイベントを管理します。典型的な例は次のようになります: + +```javascript +var app = require('app'); // Module to control application life. +var BrowserWindow = require('browser-window'); // Module to create native browser window. + +// Report crashes to our server. +require('crash-reporter').start(); + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the javascript object is GCed. +var mainWindow = null; + +// Quit when all windows are closed. +app.on('window-all-closed', function() { + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// This method will be called when Electron has done everything +// initialization and ready for creating browser windows. +app.on('ready', function() { + // Create the browser window. + mainWindow = new BrowserWindow({width: 800, height: 600}); + + // and load the index.html of the app. + mainWindow.loadUrl('file://' + __dirname + '/index.html'); + + // Open the devtools. + mainWindow.openDevTools(); + + // Emitted when the window is closed. + mainWindow.on('closed', function() { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null; + }); +}); +``` + +最後に表示するウェブページ`index.html`は次のようになります: + + +```html + + + + Hello World! + + +

Hello World!

+ We are using io.js + and Electron . + + +``` + +## アプリを実行する + +アプリケーションを作り終えたら、[Application distribution](./application-distribution.md)ガイドにしたがってディストリビューションを作成します、そしてパッケージされたアプリケーションとして配布することが可能です。またダウンロードしたElectronのバイナリをアプリケーション・ディレクトリを実行するために利用することもできます。 + +Windowsの場合: + +```bash +$ .\electron\electron.exe your-app\ +``` + +Linuxの場合: + +```bash +$ ./electron/electron your-app/ +``` + +OS Xの場合: + +```bash +$ ./Electron.app/Contents/MacOS/Electron your-app/ +``` + +`Electron.app` はElectronのリリースパッケージに含まれており、[ここ](https://github.com/atom/electron/releases) からダウンロードできます。 diff --git a/docs-translations/ko/README.md b/docs-translations/ko/README.md new file mode 100644 index 000000000000..2fb242fae906 --- /dev/null +++ b/docs-translations/ko/README.md @@ -0,0 +1,70 @@ +## 개발 가이드 + +* [어플리케이션 배포](tutorial/application-distribution.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) +* [Pepper 플래시 플러그인 사용하기](tutorial/using-pepper-flash-plugin.md) + +## 튜토리얼 + +* [시작하기](tutorial/quick-start.md) +* [데스크톱 환경 통합](tutorial/desktop-environment-integration.md) +* [온라인/오프라인 이벤트 감지](tutorial/online-offline-events.md) + +## API 레퍼런스 + +* [개요](api/synopsis.md) +* [Process 객체](api/process.md) +* [크롬 Command Line 스위치 지원](api/chrome-command-line-switches.md) + +### 커스텀 DOM elements: + +* [`File` 객체](api/file-object.md) +* [`` 태그](api/web-view-tag.md) +* [`window.open` 함수](api/window-open.md) + +### 메인 프로세스를 위한 모듈들: + +* [app (0% 번역됨)](api/app.md) +* [auto-updater](api/auto-updater.md) +* [browser-window (0% 번역됨)](api/browser-window.md) +* [content-tracing](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) +* [protocol](api/protocol.md) +* [session](api/session.md) +* [web-contents (0% 번역됨)](api/web-contents.md) +* [tray](api/tray.md) + +### 랜더러 프로세스를 위한 모듈들 (웹 페이지): + +* [ipc (renderer)](api/ipc-renderer.md) +* [remote](api/remote.md) +* [web-frame](api/web-frame.md) + +### 두 프로세스에서 모두 사용 가능한 모듈들: + +* [clipboard](api/clipboard.md) +* [crash-reporter](api/crash-reporter.md) +* [native-image](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.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-osx.md) +* [빌드 설명서 (Windows)](development/build-instructions-windows.md) +* [빌드 설명서 (Linux)](development/build-instructions-linux.md) +* [디버거에서 디버그 심볼 서버 설정](development/setting-up-symbol-server.md) diff --git a/docs-translations/ko/api/accelerator.md b/docs-translations/ko/api/accelerator.md new file mode 100644 index 000000000000..9e72543a4cc6 --- /dev/null +++ b/docs-translations/ko/api/accelerator.md @@ -0,0 +1,46 @@ +# Accelerator + +Accelerator는 키보드 단축키를 표현하는 문자열입니다, 여러 혼합키와 키코드를 `+` 문자를 +이용하여 결합할 수 있습니다. + +예제: + +* `Command+A` +* `Ctrl+Shift+Z` + +## 플랫폼에 관련하여 주의할 점 + +Linux와 Windows에서는 `Command`키가 없으므로 작동하지 않습니다. 대신에 `CommandOrControl`을 +사용하면 OS X의 `Command`와 Linux, Windows의 `Control` 모두 지원할 수 있습니다. + +`Super`키는 Windows와 Linux 에서는 `윈도우`키를, OS X에서는 `Cmd`키로 맵핑됩니다. + +## 사용 가능한 혼합키 + +* `Command` (단축어 `Cmd`) +* `Control` (단축어 `Ctrl`) +* `CommandOrControl` (단축어 `CmdOrCtrl`) +* `Alt` +* `Shift` +* `Super` + +## 사용 가능한 전체 키코드 + +* `0` 부터 `9` 까지 +* `A` 부터 `Z` 까지 +* `F1` 부터 `F24` 까지 +* `~`, `!`, `@`, `#`, `$`, etc 와 같은 구두점 기호들 +* `Plus` +* `Space` +* `Backspace` +* `Delete` +* `Insert` +* `Return` (또는 `Enter`) +* `Up`, `Down`, `Left` 와 `Right` +* `Home` 그리고 `End` +* `PageUp` 그리고 `PageDown` +* `Escape` (단축어 `Esc`) +* `VolumeUp`, `VolumeDown` 그리고 `VolumeMute` +* `MediaNextTrack`, `MediaPreviousTrack`, `MediaStop` 그리고 `MediaPlayPause` + +__키코드는 `단축어`로도 사용할 수 있습니다__ diff --git a/docs-translations/ko/api/auto-updater.md b/docs-translations/ko/api/auto-updater.md new file mode 100644 index 000000000000..23f881c32ba8 --- /dev/null +++ b/docs-translations/ko/api/auto-updater.md @@ -0,0 +1,131 @@ +# autoUpdater + +**이 모듈은 현재 OS X에서만 사용할 수 있습니다.** + +Windows 어플리케이션 인스톨러를 생성하려면 [atom/grunt-electron-installer](https://github.com/atom/grunt-electron-installer)를 참고하세요. + +`auto-updater` 모듈은 [Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac) 프레임워크의 간단한 Wrapper입니다. + +Squirrel.Mac은 업데이트 설치를 위해 `.app` 폴더에 +[codesign](https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/codesign.1.html) +툴을 사용한 서명을 요구합니다. + +## Squirrel + +Squirrel은 어플리케이션이 **안전하고 투명한 업데이트**를 제공할 수 있도록 하는데 초점이 맞춰진 OS X 프레임워크입니다. + +Squirrel은 사용자에게 어플리케이션의 업데이트를 알릴 필요 없이 서버가 지시하는 버전을 받아온 후 자동으로 업데이트합니다. +이 기능을 사용하면 Squirrel을 통해 클라이언트의 어플리케이션을 지능적으로 업데이트 할 수 있습니다. + +또한 요청시 커스텀 헤더 또는 요청 본문에 인증 정보를 포함시킬 수 있습니다. +서버에선 이러한 요청을 분류 처리하여 적당한 업데이트를 제공할 수 있습니다. + +Squirrel JSON 업데이트 요청시 처리는 반드시 어떤 업데이트가 필요한지 요청의 기준에 맞춰 동적으로 생성되어야 합니다. +Squirrel은 사용해야 하는 업데이트 선택하는 과정을 서버에 의존합니다. [서버 지원](#server-support)을 참고하세요. + +Squirrel의 인스톨러는 오류에 관대하게 설계되었습니다. 그리고 업데이트가 유효한지 확인합니다. + +## 업데이트 요청 + +Squirrel은 업데이트 확인을 위해 클라이언트 어플리케이션의 요청은 무시합니다. +Squirrel은 응답을 분석해야 할 책임이 있기 때문에 `Accept: application/json`이 요청 헤더에 추가됩니다. + +업데이트 응답과 본문 포맷에 대한 요구 사항은 [Server Support](#server-support)를 참고하세요. + +업데이트 요청에는 서버가 해당 어플리케이션이 어떤 버전을 사용해야 하는지 판단하기 위해 *반드시* 버전 식별자를 포함시켜야 합니다. +추가로 OS 버전, 사용자 이름 같은 다른 식별 기준을 포함하여 서버에서 적합한 어플리케이션을 제공할 수 있도록 할 수 있습니다. + +버전 식별자와 다른 기준을 특정하는 업데이트 요청 폼을 서버로 전달하기 위한 공통적인 방법으로 쿼리 인자를 사용하는 방법이 있습니다: + +```javascript +// In the main process +var app = require('app'); +var autoUpdater = require('auto-updater'); +autoUpdater.setFeedUrl('http://mycompany.com/myapp/latest?version=' + app.getVersion()); +``` + +## 서버 지원 + +업데이트를 제공하는 서버는 반드시 클라이언트로부터 받은 [Update Request](#update-requests)를 기반으로 업데이트를 처리할 수 있어야 합니다. + +만약 업데이트 요청이 들어오면 서버는 반드시 [200 OK](http://tools.ietf.org/html/rfc2616#section-10.2.1) 상태 코드를 포함한 +[업데이트 JSON](#update-json-format)을 본문으로 보내야 합니다. +이 응답을 받으면 Squirrel은 이 업데이트를 다운로드할 것입니다. 참고로 현재 설치된 버전과 서버에서 받아온 새로운 버전이 같아도 상관하지 않고 무조건 받습니다. +업데이트시 버전 중복을 피하려면 서버에서 클라이언트 업데이트 요청에 대해 통보하지 않으면 됩니다. + +만약 따로 업데이트가 없다면 [204 No Content](http://tools.ietf.org/html/rfc2616#section-10.2.5) 상태 코드를 반환해야 합니다. +Squirrel은 지정한 시간이 지난 후 다시 업데이트를 확인합니다. + +## JSON 포맷 업데이트 + +업데이트가 사용 가능한 경우 Squirrel은 다음과 같은 구조의 json 데이터를 응답으로 받습니다: + +```json +{ + "url": "http://mycompany.com/myapp/releases/myrelease", + "name": "My Release Name", + "notes": "Theses are some release notes innit", + "pub_date": "2013-09-18T12:29:53+01:00" +} +``` + +응답 json 데이터에서 "url" 키는 필수적으로 포함해야 하고 다른 키들은 옵션입니다. + +Squirrel은 "url"로 `Accept: application/zip` 헤더와 함께 업데이트 zip 파일을 요청합니다. +향후 업데이트 포맷에 대해 서버에서 적절한 포맷을 반환할 수 있도록 MIME 타입을 `Accept` 헤더에 담아 요청합니다. + +`pub_date`은 ISO 8601 표준에 따라 포맷된 날짜입니다. + +## 업데이트 서버 구현 + +[Nuts](https://github.com/GitbookIO/nuts)는 위에서 설명한 업데이트 서버의 오픈 소스 구현입니다. +이 구현은 Github 릴리즈와 완벽하게 통합되어 있습니다. Nuts는 `Squirrel.Mac`과 `Squirrel.Windows`를 지원하고 다운로드와 업데이트를 관리합니다. +이 구현을 사용하면 cross-platform 지원을 신경 쓸 필요가 없습니다. + +## Events + +`autoUpdater` 객체는 다음과 같은 이벤트를 발생시킵니다: + +### Event: 'error' + +* `event` Event +* `message` String + +업데이트시 에러가 나면 발생하는 이벤트입니다. + +### Event: 'checking-for-update' + +업데이트를 확인하기 시작할 때 발생하는 이벤트입니다. + +### Event: 'update-available' + +사용 가능한 업데이트가 있을 때 발생하는 이벤트입니다. 이벤트는 자동으로 다운로드 됩니다. + +### Event: 'update-not-available' + +사용 가능한 업데이트가 없을 때 발생하는 이벤트입니다. + +### Event: 'update-downloaded' + +* `event` Event +* `releaseNotes` String +* `releaseName` String +* `releaseDate` Date +* `updateUrl` String +* `quitAndUpdate` Function + +업데이트의 다운로드가 완료되었을 때 발생하는 이벤트입니다. `quitAndUpdate()`를 호출하면 어플리케이션을 종료하고 업데이트를 설치합니다. + +## Methods + +`autoUpdater` 객체에서 사용할 수 있는 메서드입니다: + +### `autoUpdater.setFeedUrl(url)` + +* `url` String + +`url`을 설정하고 자동 업데이터를 초기화합니다. `url`은 한번 설정되면 변경할 수 없습니다. + +### `autoUpdater.checkForUpdates()` + +서버에 새로운 업데이트가 있는지 요청을 보내 확인합니다. API를 사용하기 전에 `setFeedUrl`를 호출해야 합니다. diff --git a/docs-translations/ko/api/chrome-command-line-switches.md b/docs-translations/ko/api/chrome-command-line-switches.md new file mode 100644 index 000000000000..5b9bf4722042 --- /dev/null +++ b/docs-translations/ko/api/chrome-command-line-switches.md @@ -0,0 +1,105 @@ +# 크롬 Command-Line 스위치 지원 + +다음 Command-Line 스위치들은 크롬 브라우저에서 제공되는 추가 옵션이며 Electron에서도 지원합니다. +[app][app]의 [ready][ready]이벤트가 작동하기 전에 [app.commandLine.appendSwitch][append-switch] API를 사용하면 +어플리케이션 내부에서 스위치들을 추가할 수 있습니다: + +```javascript +var app = require('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기반의 리모트 디버거를 활성화 시킵니다. (개발자 콘솔) + +## --proxy-server=`address:port` + +시스템 설정의 프록시 서버를 무시하고 지정한 서버로 연결합니다. HTTP와 HTTPS 요청에만 적용됩니다. + +## --proxy-pac-url=`url` + +지정한 `url`의 PAC 스크립트를 사용합니다. + +## --no-proxy-server + +프록시 서버를 사용하지 않습니다. 다른 프록시 서버 플래그 및 설정을 무시하고 언제나 직접 연결을 사용합니다. + +## --host-rules=`rules` + +Hostname 맵핑 규칙을 설정합니다. (`,`로 분리) + +예시: + +* `MAP * 127.0.0.1` Forces all hostnames to be mapped to 127.0.0.1 +* `MAP *.google.com proxy` Forces all google.com subdomains to be resolved to + "proxy". +* `MAP test.com [::1]:77` Forces "test.com" to resolve to IPv6 loopback. Will + also force the port of the resulting socket address to be 77. +* `MAP * baz, EXCLUDE www.google.com` Remaps everything to "baz", except for + "www.google.com". + +이 맵핑은 네트워크 요청시의 endpoint를 지정합니다. (TCP 연결과 직접 연결의 호스트 resolver, http 프록시 연결의 `CONNECT`, `SOCKS` 프록시 연결의 endpoint 호스트) + +## --host-resolver-rules=`rules` + +`--host-rules` 플래그와 비슷하지만 이 플래그는 host resolver에만 적용됩니다. + +[app]: app.md +[append-switch]: app.md#appcommandlineappendswitchswitch-value +[ready]: app.md#event-ready + +## --ignore-certificate-errors + +인증서 에러를 무시합니다. + +## --ppapi-flash-path=`path` + +Pepper 플래시 플러그인의 위치를 설정합니다. + +## --ppapi-flash-version=`version` + +Pepper 플래시 플러그인의 버전을 설정합니다. + +## --log-net-log=`path` + +Net log 이벤트를 지정한 `path`에 로그로 기록합니다. + +## --v=`log_level` + +기본 V-logging 최대 활성화 레벨을 지정합니다. 기본값은 0입니다. 기본적으로 양수를 레벨로 사용합니다. + +`--v=-1`를 사용하면 로깅이 비활성화 됩니다. + +## --vmodule=`pattern` + +`--v` 옵션에 전달된 값을 덮어쓰고 모듈당 최대 V-logging 레벨을 지정합니다. +예를 들어 `my_module=2,foo*=3`는 `my_module.*`, `foo*.*`와 같은 파일 이름 패턴을 가진 모든 소스 코드들의 로깅 레벨을 각각 2와 3으로 설정합니다. + +슬래시(`/`), 백슬래시(`\`)를 포함하는 모든 패턴은 모듈뿐만 아니라 모든 경로명에 대해서도 테스트 됩니다. +예를 들어 `*/foo/bar/*=2` 표현식은 `foo/bar` 디렉터리 안의 모든 소스 코드의 로깅 레벨을 2로 지정합니다. + +모든 크로미움과 관련된 로그를 비활성화하고 어플리케이션의 로그만 활성화 하려면 다음과 같이 코드를 작성하면 됩니다: + + +```javascript +app.commandLine.appendSwitch('v', -1); +app.commandLine.appendSwitch('vmodule', 'console=0'); +``` diff --git a/docs-translations/ko/api/clipboard.md b/docs-translations/ko/api/clipboard.md new file mode 100644 index 000000000000..080078274e01 --- /dev/null +++ b/docs-translations/ko/api/clipboard.md @@ -0,0 +1,105 @@ +# clipboard + +`clipboard` 모듈은 복사/붙여넣기 작업을 수행하는 방법을 제공합니다. 다음 예제는 클립보드에 문자열을 씁니다: + +```javascript +var clipboard = require('clipboard'); +clipboard.writeText('Example String'); +``` + +X Window 시스템에선 selection 클립보드도 존재합니다. 이를 사용하려면 인자 뒤에 `selection` 문자열을 같이 지정해주어야 합니다: + +```javascript +var clipboard = require('clipboard'); +clipboard.writeText('Example String', 'selection'); +console.log(clipboard.readText('selection')); +``` + +## Methods + +`clipboard` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +**참고:** Experimental 마크가 붙은 API는 실험적인 기능이며 차후 최신 버전에서 제거될 수 있습니다. + +### `clipboard.readText([type])` + +* `type` String (optional) + +클립보드 컨텐츠를 `plain text`로 반환합니다. + +### `clipboard.writeText(text[, type])` + +* `text` String +* `type` String (optional) + +클립보드에 `plain text`로 문자열을 씁니다. + +### `clipboard.readHtml([type])` + +* `type` String (optional) + +클립보드 컨텐츠를 `markup`으로 반환합니다. + +### `clipboard.writeHtml(markup[, type])` + +* `markup` String +* `type` String (optional) + +클립보드에 `markup`으로 씁니다. + +### `clipboard.readImage([type])` + +* `type` String (optional) + +클립보드로부터 [NativeImage](native-image.md)로 이미지를 읽어들입니다. + +### `clipboard.writeImage(image[, type])` + +* `image` [NativeImage](native-image.md) +* `type` String (optional) + +클립보드에 `image`를 씁니다. + +### `clipboard.clear([type])` + +* `type` String (optional) + +클립보드에 저장된 모든 컨텐츠를 삭제합니다. + +### clipboard.availableFormats([type]) + +클립보드의 `type`에 해당하는 지원하는 `format`을 문자열로 반환합니다. + +### `clipboard.has(data[, type])` + +* `data` String +* `type` String (optional) + +클립보드가 지정한 `data`의 형식을 지원하는지 확인합니다. + +```javascript +var clipboard = require('clipboard'); +console.log(clipboard.has('

selection

')); +``` + +### `clipboard.read(data[, type])` _Experimental_ + +* `data` String +* `type` String (optional) + +클립보드로부터 `data`를 읽어들입니다. + +### `clipboard.write(data[, type])` _Experimental_ + +* `data` Object + * `text` String + * `html` String + * `image` [NativeImage](native-image.md) +* `type` String (optional) + +```javascript +var clipboard = require('clipboard'); +clipboard.write({text: 'test', html: "test"}); +``` + +`data`를 클립보드에 씁니다. diff --git a/docs-translations/ko/api/content-tracing.md b/docs-translations/ko/api/content-tracing.md new file mode 100644 index 000000000000..08221fbc7076 --- /dev/null +++ b/docs-translations/ko/api/content-tracing.md @@ -0,0 +1,140 @@ +# contentTracing + +`content-tracing` 모듈은 Chromium 컨텐츠 모듈단에서 생성된 데이터를 수집하고 추적하는데 사용됩니다. +이 모듈은 웹 인터페이스를 포함하고 있지 않으며 크롬 브라우저에서 `chrome://tracing/` 페이지를 열어 생성된 파일을 로드하면 결과를 볼 수 있습니다. + +```javascript +var contentTracing = require('content-tracing'); + +contentTracing.startRecording('*', contentTracing.DEFAULT_OPTIONS, function() { + console.log('Tracing started'); + + setTimeout(function() { + contentTracing.stopRecording('', function(path) { + console.log('Tracing data recorded to ' + path); + }); + }, 5000); +}); +``` + +## Methods + +`content-tracing` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `contentTracing.getCategories(callback)` + +* `callback` Function + +카테고리 그룹 세트를 가져옵니다. 카테고리 그룹은 도달된 코드 경로를 변경할 수 있습니다. + +모든 child 프로세스가 `getCategories` 요청을 승인하면 `callback`이 한 번 호출되며 인자에 카테고리 그룹의 배열이 전달됩니다. + +### `contentTracing.startRecording(options, callback)` + +* `options` Object + * `categoryFilter` String + * `traceOptions` String +* `callback` Function + +모든 프로세스에서 레코딩을 시작합니다. + +레코딩은 지역적으로 즉시 실행됩니다. 그리고 비동기로 child 프로세스는 곧 EnableRecording 요청을 받게 됩니다. +모든 child 프로세스가 `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번째까지의 옵션은 추적 레코딩 모드입니다. 이에 따라 상호 배타적입니다. +만약 레코딩 모드가 한 개 이상 지정되면 마지막 지정한 모드만 사용됩니다. +어떤 모드도 설정되지 않았다면 `record-until-full` 모드가 기본으로 사용됩니다. + +추적 옵션은 `traceOptions`이 파싱되어 적용되기 전까지 다음과 같은 기본값이 사용됩니다. + +`record-until-full`이 기본 모드, `enable-sampling`과 `enable-systrace`옵션은 포함되지 않음 + +## `contentTracing.stopRecording(resultFilePath, callback)` + +* `resultFilePath` String +* `callback` Function + +모든 프로세스에서 레코딩을 중지합니다. + +Child 프로세스는 일반적으로 추적 데이터와 희귀한 플러시 그리고 추적 데이터를 메인 프로세스로 보내는 작업에 대해 캐싱 합니다. +이러한 일을 하는 이유는 IPC를 통해 추적 데이터를 보내는 작업은 매우 비싼 연산을 동반하기 때문입니다. +우리는 추적에 의한 런타임 오버헤드를 피하고자 합니다. +그래서 추적이 끝나면 모든 child 프로세스에 보류된 추적 데이터를 플러시 할 것인지 물어봅니다. + +모든 child 프로세스가 `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()` + +Watch 이벤트를 중단합니다. 만약 추적이 활성화되어 있다면 이 메서드는 watch 이벤트 콜백과 race가 일어날 것입니다. diff --git a/docs-translations/ko/api/crash-reporter.md b/docs-translations/ko/api/crash-reporter.md new file mode 100644 index 000000000000..3a7bc35f0982 --- /dev/null +++ b/docs-translations/ko/api/crash-reporter.md @@ -0,0 +1,67 @@ +# crashReporter + +`crash-reporter` 모듈은 어플리케이션의 크래시 정보를 자동으로 원격 서버에 업로드하는데 사용합니다. + +다음 예제는 윈격 서버에 어플리케이션 크래시 정보를 자동으로 보고하는 예제입니다: + +```javascript +var crashReporter = require('crash-reporter'); + +crashReporter.start({ + productName: 'YourName', + companyName: 'YourCompany', + submitUrl: 'https://your-domain.com/url-to-submit', + autoSubmit: true +}); +``` + +## Methods + +`crash-reporter` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `crashReporter.start(options)` + +* `options` Object, properties: + +* `productName` String, 기본값: Electron +* `companyName` String, 기본값: GitHub, Inc +* `submitUrl` String, 기본값: http://54.249.141.255:1127/post + * 크래시 리포트는 POST 방식으로 이 URL로 전송됩니다. +* `autoSubmit` Boolean, 기본값: true + * true로 지정할 경우 유저의 승인 없이 자동으로 오류를 보고합니다. +* `ignoreSystemCrashHandler` Boolean, 기본값: false +* `extra` Object + * 크래시 리포트 시 같이 보낼 추가 정보를 지정하는 객체입니다. + * 문자열로 된 속성만 정상적으로 보내집니다. + * 중첩 객체는 지원되지 않습니다. (Nested objects are not supported) + +다른 crashReporter API를 사용하기 전에 이 메서드를 먼저 호출해야 합니다. + +**참고:** OS X에선 Windows와 Linux의 `breakpad`와 달리 새로운 `crashpad` 클라이언트를 사용합니다. +오류 수집 기능을 활성화 시키려면 오류를 수집하고 싶은 메인 프로세스나 랜더러 프로세스에서 +`crashReporter.start` 메서드를 호출하여 `crashpad`를 초기화 해야합니다. + +### `crashReporter.getLastCrashReport()` + +마지막 크래시 리포트의 날짜와 ID를 반환합니다. +이전 크래시 리포트가 없거나 Crash Reporter가 시작되지 않았을 경우 `null`이 반환됩니다. + +### `crashReporter.getUploadedReports()` + +모든 업로드된 크래시 리포트를 반환합니다. 각 보고는 날짜와 업로드 ID를 포함하고 있습니다. + +## crash-reporter 업로드 형식 + +Crash Reporter는 다음과 같은 데이터를 `submitUrl`에 `POST` 방식으로 전송합니다: + +* `rept` String - 예시 'electron-crash-service' +* `ver` String - Electron의 버전 +* `platform` String - 예시 'win32' +* `process_type` String - 예시 'renderer' +* `ptime` Number +* `_version` String - `package.json`내의 `version` 필드 +* `_productName` String - Crash Reporter의 `options` 객체에서 정의한 제품명. +* `prod` String - 기본 제품의 이름. 이 경우 Electron으로 표시됩니다. +* `_companyName` String - Crash Reporter의 `options` 객체에서 정의한 회사명. +* `upload_file_minidump` File - 크래시 리포트 파일 +* Crash Reporter의 `options` 객체에서 정의한 `extra` 객체의 속성들. diff --git a/docs-translations/ko/api/dialog.md b/docs-translations/ko/api/dialog.md new file mode 100644 index 000000000000..08530c054797 --- /dev/null +++ b/docs-translations/ko/api/dialog.md @@ -0,0 +1,106 @@ +# dialog + +`dialog` 모듈은 파일 열기, 알림과 같은 네이티브 시스템의 대화 상자를 조작할 때 사용할 수 있는 모듈입니다. +이 모듈을 사용하면 웹 어플리케이션에서 일반 네이티브 어플리케이션과 비슷한 사용자 경험을 제공할 수 있습니다. + +다음 예제는 파일과 디렉터리를 다중으로 선택하는 대화 상자를 표시하는 예제입니다: + +```javascript +var win = ...; // 대화 상자를 사용할 BrowserWindow 객체 +var dialog = require('dialog'); +console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); +``` + +**OS X 알림**: 대화 상자를 시트처럼 보여지게 하려면 `browserWindow` 인자에 `BrowserWindow` 객체의 참조를 제공하면 됩니다. + +## Methods + +`dialog` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `dialog.showOpenDialog([browserWindow][, options][, callback])` + +* `browserWindow` BrowserWindow (optional) +* `options` Object (optional) + * `title` String + * `defaultPath` String + * `filters` Array + * `properties` Array - 대화 상자가 사용할 기능(모드)이 담긴 배열입니다. + 다음을 포함할 수 있습니다: `openFile`, `openDirectory`, `multiSelections`, `createDirectory` +* `callback` Function (optional) + +사용할 대화 상자의 기능이 담긴 배열입니다. 다음을 포함할 수 있습니다: `openFile`, `openDirectory`, `multiSelections`, `createDirectory` + +작업에 성공하면 콜백으로 유저가 선택한 파일의 경로를 포함한 배열을 반환합니다. 그 외엔 `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`이 전달되면 메소드가 비동기로 작동되며 결과는 `callback(filenames)`을 통해 전달됩니다. + +**참고:** Windows와 Linux에선 파일 선택 모드, 디렉터리 선택 모드를 동시에 사용할 수 없습니다. +이러한 이유로 `properties`를 `['openFile', 'openDirectory']`로 설정하면 디렉터리 선택 대화 상자가 표시됩니다. + +### `dialog.showSaveDialog([browserWindow][, options][, callback])` + +* `browserWindow` BrowserWindow (optional) +* `options` Object (optional) + * `title` String + * `defaultPath` String + * `filters` Array +* `callback` Function (optional) + +작업에 성공하면 콜백으로 유저가 선택한 파일의 경로를 포함한 배열을 반환합니다. 그 외엔 `undefined`를 반환합니다. + +`filters`를 지정하면 유저가 저장 가능한 파일 형식을 지정할 수 있습니다. 사용 방법은 `dialog.showOpenDialog`의 `filters` 속성과 같습니다. + +`callback`이 전달되면 메소드가 비동기로 작동되며 결과는 `callback(filename)`을 통해 전달됩니다. + +### `dialog.showMessageBox([browserWindow][, options][, callback])` + +* `browserWindow` BrowserWindow +* `options` Object + * `type` String - `"none"`, `"info"`, `"error"`, `"question"`, `"warning"` 중 하나를 사용할 수 있습니다. + Windows에선 따로 `icon`을 설정하지 않은 이상 "question"과 "info"는 같은 아이콘으로 표시됩니다. + * `buttons` Array - 버튼들의 라벨을 포함한 배열입니다. + * `title` String - 대화 상자의 제목입니다. 몇몇 플랫폼에선 보이지 않을 수 있습니다. + * `message` String - 대화 상자의 본문 내용입니다. + * `detail` String - 메시지의 추가 정보입니다. + * `icon` [NativeImage](native-image.md) + * `cancelId` Integer - 유저가 대화 상자의 버튼을 클릭하지 않고 대화 상자를 취소했을 때 반환되는 버튼의 인덱스입니다. + 기본적으로 버튼 리스트가 "cancel" 또는 "no" 라벨을 가지고 있을 때 해당 버튼의 인덱스를 반환합니다. 따로 두 라벨이 지정되지 않은 경우 0을 반환합니다. + OS X와 Windows에선 `cancelId` 지정 여부에 상관없이 "Cancel" 버튼이 언제나 `cancelId`로 지정됩니다. + * `noLink` Boolean - Windows Electron은 "Cancel"이나 "Yes"와 같은 흔히 사용되는 버튼을 찾으려고 시도하고 + 대화 상자 내에서 해당 버튼을 커맨드 링크처럼 만듭니다. 이 기능으로 앱을 좀 더 Modern Windows 앱처럼 만들 수 있습니다. + 이 기능을 원하지 않으면 `noLink`를 true로 지정하면 됩니다. +* `callback` Function + +대화 상자를 표시합니다. `browserWindow`를 지정하면 대화 상자가 완전히 닫힐 때까지 지정한 창을 사용할 수 없습니다. +완료 시 유저가 선택한 버튼의 인덱스를 반환합니다. + +역주: 부정을 표현하는 "아니오", "취소"와 같은 한글 단어는 지원되지 않습니다. +만약 OS X 또는 Windows에서 "확인", "취소"와 같은 순서로 버튼을 지정하게 될 때 Alt + f4로 해당 대화 상자를 끄게 되면 "확인"을 누른 것으로 판단되어 버립니다. +이를 해결하려면 "Cancel"을 대신 사용하거나 BrowserWindow API를 사용하여 대화 상자를 직접 구현해야합니다. + +`callback`이 전달되면 메소드가 비동기로 작동되며 결과는 `callback(response)`을 통해 전달됩니다. + +### `dialog.showErrorBox(title, content)` + +에러 메시지를 보여주는 대화 상자를 표시합니다. + +이 API는 `app` 모듈의 `ready` 이벤트가 발생하기 전에 사용할 수 있습니다. +이 메소드는 보통 어플리케이션이 시작되기 전에 특정한 에러를 표시하기 위해 사용됩니다. diff --git a/docs-translations/ko/api/file-object.md b/docs-translations/ko/api/file-object.md new file mode 100644 index 000000000000..6006220647dd --- /dev/null +++ b/docs-translations/ko/api/file-object.md @@ -0,0 +1,29 @@ +# `File` 객체 + +DOM의 File 인터페이스는 네이티브 파일을 추상화 합니다. +유저가 직접 HTML5 File API를 이용하여 작업할 때 선택된 파일의 경로를 알 수 있도록 +Electron은 파일의 실제 경로를 담은 `path` 속성을 File 인터페이스에 추가하였습니다. + +다음 예제는 앱으로 드래그 앤 드롭한 파일의 실제 경로를 가져옵니다: + +```html +
+ Drag your file here +
+ + +``` diff --git a/docs-translations/ko/api/frameless-window.md b/docs-translations/ko/api/frameless-window.md new file mode 100644 index 000000000000..64422460775f --- /dev/null +++ b/docs-translations/ko/api/frameless-window.md @@ -0,0 +1,85 @@ +# Frameless Window + +Frameless Window는 [테두리](https://developer.mozilla.org/en-US/docs/Glossary/Chrome)가 없는 창입니다. +이 기능은 윈도우 창의 일부분인 툴바와 같이 웹 페이지의 일부분이 아닌 부분을 보이지 않도록 합니다. +[`BrowserWindow`](browser-window.md) 클래스의 옵션에서 설정할 수 있습니다. + +## Frameless Window 만들기 + +Frameless Window를 만드려면 [BrowserWindow](browser-window.md) 객체의 `options`에서 `frame` 옵션을 `false`로 지정하면 됩니다: + +```javascript +var BrowserWindow = require('browser-window'); +var win = new BrowserWindow({ width: 800, height: 600, frame: false }); +``` + +### 최신 Mac에서 사용할 수 있는 대안 + +OS X 10.10 Yosemite 이후의 최신 버전부터는 테두리가 없는 창을 만들 때 새로운 방법을 사용할 수 있습니다. +`frame` 옵션을 `false`로 지정하여 제목과 창 구성 요소를 모두 비활성화하는 대신 새로운 `title-bar-style` +옵션을 통해 제목만 숨기고 창 구성 요소("흔히 신호등으로 알고 있는")의 기능과 창 크기를 그대로 유지할 수 있습니다: + +```javascript +var BrowserWindow = require('browser-window'); +var win = new BrowserWindow({ width: 800, height: 600, 'title-bar-style': 'hidden' }); + +## 투명한 창 만들기 + +Frameless Window의 창의 배경을 투명하게 만들고 싶다면 `transparent` 옵션을 `true`로 바꿔주기만 하면됩니다: + +```javascript +var win = new BrowserWindow({ transparent: true, frame: false }); +``` + +### API의 한계 + +* 투명한 영역을 통과하여 클릭할 수 없습니다. 우리는 이 문제를 해결하기 위해 API를 제공할 예정이었지만 현재로써는 + [upstream 버그](https://code.google.com/p/chromium/issues/detail?id=387234)로 인해 중단된 상태입니다. +* 투명한 창은 크기를 조절할 수 없습니다. `resizable` 속성을 `true`로 할 경우 몇몇 플랫폼에선 크래시가 일어납니다. +* `blur` 필터는 웹 페이지에서만 적용됩니다. 윈도우 아래 컨텐츠에는 블러 효과를 적용할 방법이 없습니다. (예시: 유저의 시스템에 열린 다른 어플리케이션) +* Windows에선 DWM(데스크톱 창 관리자)가 비활성화되어 있을 경우 투명한 창이 작동하지 않습니다. +* Linux를 사용할 경우 [alpha channel doesn't work on some NVidia drivers](https://code.google.com/p/chromium/issues/detail?id=369209) + upstream 버그가 있는 관계로 투명한 창 기능을 사용하려면 CLI 옵션에 `--enable-transparent-visuals --disable-gpu`을 추가해야 합니다. + 이 옵션은 GPU의 사용을 중단하고 윈도우를 생성하는데 ARGB를 사용할 수 있도록 해줍니다. +* OS X(Mac)에선 네이티브 창에서 보여지는 그림자가 투명한 창에선 보이지 않습니다. + +## 드래그 가능 위치 지정 + +기본적으로 Frameless Window는 드래그 할 수 없습니다. +어플리케이션의 CSS에서 특정 범위를 `-webkit-app-region: drag`로 지정하면 OS의 기본 타이틀 바 처럼 드래그 되도록 할 수 있습니다. +그리고 `-webkit-app-region: no-drag`를 지정해서 드래그 불가능 영역을 만들 수도 있습니다. 현재 사각형 형태의 범위만 지원합니다. + +창 전체를 드래그 가능하게 만드려면 `-webkit-app-region: drag`을 `body`의 스타일에 지정하면 됩니다: + +```html + + +``` + +참고로 창 전체를 드래그 영역으로 지정할 경우 사용자가 버튼을 클릭할 수 없게 되므로 버튼은 드래그 불가능 영역으로 지정해야 합니다: + +```css +button { + -webkit-app-region: no-drag; +} +``` + +따로 커스텀 타이틀 바를 만들어 사용할 때는 타이틀 바 내부의 모든 버튼을 드래그 불가능 영역으로 지정해야 합니다. + +## 텍스트 선택 + +Frameless Window에서 텍스트가 선택되는 드래그 동작은 혼란을 야기할 수 있습니다. +예를 들어 타이틀 바를 드래그 할 때 타이틀 바의 텍스트를 실수로 선택할 수 있습니다. +이를 방지하기 위해 다음과 같이 드래그 영역의 텍스트 선택 기능을 비활성화해야 할 필요가 있습니다: + +```css +.titlebar { + -webkit-user-select: none; + -webkit-app-region: drag; +} +``` + +## 컨텍스트 메뉴 + +몇몇 플랫폼에선 드래그 가능 영역이 non-client 프레임으로 처리됩니다. 이러한 플랫폼에선 드래그 가능 영역에서 오른쪽 클릭 할 경우 시스템 메뉴가 팝업 됩니다. +이러한 이유로 컨텍스트 메뉴 지정 시 모든 플랫폼에서 정상적으로 작동하게 하려면 커스텀 컨텍스트 메뉴를 드래그 영역 내에 만들어선 안됩니다. diff --git a/docs-translations/ko/api/global-shortcut.md b/docs-translations/ko/api/global-shortcut.md new file mode 100644 index 000000000000..84c1c70798d7 --- /dev/null +++ b/docs-translations/ko/api/global-shortcut.md @@ -0,0 +1,59 @@ +# global-shortcut + +`global-shortcut` 모듈은 운영체제의 전역 키보드 단축키를 등록/해제 하는 방법을 제공합니다. +이 모듈을 사용하여 사용자가 다양한 작업을 편하게 할 수 있도록 단축키를 정의 할 수 있습니다. + +**참고:** 등록된 단축키는 어플리케이션이 백그라운드로 작동(창이 포커스 되지 않음) 할 때도 계속해서 작동합니다. +이 모듈은 `app` 모듈의 `ready` 이벤트 이전에 사용할 수 없습니다. + +```javascript +var app = require('app'); +var globalShortcut = require('global-shortcut'); + +app.on('ready', function() { + // 'ctrl+x' 단축키를 리스너에 등록합니다. + var ret = globalShortcut.register('ctrl+x', function() { console.log('ctrl+x is pressed'); }) + + if (!ret) { + console.log('registration failed'); + } + + // 단축키가 등록되었는지 확인합니다. + console.log(globalShortcut.isRegistered('ctrl+x')); +}); + +app.on('will-quit', function() { + // 단축키의 등록을 해제합니다. + globalShortcut.unregister('ctrl+x'); + + // 모든 단축키의 등록을 해제합니다. + globalShortcut.unregisterAll(); +}); +``` + +## Methods + +`global-shortcut` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `globalShortcut.register(accelerator, callback)` + +* `accelerator` [Accelerator](accelerator.md) +* `callback` Function + +`accelerator`로 표현된 전역 단축키를 등록합니다. 유저로부터 등록된 단축키가 눌렸을 경우 `callback` 함수가 호출됩니다. + +### `globalShortcut.isRegistered(accelerator)` + +* `accelerator` [Accelerator](accelerator.md) + +지정된 `accelerator` 단축키가 등록되었는지 여부를 확인합니다. 반환값은 boolean(true, false) 입니다. + +### `globalShortcut.unregister(accelerator)` + +* `accelerator` [Accelerator](accelerator.md) + +`accelerator`에 해당하는 전역 단축키를 등록 해제합니다. + +### `globalShortcut.unregisterAll()` + +모든 전역 단축키 등록을 해제합니다. diff --git a/docs-translations/ko/api/ipc-main-process.md b/docs-translations/ko/api/ipc-main-process.md new file mode 100644 index 000000000000..ad4e545ce474 --- /dev/null +++ b/docs-translations/ko/api/ipc-main-process.md @@ -0,0 +1,71 @@ +# ipc (main process) + +`ipc` (main process) 모듈은 메인 프로세스에서 사용할 때 랜더러 프로세스(웹 페이지)에서 전달된 동기 또는 비동기 메시지를 보내고 받는 방법을 제공합니다. +랜더러 프로세스에서 메시지를 전달하면 이 모듈을 통해 메시지를 받을 수 있습니다. + +## 메시지 전송 + +물론 메인 프로세스에서 랜더러 프로세스로 메시지를 보내는 것도 가능합니다. +자세한 내용은 [WebContents.send](web-contents.md#webcontentssendchannel-args)를 참고하세요. + +- 메시지를 전송할 때 이벤트 이름은 `channel`이 됩니다. +- 메시지에 동기로 응답할 땐 반드시 `event.returnValue`를 설정해야 합니다. +- 메시지를 비동기로 응답할 땐 `event.sender.send(...)` 메서드를 사용할 수 있습니다. + +랜더러 프로세스와 메인 프로세스간에 메시지를 전달하고 받는 예제입니다: + +```javascript +// 메인 프로세스 +var ipc = require('ipc'); +ipc.on('asynchronous-message', function(event, arg) { + console.log(arg); // prints "ping" + event.sender.send('asynchronous-reply', 'pong'); +}); + +ipc.on('synchronous-message', function(event, arg) { + console.log(arg); // prints "ping" + event.returnValue = 'pong'; +}); +``` + +```javascript +// 랜더러 프로세스 (web page) +var ipc = require('ipc'); +console.log(ipc.sendSync('synchronous-message', 'ping')); // prints "pong" + +ipc.on('asynchronous-reply', function(arg) { + console.log(arg); // prints "pong" +}); +ipc.send('asynchronous-message', 'ping'); +``` + +## 메시지 리스닝 + +`ipc` 모듈은 다음과 같은 이벤트 메서드를 가지고 있습니다: + +### `ipc.on(channel, callback)` + +* `channel` String - 이벤트 이름 +* `callback` Function + +이벤트가 발생하면 `callback`에 `event` 객체와 `arg` 메시지가 포함되어 호출됩니다. + +## IPC Events + +`callback`에서 전달된 `event` 객체는 다음과 같은 메서드와 속성을 가지고 있습니다: + +### `Event.returnValue` + +이 메시지를 지정하면 동기 메시지를 전달합니다. + +### `Event.sender` + +메시지를 보낸 `WebContents` 객체를 반환합니다. + +### `Event.sender.send(channel[, arg1][, arg2][, ...])` + +* `channel` String - The event name. +* `arg` (optional) + +랜더러 프로세스로 비동기 메시지를 전달합니다. +옵션으로 `arg`에 한 개 또는 여러 개의 메시지를 포함할 수 있습니다. 모든 타입을 사용할 수 있습니다. diff --git a/docs-translations/ko/api/ipc-renderer.md b/docs-translations/ko/api/ipc-renderer.md new file mode 100644 index 000000000000..67864c4e1157 --- /dev/null +++ b/docs-translations/ko/api/ipc-renderer.md @@ -0,0 +1,45 @@ +# ipc (renderer) + +`ipc` (renderer) 모듈은 메인 프로세스로 동기 또는 비동기 메시지를 보내고 받는 방법을 제공합니다. +물론 메인 프로세스로부터 받은 메시지에 응답할 수도 있습니다. + +**참고:** 만약 랜더러 프로세스에서 메인 프로세스의 모듈을 직접적 사용하고 싶다면 [remote](remote.md) 모듈을 사용하는 것을 고려해보는 것이 좋습니다. + +[ipc (main process)](ipc-main-process.md)에서 예제를 확인 할 수 있습니다. + +## Methods + +`ipc` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +**참고:** 이 메소드들을 사용하여 `message`를 보낼 땐 반드시 메인 프로세스의 +[`ipc (main process)`](ipc-main-process.md) 모듈에서도 이벤트 리스너를 등록해 두어야 합니다. + +### `ipc.send(channel[, arg1][, arg2][, ...])` + +* `channel` String - 이벤트 이름 +* `arg` (optional) + +`channel`을 통해 메인 프로세스에 비동기 메시지를 보냅니다. +옵션으로 `arg`에 한 개 또는 여러 개의 메시지를 포함할 수 있습니다. 모든 타입을 사용할 수 있습니다. +메인 프로세스는 `ipc`를 통해 `channel` 이벤트를 리스닝 할 수 있습니다. + +### `ipc.sendSync(channel[, arg1][, arg2][, ...])` + +* `channel` String - 이벤트 이름 +* `arg` (optional) + +`channel`을 통해 메인 프로세스에 동기 메시지를 보냅니다. +옵션으로 `arg`에 한 개 또는 여러 개의 메시지를 포함할 수 있습니다. 모든 타입을 사용할 수 있습니다. +메인 프로세스는 `ipc`를 통해 `channel` 이벤트를 리스닝 할 수 있습니다. + +메인 프로세스에선 `ipc` 모듈의 `channel` 이벤트를 통해 받은 `event.returnValue`로 회신 할 수 있습니다. + +**참고:** 동기 메시징은 모든 랜더러 프로세스의 작업을 일시 중단시킵니다. 이 메서드를 사용하는 것을 권장하지 않습니다. + +### `ipc.sendToHost(channel[, arg1][, arg2][, ...])` + +* `channel` String - 이벤트 이름 +* `arg` (optional) + +`ipc.send`와 비슷하지만 이벤트를 메인 프로세스 대신 호스트 페이지내의 ``로 보냅니다. +옵션으로 `arg`에 한 개 또는 여러 개의 메시지를 포함할 수 있습니다. 모든 타입을 사용할 수 있습니다. diff --git a/docs-translations/ko/api/menu-item.md b/docs-translations/ko/api/menu-item.md new file mode 100644 index 000000000000..8f1021009954 --- /dev/null +++ b/docs-translations/ko/api/menu-item.md @@ -0,0 +1,52 @@ +# MenuItem + +`menu-item` 모듈은 어플리케이션 또는 컨텐츠 [`menu`](menu.md)에 아이템을 추가할 수 있도록 관련 클래스를 제공합니다. + +[`menu`](menu.md)에서 예제를 확인할 수 있습니다. + +## Class: MenuItem + +`MenuItem` 인스턴스 객체에서 사용할 수 있는 메서드입니다: + +### new MenuItem(options) + +* `options` Object + * `click` Function - 메뉴 아이템이 클릭될 때 `click(menuItem, browserWindow)` 형태로 호출 되는 콜백 함수 + * `role` String - 메뉴 아이템의 액션을 정의합니다. 이 속성을 지정하면 `click` 속성이 무시됩니다. + * `type` String - `MenuItem`의 타입 `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`일 경우 반드시 설정해야합니다. 일반 메뉴 아이템일 경우 생략할 수 있습니다. + * `id` String - 현재 메뉴 아이템에 대해 유일키를 지정합니다. 이 키는 이후 `position` 옵션에서 사용할 수 있습니다. + * `position` String - 미리 지정한 `id`를 이용하여 메뉴 아이템의 위치를 세밀하게 조정합니다. + +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. + +The `role` property can have following values: + +* `undo` +* `redo` +* `cut` +* `copy` +* `paste` +* `selectall` +* `minimize` - Minimize current window +* `close` - Close current window + +On OS X `role` can also have following additional values: + +* `about` - Map to the `orderFrontStandardAboutPanel` action +* `hide` - Map to the `hide` action +* `hideothers` - Map to the `hideOtherApplications` action +* `unhide` - Map to the `unhideAllApplications` action +* `front` - Map to the `arrangeInFront` action +* `window` - The submenu is a "Window" menu +* `help` - The submenu is a "Help" menu +* `services` - The submenu is a "Services" menu diff --git a/docs-translations/ko/api/menu.md b/docs-translations/ko/api/menu.md new file mode 100644 index 000000000000..a8ac499f5768 --- /dev/null +++ b/docs-translations/ko/api/menu.md @@ -0,0 +1,350 @@ +# Menu + +`menu` 클래스는 어플리케이션 메뉴와 [컨텍스트 메뉴](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/ContextMenus)를 만들 때 사용됩니다. +이 모듈은 메인 프로세스용 모듈이지만 `remote` 모듈을 통해 랜더러 프로세스에서도 사용할 수 있습니다. + +각 메뉴는 여러 개의 [메뉴 아이템](menu-item.md)으로 구성되고 서브 메뉴를 가질 수도 있습니다. + +다음 예제는 웹 페이지 내에서 [remote](remote.md) 모듈을 활용하여 동적으로 메뉴를 생성하는 예제입니다. +그리고 유저가 페이지에서 오른쪽 클릭을 할 때마다 마우스 위치에 팝업 형태로 메뉴를 표시합니다: + +```html + + +``` + +또 하나의 예를 들자면 다음 예제는 랜더러 프로세스에서 template 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('shell').openExternal('http://electron.atom.io') } + }, + ] + }, +]; + +if (process.platform == 'darwin') { + var name = require('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+Shift+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' + } + ); +} + +menu = Menu.buildFromTemplate(template); +Menu.setApplicationMenu(menu); +``` + +## Class: Menu + +### `new Menu()` + +새로운 메뉴를 생성합니다. + +### Methods + +`menu` 클래스는 다음과 같은 메서드를 가지고 있습니다: + +### `Menu.setApplicationMenu(menu)` + +* `menu` Menu + +지정한 `menu`를 어플리케이션 메뉴로 만듭니다. OS X에선 상단바에 표시되며 Windows와 Linux에선 각 창의 상단에 표시됩니다. + +### `Menu.sendActionToFirstResponder(action)` _OS X_ + +* `action` String + +`action`을 어플리케이션의 first responder에 전달합니다. +이 메서드는 Cocoa 메뉴 동작을 에뮬레이트 하는데 사용되며 보통 `MenuItem`의 `selector` 속성에 사용됩니다. + +**참고:** 이 메서드는 OS X에서만 사용할 수 있습니다. + +### `Menu.buildFromTemplate(template)` + +* `template` Array + +기본적으로 `template`는 [MenuItem](menu-item.md)을 생성할 때 사용하는 `options`의 배열입니다. 사용법은 위에서 설명한 것과 같습니다. + +또한 `template`에는 다른 속성도 추가할 수 있으며 메뉴가 만들어질 때 해당 메뉴 아이템의 프로퍼티로 변환됩니다. + +### `Menu.popup(browserWindow[, x, y])` + +* `browserWindow` BrowserWindow +* `x` Number (optional) +* `y` Number (만약 `x`를 지정했을 경우 `y`도 필수로 지정해야 합니다) + +메뉴를 `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` + +### 메뉴 아이템 기본 동작 + +OS X는 몇가지 메뉴 아이템에 대해 `About xxx`, `Hide xxx`, `Hide Others`와 같은 기본 동작을 제공하고 있습니다. +메뉴 아이템의 기본 동작을 지정하려면 반드시 메뉴 아이템의 `role` 속성을 지정해야 합니다. + +### 메인 메뉴의 이름 + +OS X에선 지정한 어플리케이션 메뉴에 상관없이 메뉴의 첫번째 라벨은 언제나 어플리케이션의 이름이 됩니다. +어플리케이션 이름을 변경하려면 앱 번들내의 `Info.plist` 파일을 수정해야합니다. +자세한 내용은 [About Information Property List Files][AboutInformationPropertyListFiles] 문서를 참고하세요. + +## 메뉴 아이템 위치 + +`Menu.buildFromTemplate`로 메뉴를 만들 때 `position`과 `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/ko/api/native-image.md b/docs-translations/ko/api/native-image.md new file mode 100644 index 000000000000..423cedb98300 --- /dev/null +++ b/docs-translations/ko/api/native-image.md @@ -0,0 +1,145 @@ +# NativeImage + +Electron은 파일 경로 또는 `NativeImage` 인스턴스를 통해 이미지를 사용할 수 있는 API를 가지고 있습니다. +`null`을 전달할 경우 빈 이미지가 생성됩니다. + +예를 들어 트레이 메뉴를 만들거나 윈도우의 아이콘을 설정할 때 다음과 같이 파일 경로를 전달하여 이미지를 사용할 수 있습니다: + +```javascript +var appIcon = new Tray('/Users/somebody/images/icon.png'); +var window = new BrowserWindow({icon: '/Users/somebody/images/window.png'}); +``` + +이 예제는 클립보드로부터 가져온 `NativeImage`로 트레이 메뉴를 생성합니다: + +```javascript +var clipboard = require('clipboard'); +var image = clipboard.readImage(); +var appIcon = new Tray(image); +``` + +## 지원하는 포맷 + +현재 `PNG` 와 `JPEG` 이미지 포맷을 지원하고 있습니다. +손실 없는 이미지 압축과 투명도 지원을 위해 `PNG` 사용을 권장합니다. + +그리고 Windows에서는 `ICO` 포맷도 사용할 수 있습니다. + +## 고해상도 이미지 + +플랫폼이 high-DPI를 지원하는 경우 `@2x`와 같이 이미지의 파일명 뒤에 접미사를 추가하여 고해상도 이미지로 지정할 수 있습니다. + +예를 들어 `icon.png` 라는 기본 해상도의 이미지를 기준으로 크기를 두 배로 늘린 이미지를 `icon@2x.png` 처럼 지정하면 고해상도 이미지로 처리됩니다. + +서로 다른 해상도(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` + +## 템플릿 이미지 + +템플릿 이미지는 검은색과 명확한 색상(알파 채널)으로 이루어져 있습니다. +템플릿 이미지는 단독 이미지로 사용되지 않고 다른 컨텐츠와 혼합되어 최종 외관 만드는데 사용됩니다. + +가장 일반적으로 템플릿 이미지는 밝고 어두운 테마 색상으로 변경할 수 있는 메뉴 바 아이콘 등에 사용되고 있습니다. + +**참고:** 템플릿 이미지는 OS X 운영체제만 지원합니다. + +템플릿 이미지를 지정하려면 다음 예제와 같이 파일명에 `Template` 문자열을 추가해야 합니다: + +* `xxxTemplate.png` +* `xxxTemplate@2x.png` + +## Methods + +`NativeImage` 클래스는 다음과 같은 메서드를 가지고 있습니다: + +### `NativeImage.createEmpty()` + +빈 `NativeImage` 인스턴스를 만듭니다. + +### `NativeImage.createFromPath(path)` + +* `path` String + +`path`로부터 이미지를 로드하여 새로운 `NativeImage` 인스턴스를 만듭니다. + +### `NativeImage.createFromBuffer(buffer[, scaleFactor])` + +* `buffer` [Buffer][buffer] +* `scaleFactor` Double (optional) + +`buffer`로부터 이미지를 로드하여 새로운 `NativeImage` 인스턴스를 만듭니다. `scaleFactor`는 1.0이 기본입니다. + +### `NativeImage.createFromDataUrl(dataUrl)` + +* `dataUrl` String + +`dataUrl`로부터 이미지를 로드하여 새로운 `NativeImage` 인스턴스를 만듭니다. + +## Instance Methods + +`nativeImage` 인스턴스 객체에서 사용할 수 있는 메서드 입니다: + +```javascript +var NativeImage = require('native-image'); + +var image = NativeImage.createFromPath('/Users/somebody/images/icon.png'); +``` + +### `image.toPng()` + +`PNG` 이미지를 인코딩한 데이터를 [Buffer][buffer]로 반환합니다. + +### `image.toJpeg(quality)` + +* `quality` Integer 0 - 100 사이의 값 (**required**) + +`JPEG` 이미지를 인코딩한 데이터를 [Buffer][buffer]로 반환합니다. + +### `image.toDataUrl()` + +이미지를 data URL로 반환합니다. + +### `image.isEmpty()` + +이미지가 비었는지 확인합니다. + +### `image.getSize()` + +이미지의 사이즈를 반환합니다. + +### `image.setTemplateImage(option)` + +* `option` Boolean + +해당 이미지를 템플릿 이미지로 설정합니다. + +### `image.isTemplateImage()` + +이미지가 템플릿 이미지인지 확인합니다. + +[buffer]: https://iojs.org/api/buffer.html#buffer_class_buffer diff --git a/docs-translations/ko/api/power-monitor.md b/docs-translations/ko/api/power-monitor.md new file mode 100644 index 000000000000..990bf54e48c0 --- /dev/null +++ b/docs-translations/ko/api/power-monitor.md @@ -0,0 +1,37 @@ +# power-monitor + +`power-monitor` 모듈은 PC의 파워 상태를 나타냅니다. (주로 노트북 등에서 사용됩니다) +이 모듈은 메인 프로세스에서만 사용할 수 있으며, (remote 모듈(RPC)을 사용해도 작동이 됩니다) +메인 프로세스의 `app` 모듈에서 `ready` 이벤트를 호출하기 전까지 사용할 수 없습니다. + +예제: + +```javascript +var app = require('app'); + +app.on('ready', function() { + require('power-monitor').on('suspend', function() { + console.log('절전모드로 진입합니다!'); + }); +}); +``` + +## Events + +`power-monitor` 모듈은 다음과 같은 이벤트를 가지고 있습니다: + +## Event: `suspend` + +시스템이 절전모드로 진입할 때 발생하는 이벤트입니다. + +## Event: `resume` + +시스템의 절전모드가 해제될 때 발생하는 이벤트입니다. + +## Event: `on-ac` + +시스템이 AC 어뎁터 충전기를 사용하기 시작할 때 발생하는 이벤트입니다. + +## Event: `on-battery` + +시스템이 배터리를 사용하기 시작할 때 발생하는 이벤트입니다. diff --git a/docs-translations/ko/api/power-save-blocker.md b/docs-translations/ko/api/power-save-blocker.md new file mode 100644 index 000000000000..0521a82b0e70 --- /dev/null +++ b/docs-translations/ko/api/power-save-blocker.md @@ -0,0 +1,46 @@ +# powerSaveBlocker + +`power-save-blocker` 모듈은 시스템이 저전력(슬립) 모드로 진입하는 것을 막고 앱 시스템과 화면이 항상 활성화 상태를 유지할 수 있도록 하는 몇가지 유틸리티를 제공하는 모듈입니다. + +예제: + +```javascript +var powerSaveBlocker = require('power-save-blocker'); + +var id = powerSaveBlocker.start('prevent-display-sleep'); +console.log(powerSaveBlocker.isStarted(id)); + +powerSaveBlocker.stop(id); +``` + +## Methods + +`powerSaveBlocker` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `powerSaveBlocker.start(type)` + +* `type` String - Power save blocker 종류 + * `prevent-app-suspension` - 저전력 모드 등으로 인한 어플리케이션 작동 중단을 방지합니다. + 시스템을 항시 활성화 상태로 만듭니다. 하지만 화면은 자동으로 꺼질 수 있습니다. 사용 예시: 파일 다운로드, 음악 재생 등. + * `prevent-display-sleep`- 슬립 모드 등으로 인한 어플리케이션의 작동 중단을 방지합니다. + 시스템을 항시 활성화 상태로 만들고 슬립 모드(화면 꺼짐)를 방지합니다. 사용 예시: 비디오 재생 등. + +시스템이 저전력 모드(슬립)로 진입하는 것을 막기 시작합니다. 정수로 된 식별 ID를 반환합니다. + +**참고:** `prevent-display-sleep` 모드는 `prevent-app-suspension` 보다 우선 순위가 높습니다. +두 모드 중 가장 높은 우선 순위의 모드만 작동합니다. 다시 말해 `prevent-display-sleep` 모드는 언제나 `prevent-app-suspension` 모드의 효과를 덮어씌웁니다. + +예를 들어 A-요청이 `prevent-app-suspension` 모드를 사용하고 B-요청이 `prevent-display-sleep`를 사용하는 API 호출이 있었다 치면 +`prevent-display-sleep` 모드를 사용하는 B의 작동이 중단(stop)되기 전까지 작동하다 B가 중단되면 `prevent-app-suspension` 모드를 사용하는 A가 작동하기 시작합니다. + +### `powerSaveBlocker.stop(id)` + +* `id` Integer - `powerSaveBlocker.start`로 부터 반환되는 power save blocker 식별 ID. + +설정한 power save blocker를 중지합니다. + +### `powerSaveBlocker.isStarted(id)` + +* `id` Integer - `powerSaveBlocker.start`로 부터 반환되는 power save blocker 식별 ID. + +지정한 id의 `powerSaveBlocker`가 실행 중인지 확인합니다. diff --git a/docs-translations/ko/api/process.md b/docs-translations/ko/api/process.md new file mode 100644 index 000000000000..f557f8cc6be5 --- /dev/null +++ b/docs-translations/ko/api/process.md @@ -0,0 +1,22 @@ +# process + +Electron의 `process` 객체는 기존의 node와는 달리 약간의 차이점이 있습니다: + +* `process.type` String - 프로세스의 타입, `browser` (메인 프로세스) 또는 `renderer`가 됩니다. +* `process.versions['electron']` String - Electron의 버전. +* `process.versions['chrome']` String - Chromium의 버전. +* `process.resourcesPath` String - JavaScript 소스코드의 경로. + +## Methods + +`process` 객체는 다음과 같은 메서드를 가지고 있습니다: + +### `process.hang()` + +현재 프로세스의 주 스레드를 중단시킵니다. + +### `process.setFdLimit(maxDescriptors)` _OS X_ _Linux_ + +* `maxDescriptors` Integer + +`maxDescriptors`에 file descriptor 소프트 리미트를 설정하거나 OS 하드 리미트를 설정합니다. 값은 현재 프로세스에 대해 낮은 값이어야 합니다. diff --git a/docs-translations/ko/api/protocol.md b/docs-translations/ko/api/protocol.md new file mode 100644 index 000000000000..d79963239585 --- /dev/null +++ b/docs-translations/ko/api/protocol.md @@ -0,0 +1,148 @@ +# protocol + +`protocol` 모듈은 이미 있는 프로토콜의 동작을 가로채거나 새로운 프로토콜을 만들 수 있는 기능을 제공합니다. + +다음 예제는 `file://` 프로토콜과 비슷한 일을 하는 커스텀 프로토콜을 설정합니다: + +```javascript +var app = require('app'); +var path = require('path'); + +app.on('ready', function() { + var protocol = require('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` 이벤트가 발생한 이후에만 사용할 수 있습니다. + +## Methods + +`protocol` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `protocol.registerStandardSchemes(schemes)` + +* `schemes` Array - 표준 스킴으로 등록할 커스텀 스킴 리스트 + +표준 `scheme`의 형식은 RFC 3986 [일반 URI 구문](https://tools.ietf.org/html/rfc3986#section-3) 표준을 따릅니다. +이 형식은 `file:`과 `filesystem:`을 포함합니다. + +### `protocol.registerFileProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +`scheme`에 파일을 응답으로 보내는 프로토콜을 등록합니다. +`handler`는 `scheme`와 함께 `request`가 생성될 때 `handler(request, callback)` 형식으로 호출됩니다. +`completion` 콜백은 `scheme`가 성공적으로 등록되었을 때 `completion(null)` 형식으로 호출되고 +등록에 실패했을 땐 `completion(error)` 형식으로 에러 내용을 담아 호출됩니다. + +`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)에서 확인할 수 있습니다. + +기본적으로 `scheme`은 `http:`와 같이 처리됩니다. `file:`과 같이 "일반적인 URI 문법"과는 다르게 인식되는 프로토콜은 +`protocol.registerStandardSchemes`을 사용하여 표준 스킴으로 처리되도록 할 수 있습니다. + +### `protocol.registerBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +`scheme`에 `Buffer`를 응답으로 보내는 프로토콜을 등록합니다. +반드시 `Buffer` 또는 `data`, `mimeType`, `chart` 속성을 포함한 객체 중 하나를 인자에 포함하여 `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 (optional) + +`scheme`에 `문자열`을 응답으로 보내는 프로토콜을 등록합니다. +반드시 `문자열` 또는 `data`, `mimeType`, `chart` 속성을 포함한 객체 중 하나를 인자에 포함하여 `callback`을 호출해야 합니다. + +### `protocol.registerHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +`scheme`에 HTTP 요청을 응답으로 보내는 프로토콜을 등록합니다. +반드시 `url`, `method`, `referer`, `session` 속성을 포함하는 객체를 인자에 포함하여 `callback`을 호출해야 합니다. + +기본적으로 HTTP 요청은 현재 세션을 재사용합니다. 만약 서로 다른 세션에 요청을 보내고 싶으면 `session`을 `null`로 지정해야 합니다. + +### `protocol.unregisterProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function (optional) + +`scheme`의 커스텀 프로토콜 등록을 해제합니다. + +### `protocol.isProtocolHandled(scheme, callback)` + +* `scheme` String +* `callback` Function + +`scheme`에 동작(handler)이 등록되어 있는지 여부를 확인합니다. `callback`으로 결과(boolean)가 반환됩니다. + +### `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` 프로토콜을 가로채고 `handler`를 문자열 전송에 대한 새로운 동작으로 사용합니다. + +### `protocol.interceptBufferProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +`scheme` 프로토콜을 가로채고 `handler`를 `Buffer` 전송에 대한 새로운 동작으로 사용합니다. + +### `protocol.interceptHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `completion` Function (optional) + +`scheme` 프로토콜을 가로채고 `handler`를 HTTP 프로토콜의 요청에 대한 새로운 동작으로 사용합니다. + +### `protocol.uninterceptProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function (optional) + +가로챈 `scheme`를 삭제하고 기본 핸들러로 복구합니다. diff --git a/docs-translations/ko/api/remote.md b/docs-translations/ko/api/remote.md new file mode 100644 index 000000000000..15c82ff0d21e --- /dev/null +++ b/docs-translations/ko/api/remote.md @@ -0,0 +1,127 @@ +# remote + +`remote` 모듈은 메인 프로세스와 랜더러 프로세스(웹 페이지) 사이의 inter-process (IPC) 통신을 간단하게 추상화 한 모듈입니다. + +Electron의 랜더러 프로세스에선 GUI와 관련 없는 모듈만 사용할 수 있습니다. +기본적으로 랜더러 프로세스에서 메인 프로세스의 API를 사용하려면 메인 프로세스와 inter-process 통신을 해야 합니다. +하지만 `remote` 모듈을 사용하면 따로 inter-process 통신을 하지 않고 직접 명시적으로 모듈을 사용할 수 있습니다. +Java의 [RMI](http://en.wikipedia.org/wiki/Java_remote_method_invocation)와 개념이 비슷합니다. + +다음 예제는 랜더러 프로세스에서 브라우저 창을 만드는 예제입니다: + +```javascript +var remote = require('remote'); +var BrowserWindow = remote.require('browser-window'); + +var win = new BrowserWindow({ width: 800, height: 600 }); +win.loadUrl('https://github.com'); +``` + +**참고:** 반대로 메인 프로세스에서 랜더러 프로세스에 접근 하려면 [webContents.executeJavascript](web-contents.md#webcontentsexecutejavascriptcode-usergesture) 메서드를 사용하면 됩니다. + +## Remote 객체 + +`remote` 모듈로부터 반환된 각 객체(메서드 포함)는 메인 프로세스의 객체를 추상화 한 객체입니다. (우리는 그것을 remote 객체 또는 remote 함수라고 부릅니다) +Remote 모듈의 메서드를 호출하거나, 객체에 접근하거나, 생성자로 객체를 생성하는 등의 작업은 실질적으로 동기형 inter-process 메시지를 보냅니다. + +위의 예제에서 사용한 두 `BrowserWindow`와 `win`은 remote 객체입니다. 그리고 `new BrowserWindow`이 생성하는 `BrowserWindow` 객체는 랜더러 프로세스에서 생성되지 않습니다. +대신에 이 `BrowserWindow` 객체는 메인 프로세스에서 생성되며 랜더러 프로세스에 `win` 객체와 같이 이에 대응하는 remote 객체를 반환합니다. + +## Remote 객체의 생명 주기 + +Electron은 랜더러 프로세스의 remote 객체가 살아있는 한(다시 말해서 GC(garbage collection)가 일어나지 않습니다) 대응하는 메인 프로세스의 객체는 릴리즈되지 않습니다. +Remote 객체가 GC 되려면 대응하는 메인 프로세스 내부 객체의 참조가 해제되어야만 합니다. + +만약 remote 객체가 랜더러 프로세스에서 누수가 생겼다면 (예시: 맵에 저장하고 할당 해제하지 않음) 대응하는 메인 프로세스의 객체도 누수가 생깁니다. +그래서 remote 객체를 사용할 땐 메모리 누수가 생기지 않도록 매우 주의해서 사용해야 합니다. + +참고로 문자열, 숫자와 같은 원시 값 타입은 복사에 의한 참조로 전달됩니다. + +## 메인 프로세스로 콜백 넘기기 + +메인 프로세스의 코드는 `remote` 모듈을 통해 랜더러 프로세스가 전달하는 콜백 함수를 받을 수 있습니다. +하지만 이 작업은 반드시 주의를 기울여 사용해야 합니다. + +첫째, 데드락을 피하기 위해 메인 프로세스로 전달된 콜백들은 비동기로 호출됩니다. +이러한 이유로 메인 프로세스로 전달된 콜백들의 반환 값을 내부 함수에서 언제나 정상적으로 받을 것이라고 예측해선 안됩니다. + +예를 들어 메인 프로세스에서 `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] +``` + +보다시피 랜더러 콜백의 동기 반환 값은 예상되지 않은 처리입니다. +그리고 메인 프로세스에서 처리한 함수의 반환 값과 일치하지 않습니다. + +둘째, 콜백들은 메인 프로세스로 전달, 호출된 이후에도 자동으로 함수의 참조가 릴리즈 되지 않습니다. +함수 참조는 메인 프로세스에서 GC가 일어나기 전까지 계속 프로세스에 남아있게 됩니다. + +다음 코드를 보면 느낌이 올 것입니다. 이 예제는 remote 객체에 `close` 이벤트 콜백을 설치합니다: + +```javascript +var remote = require('remote'); + +remote.getCurrentWindow().on('close', function() { + // blabla... +}); +``` + +하지만 이 코드 처럼 이벤트를 명시적으로 제거하지 않는 이상 콜백 함수의 참조가 계속해서 메인 프로세스에 남아있게 됩니다. +만약 명시적으로 콜백을 제거하지 않으면 매 번 창을 새로고침 할 때마다 콜백을 새로 설치합니다. +게다가 이전 콜백이 제거되지 않고 계속해서 쌓이면서 메모리 누수가 발생합니다. + +설상가상으로 이전에 설치된 콜백의 콘텍스트가 릴리즈 되고 난 후(예: 페이지 새로고침) `close` 이벤트가 발생하면 예외가 발생하고 메인 프로세스가 작동 중지됩니다. + +이러한 문제를 피하려면 랜더러 프로세스에서 메인 프로세스로 넘긴 함수의 참조를 사용 후 확실하게 제거해야 합니다. +작업 후 이벤트 콜백을 포함하여 책임 있게 함수의 참조를 제거하거나 메인 프로세스에서 랜더러 프로세스가 종료될 때 내부적으로 함수 참조를 제거하도록 설계해야 합니다. + +## Methods + +`remote` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `remote.require(module)` + +* `module` String + +메인 프로세스의 `require(module)` API를 실행한 후 결과 객체를 반환합니다. + +### `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')`와 같습니다. 하지만 캐시 됩니다. diff --git a/docs-translations/ko/api/screen.md b/docs-translations/ko/api/screen.md new file mode 100644 index 000000000000..26975cbb1e27 --- /dev/null +++ b/docs-translations/ko/api/screen.md @@ -0,0 +1,120 @@ +# screen + +`screen` 모듈은 화면 크기, 디스플레이, 커서 위치 등등의 다양한 정보를 가져옵니다. +이 모듈은 `app` 모듈의 `ready` 이벤트가 발생하기 전까지 사용할 수 없습니다. + +`screen`은 [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)를 상속 받았습니다. + +**참고:** 랜더러 / DevTools에선 이미 DOM 속성이 `window.screen`을 가지고 있으므로 `screen = require('screen')` 형식으로 모듈을 사용할 수 없습니다. +밑의 예제와 같이 `atomScreen` 같은 이름으로 모듈 이름을 대체하여 사용해야 합니다. + +다음 예제는 화면 전체를 채우는 윈도우 창을 생성합니다: + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +var mainWindow; + +app.on('ready', function() { + var atomScreen = require('screen'); + var size = atomScreen.getPrimaryDisplay().workAreaSize; + mainWindow = new BrowserWindow({ width: size.width, height: size.height }); +}); +``` + +다음 예제는 확장 디스플레이에 윈도우를 생성합니다: + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +var mainWindow; + +app.on('ready', function() { + var atomScreen = require('screen'); + var displays = atomScreen.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, + }); + } +}); +``` + +## Events + +`screen` 모듈은 다음과 같은 이벤트를 가지고 있습니다: + +### Event: 'display-added' + +Returns: + +* `event` Event +* `newDisplay` Object + +새로운 디스플레이가 추가되면 발생하는 이벤트입니다. + +### Event: 'display-removed' + +Returns: + +* `event` Event +* `oldDisplay` Object + +기존의 디스플레이가 제거되면 발생하는 이벤트입니다. + +### Event: 'display-metrics-changed' + +Returns: + +* `event` Event +* `display` Object +* `changedMetrics` Array + +`display`에서 하나 또는 다수의 매트릭스가 변경될 때 발생하는 이벤트입니다. +`changedMetrics`는 변경에 대한 정보를 담은 문자열의 배열입니다. +`bounds`, `workArea`, `scaleFactor`, `rotation`등이 변경될 수 있습니다. + +## Methods + +`screen` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `screen.getCursorScreenPoint()` + +현재 마우스 포인터의 절대 위치를 반환합니다. + +### `screen.getPrimaryDisplay()` + +기본 디스플레이를 반환합니다. + +### `screen.getAllDisplays()` + +사용 가능한 모든 디스플레이를 배열로 반환합니다. + +### `screen.getDisplayNearestPoint(point)` + +* `point` Object + * `x` Integer + * `y` Integer + +지정한 좌표에 가까운 디스플레이를 반환합니다. + +### `screen.getDisplayMatching(rect)` + +* `rect` Object + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +지정한 범위에 가장 가깝게 교차한 디스플레이를 반환합니다. diff --git a/docs-translations/ko/api/session.md b/docs-translations/ko/api/session.md new file mode 100644 index 000000000000..cb912aabb100 --- /dev/null +++ b/docs-translations/ko/api/session.md @@ -0,0 +1,187 @@ +# session + +`session` 객체는 [`BrowserWindow`](browser-window.md)의 [`webContents`](web-contents.md)의 프로퍼티입니다. +다음과 같이 `BrowserWindow` 인스턴스에서 접근할 수 있습니다: + +```javascript +var BrowserWindow = require('browser-window'); + +var win = new BrowserWindow({ width: 800, height: 600 }); +win.loadUrl("http://github.com"); + +var session = win.webContents.session +``` + +## Events + +### Event: 'will-download' + +* `event` Event +* `item` Object + * `url` String + * `filename` String + * `mimeType` String + * `hasUserGesture` Boolean +* `webContents` [WebContents](web-contents.md) + +Electron의 `webContents`에서 `item`을 다운로드할 때 발생하는 이벤트입니다. + +`event.preventDefault()` 메서드를 호출하면 다운로드를 취소합니다. + +```javascript +session.on('will-download', function(event, item, webContents) { + event.preventDefault(); + require('request')(item.url, function(data) { + require('fs').writeFileSync('/somewhere', data); + }); +}); +``` + +## Methods + +`session` 객체는 다음과 같은 메서드와 속성을 가지고 있습니다: + +### `session.cookies` + +`cookies` 속성은 쿠키를 조작하는 방법을 제공합니다. 예를 들어 다음과 같이 할 수 있습니다: + +```javascript +var BrowserWindow = require('browser-window'); + +var win = new BrowserWindow({ width: 800, height: 600 }); + +win.loadUrl('https://github.com'); + +win.webContents.on('did-finish-load', function() { + // 모든 쿠키를 가져옵니다. + win.webContents.session.cookies.get({}, function(error, cookies) { + if (error) throw error; + console.log(cookies); + }); + + // Url에 관련된 쿠키를 모두 가져옵니다. + win.webContents.session.cookies.get({ url : "http://www.github.com" }, + function(error, cookies) { + if (error) throw error; + console.log(cookies); + }); + + // 지정한 쿠키 데이터를 설정합니다. + // 동일한 쿠키가 있으면 해당 쿠키를 덮어씁니다. + win.webContents.session.cookies.set( + { url : "http://www.github.com", name : "dummy_name", value : "dummy"}, + function(error, cookies) { + if (error) throw error; + console.log(cookies); + }); +}); +``` + +### `session.cookies.get(details, callback)` + +`details` Object, properties: + +* `url` String - `url`에 관련된 쿠키를 가져옵니다. 이 속성을 비워두면 모든 url의 쿠키를 가져옵니다. +* `name` String - 이름을 기준으로 쿠키를 필터링합니다. +* `domain` String - `domain`과 일치하는 도메인과 서브 도메인에 대한 쿠키를 가져옵니다. +* `path` String - `path`와 일치하는 경로에 대한 쿠키를 가져옵니다. +* `secure` Boolean - 보안 속성을 기준으로 쿠키를 필터링합니다. +* `session` Boolean - 세션 또는 영구 쿠키를 필터링합니다. + +* `callback` Function - function(error, cookies) +* `error` Error +* `cookies` Array - `cookie` 객체의 배열, 속성은 다음과 같습니다: + * `name` String - 쿠키의 이름. + * `value` String - 쿠키의 값. + * `domain` String - 쿠키의 도메인. + * `host_only` String - 쿠키가 호스트 전용인가에 대한 여부. + * `path` String - 쿠키의 경로. + * `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부. (일반적으로 HTTPS) + * `http_only` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부. + * `session` Boolean - 쿠키가 세션 쿠키 또는 만료일이 있는 영구 쿠키인지에 대한 여부. + * `expirationDate` Double - (Option) UNIX 시간으로 표시되는 쿠키의 만료일에 대한 초 단위 시간. 세션 쿠키는 지원되지 않음. + +### `session.cookies.set(details, callback)` + +`details` Object, properties: + +* `url` String - `url`에 관련된 쿠키를 가져옵니다. +* `name` String - 쿠키의 이름입니다. 기본적으로 비워두면 생략됩니다. +* `value` String - 쿠키의 값입니다. 기본적으로 비워두면 생략됩니다. +* `domain` String - 쿠키의 도메인입니다. 기본적으로 비워두면 생략됩니다. +* `path` String - 쿠키의 경로입니다. 기본적으로 비워두면 생략됩니다. +* `secure` Boolean - 쿠키가 안전한 것으로 표시되는지에 대한 여부입니다. 기본값은 false입니다. +* `session` Boolean - 쿠키가 HttpOnly로 표시되는지에 대한 여부입니다. 기본값은 false입니다. +* `expirationDate` Double - UNIX 시간으로 표시되는 쿠키의 만료일에 대한 초 단위 시간입니다. 생략하면 쿠키는 세션 쿠키가 됩니다. + +* `callback` Function - function(error) + * `error` Error + +### `session.cookies.remove(details, callback)` + +* `details` Object, proprties: + * `url` String - 쿠키와 관련된 URL입니다. + * `name` String - 지울 쿠키의 이름입니다. +* `callback` Function - function(error) + * `error` Error + +### `session.clearCache(callback)` + +* `callback` Function - 작업이 완료되면 호출됩니다. + +세션의 HTTP 캐시를 비웁니다. + +### `session.clearStorageData([options, ]callback)` + +* `options` Object (optional), proprties: + * `origin` String - `scheme://host:port`와 같은 `window.location.origin` 규칙을 따르는 origin 문자열. + * `storages` Array - 비우려는 스토리지의 종류, 다음과 같은 타입을 포함할 수 있습니다: + `appcache`, `cookies`, `filesystem`, `indexdb`, `local storage`, + `shadercache`, `websql`, `serviceworkers` + * `quotas` Array - 비우려는 할당의 종류, 다음과 같은 타입을 포함할 수 있습니다: + `temporary`, `persistent`, `syncable`. +* `callback` Function - 작업이 완료되면 호출됩니다. + +웹 스토리지의 데이터를 비웁니다. + +### `session.setProxy(config, callback)` + +* `config` String +* `callback` Function - 작업이 완료되면 호출됩니다. + +세션에 사용할 프록시 `config`를 분석하고 프록시를 적용합니다. + +``` +config = scheme-proxies[";"] +scheme-proxies = ["="] +url-scheme = "http" | "https" | "ftp" | "socks" +proxy-uri-list = [","] +proxy-uri = ["://"][":"] + + 예시: + "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. + "foopy:80,bar,direct://" -- use HTTP proxy "foopy:80" for all URLs, + failing over to "bar" if "foopy:80" is + unavailable, and after that using no + proxy. + "socks4://foopy" -- use SOCKS v4 proxy "foopy:1080" for all + URLs. + "http=foopy,socks5://bar.com -- use HTTP proxy "foopy" for http URLs, + and fail over to the SOCKS5 proxy + "bar.com" if "foopy" is unavailable. + "http=foopy,direct:// -- use HTTP proxy "foopy" for http URLs, + and use no proxy if "foopy" is + unavailable. + "http=foopy;socks=foopy2 -- use HTTP proxy "foopy" for http URLs, + and use socks4://foopy2 for all other + URLs. +``` + +### `session.setDownloadPath(path)` + +* `path` String - 다운로드 위치 + +다운로드 저장 위치를 지정합니다. 기본 다운로드 위치는 각 어플리케이션 데이터 디렉터리의 `Downloads` 폴더입니다. diff --git a/docs-translations/ko/api/shell.md b/docs-translations/ko/api/shell.md new file mode 100644 index 000000000000..a84436f78071 --- /dev/null +++ b/docs-translations/ko/api/shell.md @@ -0,0 +1,46 @@ +# shell + +`shell` 모듈은 데스크톱 환경 통합에 관련한 유틸리티를 제공하는 모듈입니다. + +다음 예제는 설정된 URL을 유저의 기본 브라우저로 엽니다: + +```javascript +var shell = require('shell'); +shell.openExternal('https://github.com'); +``` + +## Methods + +`shell` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `shell.showItemInFolder(fullPath)` + +* `fullPath` String + +지정한 파일을 탐색기에서 보여줍니다. 가능한 경우 탐색기 내에서 파일을 선택합니다. + +### `shell.openItem(fullPath)` + +* `fullPath` String + +지정한 파일을 데스크톱 기본 프로그램으로 엽니다. + +### `shell.openExternal(url)` + +* `url` String + +제공된 외부 프로토콜 URL을 기반으로 데스크톱의 기본 프로그램으로 엽니다. (예를 들어 mailto: URL은 유저의 기본 이메일 에이전트로 URL을 엽니다.) + +역주: 폴더는 'file:\\\\C:\\'와 같이 지정하여 열 수 있습니다. (`\\`로 경로를 표현한 이유는 Escape 문자열을 참고하세요.) + +### `shell.moveItemToTrash(fullPath)` + +* `fullPath` String + +Move the given file to trash and returns boolean status for the operation. + +지정한 파일을 휴지통으로 이동합니다. 작업의 성공여부를 boolean 형으로 리턴합니다. + +### `shell.beep()` + +비프음을 재생합니다. diff --git a/docs-translations/ko/api/synopsis.md b/docs-translations/ko/api/synopsis.md new file mode 100644 index 000000000000..3b1633da6d5d --- /dev/null +++ b/docs-translations/ko/api/synopsis.md @@ -0,0 +1,40 @@ +# 개요 + +Electron은 모든 [Node.js의 built-in 모듈](http://nodejs.org/api/)과 third-party node 모듈을 완벽하게 지원합니다. ([네이티브 모듈](../tutorial/using-native-node-modules.md) 포함) + +Electron은 네이티브 데스크톱 어플리케이션을 개발 할 수 있도록 추가적인 built-in 모듈을 제공합니다. +몇몇 모듈은 메인 프로세스에서만 사용할 수 있고 어떤 모듈은 랜더러 프로세스(웹 페이지)에서만 사용할 수 있습니다. +또한 두 프로세스 모두 사용할 수 있는 모듈도 있습니다. + +기본적인 규칙으로 [GUI](https://en.wikipedia.org/wiki/Graphical_user_interface)와 저 수준 시스템에 관련된 모듈들은 오직 메인 프로세스에서만 사용할 수 있습니다. +[메인 프로세스 vs. 랜더러 프로세스](../tutorial/quick-start.md#메인 프로세스) 컨셉에 익숙해야 이 모듈들을 사용하기 쉬우므로 관련 문서를 읽어 보는 것을 권장합니다. + +메인 프로세스 스크립트는 일반 Node.js 스크립트와 비슷합니다: + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +var window = null; + +app.on('ready', function() { + window = new BrowserWindow({width: 800, height: 600}); + window.loadUrl('https://github.com'); +}); +``` + +랜더러 프로세스도 예외적인 node module들을 사용할 수 있다는 점을 제외하면 일반 웹 페이지와 크게 다를게 없습니다: + +```html + + + + + + +``` + +어플리케이션을 실행하려면 [앱 실행하기](../tutorial/quick-start.md#앱 실행하기) 문서를 참고하기 바랍니다. diff --git a/docs-translations/ko/api/tray.md b/docs-translations/ko/api/tray.md new file mode 100644 index 000000000000..beccf6d8b34a --- /dev/null +++ b/docs-translations/ko/api/tray.md @@ -0,0 +1,180 @@ +# Tray + +`Tray`는 OS의 알림 영역에 아이콘을 표시합니다. 보통 컨텍스트 메뉴(context menu)를 같이 사용합니다. + +```javascript +var app = require('app'); +var Menu = require('menu'); +var Tray = require('tray'); + +var appIcon = null; +app.on('ready', function(){ + appIcon = new Tray('/path/to/my/icon'); // 현재 어플리케이션 디렉터리를 기준으로 하려면 `__dirname + '/images/tray.png'` 형식으로 입력해야합니다. + var contextMenu = Menu.buildFromTemplate([ + { label: 'Item1', type: 'radio' }, + { label: 'Item2', type: 'radio' }, + { label: 'Item3', type: 'radio', checked: true }, + { label: 'Item4', type: 'radio' } + ]); + appIcon.setToolTip('이것은 나의 어플리케이션 입니다!'); + appIcon.setContextMenu(contextMenu); +}); + +``` + +__플랫폼별 한계:__ + +* Linux에서는 앱 알림 표시기(app indicator)가 지원되면 해당 기능을 사용합니다. 만약 지원하지 않으면 `GtkStatusIcon`을 대신 사용합니다. +* Linux 배포판이 앱 알림 표시기만 지원하고 있다면 `libappindicator1`를 설치하여 트레이 아이콘이 작동하도록 만들 수 있습니다. +* 앱 알림 표시기는 컨텍스트 메뉴를 가지고 있을 때만 보입니다. +* Linux에서 앱 알림 표시기가 사용될 경우, `clicked` 이벤트는 무시됩니다. + +이러한 이유로 Tray API가 모든 플랫폼에서 똑같이 작동하게 하고 싶다면 `clicked` 이벤트에 의존해선 안됩니다. +그리고 언제나 컨텍스트 메뉴를 포함해야 합니다. + +## Class: Tray + +`Tray`는 [EventEmitter][event-emitter]를 상속 받았습니다. + +### `new Tray(image)` + +* `image` [NativeImage](native-image.md) + +전달된 `image`를 이용하여 트레이 아이콘을 만듭니다. + +## Events + +`Tray` 모듈은 다음과 같은 이벤트를 가지고 있습니다: + +**참고:** 몇가지 이벤트는 특정한 플랫폼에서만 작동합니다. + +### Event: 'clicked' + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - 트레이 아이콘의 범위 + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +트레이 아이콘이 클릭될 때 발생하는 이벤트입니다. + +__주의:__ `bounds`는 OS X 와 Windows에서만 작동합니다. + +### Event: 'right-clicked' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - 트레이 아이콘의 범위 + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +트레이 아이콘을 오른쪽 클릭될 때 호출 됩니다. + +### Event: 'double-clicked' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - 트레이 아이콘의 범위 + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +트레이 아이콘이 더블 클릭될 때 발생하는 이벤트입니다. + +### Event: 'balloon-show' _Windows_ + +알림풍선이 보여질 때 발생하는 이벤트입니다. + +### Event: 'balloon-clicked' _Windows_ + +알림풍선이 클릭될 때 발생하는 이벤트입니다. + +### Event: 'balloon-closed' _Windows_ + +알림풍선이 시간이 지나 사라지거나 유저가 클릭하여 닫을 때 발생하는 이벤트입니다. + +### Event: 'drop-files' _OS X_ + +* `event` +* `files` Array - 드롭된 파일의 경로 + +트레이 아이콘에 파일이 드롭되면 발생하는 이벤트입니다. + +## Methods + +`Tray` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +**참고:** 몇가지 메서드는 특정한 플랫폼에서만 작동합니다. + +### `Tray.destroy()` + +트레이 아이콘을 즉시 삭제시킵니다. + +### `Tray.setImage(image)` + +* `image` [NativeImage](native-image.md) + +`image`를 사용하여 트레이 아이콘의 이미지를 설정합니다. + +### `Tray.setPressedImage(image)` _OS X_ + +* `image` [NativeImage](native-image.md) + +`image`를 사용하여 트레이 아이콘이 눌렸을 때의 이미지를 설정합니다. + +### `Tray.setToolTip(toolTip)` + +* `toolTip` String + +트레이 아이콘의 툴팁 텍스트를 설정합니다. + +### `Tray.setTitle(title)` _OS X_ + +* `title` String + +상태바에서 트레이 아이콘 옆에 표시되는 제목 텍스트를 설정합니다. + +### `Tray.setHighlightMode(highlight)` _OS X_ + +* `highlight` Boolean + +트레이 아이콘을 클릭했을 때 하이라이트 될지 설정합니다. + +### `Tray.displayBalloon(options)` _Windows_ + +* `options` Object + * `icon` [NativeImage](native-image.md) + * `title` String + * `content` String + +트레이에 알림풍선을 생성합니다. + +### `Tray.popContextMenu([position])` _OS X_ _Windows_ + +* `position` Object (optional) - 팝업 메뉴 위치 + * `x` Integer + * `y` Integer + +`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/ko/api/web-frame.md b/docs-translations/ko/api/web-frame.md new file mode 100644 index 000000000000..d09114e559c8 --- /dev/null +++ b/docs-translations/ko/api/web-frame.md @@ -0,0 +1,79 @@ +# webFrame + +`web-frame` 모듈은 현재 웹 페이지의 랜더링 상태를 설정 할 수 있도록 관련 유틸리티를 제공하는 모듈입니다. + +다음 예제는 현재 페이지를 200% 줌 합니다: + +```javascript +var webFrame = require('web-frame'); + +webFrame.setZoomFactor(2); +``` + +## Methods + +`web-frame` 모듈은 다음과 같은 메서드를 가지고 있습니다: + +### `webFrame.setZoomFactor(factor)` + +* `factor` Number - Zoom 값 + +지정한 값으로 페이지를 줌 합니다. 줌 값은 퍼센트를 100으로 나눈 값입니다. (예시: 300% = 3.0) + +### `webFrame.getZoomFactor()` + +현재 줌 값을 반환합니다. + +### `webFrame.setZoomLevel(level)` + +* `level` Number - Zoom level + +지정한 레벨로 줌 레벨을 변경합니다. 0은 "기본 크기" 입니다. +그리고 각각 레벨 값을 올리거나 내릴 때마다 20%씩 커지거나 작아지고 기본 크기의 50%부터 300%까지 조절 제한이 있습니다. + +### `webFrame.getZoomLevel()` + +현재 줌 레벨을 반환합니다. + +### `webFrame.setZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +줌 레벨의 최대, 최소치를 지정합니다. + +### `webFrame.setSpellCheckProvider(language, autoCorrectWord, provider)` + +* `language` String +* `autoCorrectWord` Boolean +* `provider` Object + +Input field나 text area에 철자 검사(spell checking) 제공자를 설정합니다. + +`provider`는 반드시 전달된 단어의 철자가 맞았는지 검사하는 `spellCheck` 메소드를 가지고 있어야 합니다. + +[node-spellchecker][spellchecker]를 철자 검사 제공자로 사용하는 예제입니다: + +```javascript +require('web-frame').setSpellCheckProvider("en-US", true, { + spellCheck: function(text) { + return !(require('spellchecker').isMisspelled(text)); + } +}); +``` + +### `webFrame.registerUrlSchemeAsSecure(scheme)` + +* `scheme` String + +지정한 `scheme`을 보안 스킴으로 등록합니다. + +보안 스킴은 혼합된 컨텐츠 경고를 발생시키지 않습니다. 예를 들어 `https` 와 `data`는 네트워크 공격자로부터 손상될 가능성이 없기 때문에 보안 스킴이라고 할 수 있습니다. + +### `webFrame.registerUrlSchemeAsBypassingCsp(scheme)` + +* `scheme` String + +현재 페이지 컨텐츠의 보안 정책에 상관없이 이 `scheme`로부터 리소스가 로드됩니다. + +[spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs-translations/ko/api/web-view-tag.md b/docs-translations/ko/api/web-view-tag.md new file mode 100644 index 000000000000..90f5acb9381f --- /dev/null +++ b/docs-translations/ko/api/web-view-tag.md @@ -0,0 +1,534 @@ +# `` 태그 + +`guest` 컨텐츠(웹 페이지)를 Electron 앱 페이지에 삽입하기 위해 `webview` 태그를 사용할 수 있습니다. +게스트 컨텐츠는 `webview` 컨테이너에 담겨 대상 페이지에 삽입되고 해당 페이지에선 게스트 컨텐츠의 배치 및 렌더링 과정을 조작할 수 있습니다. + +`iframe`과는 달리 `webview`는 어플리케이션과 분리된 프로세스에서 작동합니다. +이는 웹 페이지와 같은 권한을 가지지 않고 앱과 임베디드(게스트) 컨텐츠간의 모든 상호작용이 비동기로 작동한다는 것을 의미합니다. +따라서 임베디드 컨텐츠로부터 어플리케이션을 안전하게 유지할 수 있습니다. + +## 예제 + +웹 페이지를 어플리케이션에 삽입하려면 `webview` 태그를 사용해 원하는 타겟 페이지에 추가하면 됩니다. (게스트 컨텐츠가 앱 페이지에 추가 됩니다) +간단한 예로 `webview` 태그의 `src` 속성에 페이지를 지정하고 css 스타일을 이용해서 컨테이너의 외관을 설정할 수 있습니다: + +```html + +``` + +게스트 컨텐츠를 조작하기 위해 자바스크립트로 `webview` 태그의 이벤트를 리스닝 하여 응답을 받을 수 있습니다. +다음 예제를 참고하세요: 첫번째 리스너는 페이지 로딩 시작시의 이벤트를 확인하고 두번째 리스너는 페이지의 로딩이 끝난시점을 확인합니다. +그리고 페이지를 로드하는 동안 "loading..." 메시지를 표시합니다. + +```html + +``` + +## 태그 속성 + +`webview` 태그는 다음과 같은 속성을 가지고 있습니다: + +### `src` + +```html + +``` + +지정한 URL을 페이지 소스로 사용합니다. 이 속성을 지정할 경우 `webview`의 최상위 페이지가 됩니다. + +`src`에 같은 페이지를 지정하면 페이지를 새로고침합니다. + +`src` 속성은 `data:text/plain,Hello, world!` 같은 data URL도 사용할 수 있습니다. + +### `autosize` + +```html + +``` + +"on" 으로 지정하면 `webview` 컨테이너는 `minwidth`, `minheight`, `maxwidth`, `maxheight`에 맞춰서 자동으로 크기를 조절합니다. +이 속성들은 `autosize`가 활성화되어있지 않는 한 프레임에 영향을 주지 않습니다. +`autosize`가 활성화 되어있으면 `webview` 컨테이너의 크기는 각각의 지정한 최대, 최소값에 따라 조절됩니다. + +### `nodeintegration` + +```html + +``` + +"on"으로 지정하면 `webview` 페이지 내에서 `require`와 `process 객체`같은 node.js API를 사용할 수 있습니다. +이를 지정하면 내부에서 로우레벨 리소스에 접근할 수 있습니다. + +### `plugins` + +```html + +``` + +"on"으로 지정하면 `webview` 내부에서 브라우저 플러그인을 사용할 수 있습니다. + +### `preload` + +```html + +``` + +페이지가 로드되기 전에 실행할 스크립트를 지정합니다. +스크립트 URL은 `file:` 또는 `asar:` 프로토콜 중 하나를 반드시 사용해야 합니다. +왜냐하면 페이지 내에서 `require`를 사용하여 스크립트를 로드하기 때문입니다. + +페이지가 nodeintegration을 활성화 하지 않아도 지정한 스크립트는 정상적으로 작동합니다. +하지만 스크립트 내에서 사용할 수 있는 global 객체는 스크립트 작동이 끝나면 삭제됩니다. + +### `httpreferrer` + +```html + +``` + +페이지의 referrer URL을 설정합니다. + +### `useragent` + +```html + +``` + +페이지의 `User-Agent`를 설정합니다. 페이지가 로드된 후엔 `setUserAgent` 메소드를 사용해서 변경할 수 있습니다. + +### `disablewebsecurity` + +```html + +``` + +"on"으로 지정하면 페이지의 웹 보안을 해제합니다. + +### `partition` + +```html + + +``` + +페이지에서 사용하는 세션을 설정합니다. +만약 `partition` 속성이 `persist:` 접두사를 시작하면 같은 `partition` 속성을 가진 앱 내 모든 페이지가 공유하는 영구 세션을 사용합니다. +`persist:` 접두사가 없을 경우 페이지는 인 메모리 세션을 사용합니다. +동일한 `partition`을 지정하여 다중 페이지에서 동일한 세션을 공유할 수 있도록 할 수 있습니다. +만약 `partition`이 지정되지 않으면 앱의 기본 세션을 사용합니다. + +이 값은 첫 탐색 이전에만 지정할 수 있습니다. +즉. 작동중인 랜더러 프로세스의 세션은 변경할 수 없습니다. +이후 이 값을 바꾸려고 시도하면 DOM 예외를 발생시킵니다. + +## Methods + +`webview` 태그는 다음과 같은 메서드를 가지고 있습니다: + +**참고:** 태그 객체의 메서드는 페이지 로드가 끝난 뒤에만 사용할 수 있습니다. + +**예제** +```javascript +webview.addEventListener("dom-ready", function() { + webview.openDevTools(); +}); +``` + +### `.getUrl()` + +페이지의 URL을 반환합니다. + +### `.getTitle()` + +페이지의 제목을 반환합니다. + +### `.isLoading()` + +페이지가 아직 리소스를 로딩하고 있는지 확인합니다. 불린 값을 반환합니다. + +### `.isWaitingForResponse()` + +페이지가 메인 리소스의 첫 응답을 기다리고 있는지 확인합니다. 불린 값을 반환합니다. + +### `.stop()` + +모든 탐색을 취소합니다. + +### `.reload()` + +페이지를 새로고침합니다. + +### `.reloadIgnoringCache()` + +캐시를 무시하고 페이지를 새로고침합니다. + +### `.canGoBack()` + +페이지 히스토리를 한 칸 뒤로 가기를 할 수 있는지 확인합니다. 불린 값을 반환합니다. + +### `.canGoForward()` + +페이지 히스토리를 한 칸 앞으로 가기를 할 수 있는지 확인합니다. 불린 값을 반환합니다. + +### `.canGoToOffset(offset)` + +* `offset` Integer + +페이지 히스토리를 `offset` 만큼 이동할 수 있는지 확인합니다. 불린값을 반환합니다. + +### `.clearHistory()` + +탐색 히스토리를 비웁니다. + +### `.goBack()` + +페이지 뒤로 가기를 실행합니다. + +### `.goForward()` + +페이지 앞으로 가기를 실행합니다. + +### `.goToIndex(index)` + +* `index` Integer + +페이지를 지정한 `index`로 이동합니다. + +### `.goToOffset(offset)` + +* `offset` Integer + +페이지로부터 `offset` 만큼 이동합니다. + +### `.isCrashed()` + +랜더러 프로세스가 크래시 됬는지 확인합니다. + +### `.setUserAgent(userAgent)` + +* `userAgent` String + +`User-Agent`를 지정합니다. + +### `.getUserAgent()` + +페이지의 `User-Agent 문자열`을 가져옵니다. + +### `.insertCSS(css)` + +* `css` String + +페이지에 CSS를 삽입합니다. + +### `.executeJavaScript(code[, userGesture])` + +* `code` String +* `userGesture` Boolean + +페이지에서 자바스크립트 `code`를 실행합니다. + +만약 `userGesture`가 `true`로 설정되어 있으면 페이지에 유저 제스쳐 컨텍스트를 만듭니다. +이 옵션을 활성화 시키면 `requestFullScreen`와 같은 HTML API에서 유저의 승인을 무시하고 개발자가 API를 바로 사용할 수 있도록 허용합니다. + +역주: 기본적으로 브라우저에선 전체화면, 웹캠, 파일 열기등의 API를 사용하려면 유저의 승인(이벤트)이 필요합니다. + +### `.openDevTools()` + +페이지에 대한 개발자 콘솔을 엽니다. + +### `.closeDevTools()` + +페이지에 대한 개발자 콘솔을 닫습니다. + +### `.isDevToolsOpened()` + +페이지에 대한 개발자 콘솔이 열려있는지 확인합니다. 불린 값을 반환합니다. + +### `.inspectElement(x, y)` + +* `x` Integer +* `y` Integer + +(`x`, `y`) 위치에 있는 엘리먼트를 inspect합니다. + +### `.inspectServiceWorker()` + +Service worker에 대한 개발자 콘솔을 엽니다. + +### `.undo()` + +페이지에서 실행 취소 커맨드를 실행합니다. + +### `.redo()` + +페이지에서 다시 실행 커맨드를 실행합니다. + +### `.cut()` + +페이지에서 잘라내기 커맨드를 실행합니다. + +### `.copy()` + +페이지에서 복사 커맨드를 실행합니다. + +### `.paste()` + +페이지에서 붙여넣기 커맨드를 실행합니다. + +### `.pasteAndMatchStyle()` + +페이지에서 `pasteAndMatchStyle` 편집 커맨드를 실행합니다. + +### `.delete()` + +페이지에서 삭제 커맨드를 실행합니다. + +### `.selectAll()` + +페이지에서 전체 선택 커맨드를 실행합니다. + +### `.unselect()` + +페이지에서 `unselect` 커맨드를 실행합니다. + +### `.replace(text)` + +* `text` String + +페이지에서 `replace` 커맨드를 실행합니다. + +### `.replaceMisspelling(text)` + +* `text` String + +페이지에서 `replaceMisspelling` 커맨드를 실행합니다. + +### `.print([options])` + +Webview 페이지를 인쇄합니다. `webContents.print([options])` 메서드와 같습니다. + +### `.printToPDF(options, callback)` + +Webview 페이지를 PDF 형식으로 인쇄합니다. `webContents.printToPDF(options, callback)` 메서드와 같습니다. + +### `.send(channel[, args...])` + +* `channel` String +* `args` (optional) + +`channel`을 통해 페이지에 `args` 비동기 메시지를 보냅니다. +페이지에선 `ipc` 모듈의 `channel` 이벤트를 사용하면 이 메시지를 받을 수 있습니다. + +예제는 [WebContents.send](web-contents.md#webcontentssendchannel-args)를 참고하세요. + +## DOM 이벤트 + +`webview` 태그는 다음과 같은 DOM 이벤트를 가지고 있습니다: + +### Event: 'load-commit' + +Returns: + +* `url` String +* `isMainFrame` Boolean + +로드가 시작됬을 때 발생하는 이벤트입니다. +이 이벤트는 현재 문서내의 탐색뿐만 아니라 서브 프레임 문서 레벨의 로드도 포함됩니다. +하지만 비동기 리소스 로드는 포함되지 않습니다. + +### Event: 'did-finish-load' + +탐색이 끝나면 발생하는 이벤트입니다. 브라우저 탭의 스피너가 멈추고 `onload` 이벤트가 발생할 때를 생각하면 됩니다. + +### Event: 'did-fail-load' + +Returns: + +* `errorCode` Integer +* `errorDescription` String +* `validatedUrl` String + +`did-finish-load`와 비슷합니다. 하지만 이 이벤트는 `window.stop()`과 같은 무언가로 인해 로드에 실패했을 때 발생하는 이벤트입니다. + +### Event: 'did-frame-finish-load' + +Returns: + +* `isMainFrame` Boolean + +프레임의 탐색이 끝나면 발생하는 이벤트입니다. + +### Event: 'did-start-loading' + +브라우저 탭의 스피너가 돌기 시작할 때 처럼 페이지의 로드가 시작될 때 발생하는 이벤트입니다. + +### Event: 'did-stop-loading' + +브라우저 탭의 스피너가 멈출 때 처럼 페이지의 로드가 끝나면 발생하는 이벤트입니다. + +### Event: 'did-get-response-details' + +Returns: + +* `status` Boolean +* `newUrl` String +* `originalUrl` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object + +요청한 리소스에 관해 자세한 내용을 알 수 있을 때 발생하는 이벤트입니다. +`status`는 리소스를 다운로드할 소켓 커낵션을 나타냅니다. + +### Event: 'did-get-redirect-request' + +Returns: + +* `oldUrl` String +* `newUrl` String +* `isMainFrame` Boolean + +리소스를 요청하고 받는 도중에 리다이렉트가 생기면 발생하는 이벤트입니다. + +### Event: 'dom-ready' + +프레임 문서의 로드가 끝나면 발생하는 이벤트입니다. + +### Event: 'page-title-set' + +Returns: + +* `title` String +* `explicitSet` Boolean + +탐색하는 동안에 페이지의 제목이 설정되면 발생하는 이벤트입니다. `explicitSet`는 파일 URL에서 종합(synthesised)된 제목인 경우 false로 표시됩니다. + +### Event: 'page-favicon-updated' + +Returns: + +* `favicons` Array - URL 배열 + +페이지가 favicon URL을 받았을 때 발생하는 이벤트입니다. + +### Event: 'enter-html-full-screen' + +페이지가 HTML API에 의해 전체 화면 모드에 돌입했을 때 발생하는 이벤트입니다. + +### Event: 'leave-html-full-screen' + +페이지의 전체 화면 모드가 해제됬을 때 발생하는 이벤트입니다. + +### Event: 'console-message' + +Returns: + +* `level` Integer +* `message` String +* `line` Integer +* `sourceId` String + +`console.log` API에 의해 로깅될 때 발생하는 이벤트입니다. + +다음 예제는 모든 로그 메시지를 로그 레벨이나 다른 속성에 관련 없이 호스트 페이지의 콘솔에 다시 로깅하는 예제입니다. + +```javascript +webview.addEventListener('console-message', function(e) { + console.log('Guest page logged a message:', e.message); +}); +``` + +### Event: 'new-window' + +Returns: + +* `url` String +* `frameName` String +* `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, + `new-window` and `other` + +페이지가 새로운 브라우저 창을 생성할 때 발생하는 이벤트입니다. + +다음 예제 코드는 새 URL을 시스템의 기본 브라우저로 여는 코드입니다. + +```javascript +webview.addEventListener('new-window', function(e) { + require('shell').openExternal(e.url); +}); +``` + +### Event: 'close' + +페이지가 자체적으로 닫힐 때 발생하는 이벤트입니다. + +다음 예제 코드는 페이지가 자체적으로 닫힐 때 `webview`를 `about:blank` 페이지로 이동시키는 예제입니다. + +```javascript +webview.addEventListener('close', function() { + webview.src = 'about:blank'; +}); +``` + +### Event: 'ipc-message' + +Returns: + +* `channel` String +* `args` Array + +호스트 페이지에서 비동기 IPC 메시지를 보낼 때 발생하는 이벤트입니다. + +`sendToHost` 메소드와 `ipc-message` 이벤트로 호스트 페이지와 쉽게 통신을 할 수 있습니다: + +```javascript +// In embedder page. +webview.addEventListener('ipc-message', function(event) { + console.log(event.channel); + // Prints "pong" +}); +webview.send('ping'); +``` + +```javascript +// In guest page. +var ipc = require('ipc'); +ipc.on('ping', function() { + ipc.sendToHost('pong'); +}); +``` + +### Event: 'crashed' + +랜더러 프로세스가 크래시 되었을 때 발생하는 이벤트입니다. + +### Event: 'gpu-crashed' + +GPU 프로세스가 크래시 되었을 때 발생하는 이벤트입니다. + +### Event: 'plugin-crashed' + +Returns: + +* `name` String +* `version` String + +플러그인 프로세스가 크래시 되었을 때 발생하는 이벤트입니다. + +### Event: 'destroyed' + +WebContents가 파괴될 때 발생하는 이벤트입니다. diff --git a/docs-translations/ko/api/window-open.md b/docs-translations/ko/api/window-open.md new file mode 100644 index 000000000000..a4cf3bce5c93 --- /dev/null +++ b/docs-translations/ko/api/window-open.md @@ -0,0 +1,57 @@ +# `window.open` 함수 + +`window.open` 함수가 호출되면 새 창을 생성하고 `url` 페이지를 불러옵니다. +이 창은 지정한 `url`을 로드하여 만들어진 `BrowserWindow`의 새 인스턴스이며 본래 창 객체 대신 페이지의 컨트롤이 제한된 프록시 객체를 반환합니다. + +프록시 객체는 브라우저의 웹 페이지 창과 호환될 수 있도록 일부 제한된 표준 기능만 가지고 있습니다. +창의 모든 컨트롤 권한을 가지려면 `BrowserWindow`를 직접 생성해서 사용해야 합니다. + +### `window.open(url[, frameName][, features])` + +* `url` String +* `frameName` String (optional) +* `features` String (optional) + +`BrowserWindowProxy` 클래스의 객체를 반환하는 새로운 윈도우를 생성합니다. + +### `window.opener.postMessage(message, targetOrigin)` + +* `message` String +* `targetOrigin` String + +부모 윈도우에 메시지를 보냅니다. origin을 특정할 수 있으며 `*`를 지정하면 따로 origin 설정을 사용하지 않습니다. + +## Class: BrowserWindowProxy + +### `BrowserWindowProxy.blur()` + +자식 윈도우의 포커스를 해제합니다. + +### `BrowserWindowProxy.close()` + +자식 윈도우를 강제로 닫습니다. unload 이벤트가 발생하지 않습니다. + +Forcefully closes the child window without calling its unload event. + +### `BrowserWindowProxy.closed` + +자식 윈도우가 닫히면 true로 설정됩니다. + +### `BrowserWindowProxy.eval(code)` + +* `code` String + +자식 윈도우에서 특정 스크립트를 실행합니다. + +### `BrowserWindowProxy.focus()` + +자식 윈도우에 포커스를 맞춥니다. (창을 맨 앞으로 가져옵니다) + +### `BrowserWindowProxy.postMessage(message, targetOrigin)` + +* `message` String +* `targetOrigin` String + +자식 윈도우에 메시지를 보냅니다. origin을 특정할 수 있으며 `*`를 지정하면 따로 origin 설정을 사용하지 않습니다. + +참고로 자식 윈도우의 `window.opener` 객체에는 다른 속성 없이 이 메서드 한 개만 구현되어 있습니다. diff --git a/docs-translations/ko/development/atom-shell-vs-node-webkit.md b/docs-translations/ko/development/atom-shell-vs-node-webkit.md new file mode 100644 index 000000000000..ec35a2dbf1f4 --- /dev/null +++ b/docs-translations/ko/development/atom-shell-vs-node-webkit.md @@ -0,0 +1,35 @@ +# Electron이 NW.js(node-webkit)와 기술적으로 다른점 + +__주의: Electron은 Atom Shell의 새로운 이름입니다.__ + +NW.js 처럼 Electron은 JavaScript와 HTML 그리고 Node 통합 환경을 제공함으로써 +웹 페이지에서 저 수준 시스템에 접근할 수 있도록 하여 웹 기반 데스크탑 어플리케이션을 작성할 수 있도록 하는 프레임워크 입니다. + +하지만 Electron과 NW.js는 근본적인 개발흐름의 차이도 있습니다: + +__1. 어플리케이션의 엔트리 포인트__ + +NW.js에선 어플리케이션의 엔트리 포인트로 웹 페이지를 사용합니다. +`package.json`내의 main 필드에 메인 웹 페이지(index.html) URL을 지정하면 어플리케이션의 메인 윈도우로 열리게 됩니다. + +Electron에선 JavaScript를 엔트리 포인트로 사용합니다. URL을 직접 제공하는 대신 API를 사용하여 직접 브라우저 창과 HTML 파일을 로드할 수 있습니다. +또한 윈도우의 종료시기를 결정하는 이벤트를 리스닝해야합니다. + +Electron은 Node.js 런타임과 비슷하게 작동합니다. Electron의 API는 저수준이기에 브라우저 테스팅을 위해 [PhantomJS](http://phantomjs.org/)를 사용할 수도 있습니다. + +__2. 빌드 시스템__ + +Electron은 Chromium의 모든것을 빌드하는 복잡성을 피하기 위해 [libchromiumcontent](https://github.com/brightray/libchromiumcontent)를 사용하여 +Chromium의 Content API에 접근합니다. libchromiumcontent은 단일 공유 라이브러리이고 Chromium Content 모듈과 종속성 라이브러리들을 포함합니다. +유저는 Electron을 빌드 하기 위해 높은 사양의 빌드용 컴퓨터를 구비할 필요가 없습니다. + +__3. Node 통합__ + +NW.js는 웹 페이지에서 require를 사용할 수 있도록 Chromium을 패치했습니다. 한편 Electron은 Chromium의 해킹을 방지하기 위해 libuv loop와 각 플랫폼의 메시지 루프에 통합하는 등의 다른 방법을 채택하였습니다. +[`node_bindings`](../../atom/common/) 코드를 보면 이 부분이 어떻게 구현됬는지를 알 수 있습니다. + +__4. 다중 컨텍스트__ + +만약 NW.js를 사용해본적이 있다면 Node context와 Web context의 개념을 잘 알고 있을겁니다. 이 개념은 NW.js가 구현된 방식에 따라 만들어졌습니다. + +Node의 [다중 컨텍스트](http://strongloop.com/strongblog/whats-new-node-js-v0-12-multiple-context-execution/)를 사용할 경우 Electron은 웹 페이지에서 새로운 JavaScript 컨텍스트를 생성하지 않습니다. diff --git a/docs-translations/ko/development/build-instructions-linux.md b/docs-translations/ko/development/build-instructions-linux.md new file mode 100644 index 000000000000..cb254a80596d --- /dev/null +++ b/docs-translations/ko/development/build-instructions-linux.md @@ -0,0 +1,132 @@ +# 빌드 설명서 (Linux) + +이 가이드는 Linux 운영체제에서 Electron을 빌드하는 방법을 설명합니다. + +## 빌드전 요구사양 + +* Python 2.7.x. 몇몇 CentOS와 같은 배포판들은 아직도 Python 2.6.x 버전을 사용합니다. 그래서 `python -V`를 통해 버전을 확인해 줄 필요가 있습니다. +* Node.js v0.12.x. Node를 설치하는 방법은 여러가지가 있습니다. 그중 하나는 [Node.js](http://nodejs.org) 사이트에서 소스코드를 받아 빌드하는 방법입니다. + 이렇게 하면 Node를 일반 유저로 홈 디렉터리에 설치할 수 있습니다. 또는 [NodeSource](https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories)에서 소스 파일을 받아올 수 있습니다. + 자세한 내용은 [Node.js 설치 방법](https://github.com/joyent/node/wiki/Installation) 을 참고하세요. +* 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을 가상머신으로 빌드 할 계획이라면 해당 가상머신의 스토리지를 최소 25GB 이상을 확보해 놓아야 합니다. + + +## 코드 가져오기 + +```bash +$ git clone https://github.com/atom/electron.git +``` + +## 부트 스트랩 + +부트스트랩 스크립트는 필수적인 빌드 종속성 라이브러리들을 모두 다운로드하고 프로젝트 파일을 생성합니다. +스크립트가 정상적으로 작동하기 위해선 Python 2.7.x 버전이 필요합니다. +아마 다운로드 작업이 상당히 많은 시간을 소요할 것입니다. +참고로 Electron은 빌드 툴체인으로 `ninja`를 사용하므로 `Makefile`은 생성되지 않습니다. + +```bash +$ cd electron +$ ./script/bootstrap.py -v +``` + +### 크로스 컴파일 + +`arm` 아키텍쳐로 빌드 하려면 다음 종속성 라이브러리를 설치해야 합니다: + +```bash +$ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \ + g++-arm-linux-gnueabihf +``` + +그리고 `bootstrap.py` 스크립트의 `--target_arch` 파라미터에 `arm` 또는 `ia32` 아키텍쳐를 지정하여 크로스 컴파일 할 수 있습니다: + +```bash +$ ./script/bootstrap.py -v --target_arch=arm +``` + +## 빌드 하기 + +`Release` 와 `Debug` 두 타겟 모두 빌드 합니다: + +```bash +$ ./script/build.py +``` + +이 스크립트는 `out/R` 디렉터리에 크기가 매우 큰 Electron 실행 파일을 배치합니다. 파일 크기는 1.3GB를 초과합니다. +이러한 문제가 발생하는 이유는 Release 타겟 바이너리가 디버그 심볼을 포함하기 때문입니다. +파일 크기를 줄이려면 `create-dist.py` 스크립트를 실행하세요: + +```bash +$ ./script/create-dist.py +``` + +이 스크립트는 매우 작은 배포판을 `dist` 디렉터리에 생성합니다. +create-dist.py 스크립트를 실행한 이후부턴 1.3GB를 초과하는 공간을 차지하는 `out/R` 폴더의 바이너리는 삭제해도 됩니다. + +또는 `Debug` 타겟만 빌드 할 수 있습니다: + +```bash +$ ./script/build.py -c D +``` + +빌드가 모두 끝나면 `out/D` 디렉터리에서 `electron` 디버그 바이너리를 찾을 수 있습니다. + +## 정리 하기 + +빌드 파일들을 정리합니다: + +```bash +$ ./script/clean.py +``` + +## 문제 해결 + +개발 종속성 라이브러리들을 제대로 설치했는지 확인하세요. + +## libtinfo.so.5 동적 링크 라이브러리를 로드하는 도중 에러가 발생할 경우 + +미리 빌드된 `clang`은 `libtinfo.so.5`로 링크를 시도합니다. +따라서 플랫폼에 따라 적당한 `libncurses` symlink를 추가하세요: + +```bash +$ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5 +``` + +## 테스트 + +프로젝트 코딩 스타일을 확인하려면: + +```bash +$ ./script/cpplint.py +``` + +테스트를 실행하려면: + +```bash +$ ./script/test.py +``` diff --git a/docs-translations/ko/development/build-instructions-osx.md b/docs-translations/ko/development/build-instructions-osx.md new file mode 100644 index 000000000000..4951b975ae33 --- /dev/null +++ b/docs-translations/ko/development/build-instructions-osx.md @@ -0,0 +1,63 @@ +# 빌드 설명서 (OS X) + +이 가이드는 OS X 운영체제에서 Electron을 빌드하는 방법을 설명합니다. + +## 빌드전 요구 사항 + +* OS X >= 10.8 +* [Xcode](https://developer.apple.com/technologies/tools/) >= 5.1 +* [node.js](http://nodejs.org) (external) + +만약 Homebrew를 이용해 파이선을 설치했다면 다음 Python 모듈도 같이 설치해야 합니다: + +* pyobjc + +## 코드 가져오기 + +```bash +$ git clone https://github.com/atom/electron.git +``` + +## 부트 스트랩 + +부트스트랩 스크립트는 필수적인 빌드 종속성 라이브러리들을 모두 다운로드하고 프로젝트 파일을 생성합니다. +참고로 Electron은 빌드 툴체인으로 `ninja`를 사용하므로 Xcode 프로젝트는 생성되지 않습니다. + +```bash +$ cd electron +$ ./script/bootstrap.py -v +``` + +## 빌드 하기 + +`Release` 와 `Debug` 두 타겟 모두 빌드 합니다: + +```bash +$ ./script/build.py +``` + +또는 `Debug` 타겟만 빌드 할 수 있습니다: + +```bash +$ ./script/build.py -c D +``` + +빌드가 모두 끝나면 `out/D` 디렉터리에서 `Electron.app` 실행 파일을 찾을 수 있습니다. + +## 32비트 지원 + +Electron은 현재 OS X 64비트만 지원하고 있습니다. 그리고 앞으로도 OS X 32비트는 지원할 계획이 없습니다. + +## 테스트 + +프로젝트 코딩 스타일을 확인하려면: + +```bash +$ ./script/cpplint.py +``` + +테스트를 실행하려면: + +```bash +$ ./script/test.py +``` diff --git a/docs-translations/ko/development/build-instructions-windows.md b/docs-translations/ko/development/build-instructions-windows.md new file mode 100644 index 000000000000..fa165ca153ac --- /dev/null +++ b/docs-translations/ko/development/build-instructions-windows.md @@ -0,0 +1,139 @@ +# 빌드 설명서 (Windows) + +이 가이드는 Windows 운영체제에서 Electron을 빌드하는 방법을 설명합니다. + +## 빌드전 요구 사항 + +* Windows 7 / Server 2008 R2 또는 최신 버전 +* Visual Studio 2013 - [VS 2013 커뮤니티 에디션 무료 다운로드](http://www.visualstudio.com/products/visual-studio-community-vs) +* [Python 2.7](http://www.python.org/download/releases/2.7/) +* [Node.js](http://nodejs.org/download/) +* [Git](http://git-scm.com) + +현재 사용하고 있는 PC에 Windows를 설치하지 않았다면 [modern.ie](https://www.modern.ie/en-us/virtualization-tools#downloads)에서 +사용 기한이 정해져있는 무료 가상머신 버전의 Windows를 받아 Electron을 빌드하는 방법도 있습니다. + +Electron은 모든 빌드를 command-line 스크립트를 통해 빌드합니다. 따라서 빌드에 Visual Studio를 사용할 수 없습니다. +하지만 여전히 Electron을 개발할 땐 아무 에디터나 사용할 수 있습니다. 빠른 시일내에 Visual Studio를 이용한 빌드도 지원할 계획입니다. + +**참고:** Visual Studio가 직접 빌드에 사용되지 않더라도 IDE와 같이 제공된 빌드 툴체인이 빌드에 **필수적으로** 사용되므로 여전히 필요합니다. + +**참고:** Visual Studio 2015는 사용할 수 없습니다. MSVS **2013**을 사용하고 있는지 확인해주세요. + +## 코드 가져오기 + +```powershell +$ git clone https://github.com/atom/electron.git +``` + +## 부트 스트랩 + +부트스트랩 스크립트는 필수적인 빌드 종속성 라이브러리들을 모두 다운로드하고 프로젝트 파일을 생성합니다. +참고로 Electron은 빌드 툴체인으로 `ninja`를 사용하므로 Visual Studio 프로젝트는 생성되지 않습니다. + +```powershell +$ cd electron +$ python script\bootstrap.py -v +``` + +## 빌드 하기 + +`Release` 와 `Debug` 두 타겟 모두 빌드 합니다: + +```powershell +$ python script\build.py +``` + +또는 `Debug` 타겟만 빌드 할 수 있습니다: + +```powershell +$ python script\build.py -c D +``` + +빌드가 모두 끝나면 `out/D` (디버그 타겟) 또는 `out/R` (릴리즈 타겟) 디렉터리에서 `electron.exe` 실행 파일을 찾을 수 있습니다. + +## 64비트 빌드 + +64비트를 타겟으로 빌드 하려면 부트스트랩 스크립트를 실행할 때 `--target_arch=x64` 인자를 같이 넘겨주면 됩니다: + +```powershell +$ python script\bootstrap.py -v --target_arch=x64 +``` + +다른 빌드 단계도 정확하게 같습니다. + +## 테스트 + +프로젝트 코딩 스타일을 확인하려면: + +```powershell +$ python script\cpplint.py +``` + +테스트를 실행하려면: + +```powershell +$ python script\test.py +``` + +테스트 실행시 `runas`와 같은 네이티브 모듈을 포함하는데 이 모듈은 디버그 빌드에서 같이 사용할 수 없습니다. +하지만 여전히 릴리즈 빌드에선 사용할 수 있습니다. + +릴리즈 빌드로 테스트를 실행하려면 다음 커맨드를 사용하면 됩니다: + +```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를 같이 사용할 때 발생합니다. +부트스트랩 스크립트에서 Win32 Python을 사용함으로써 이 문제를 해결할 수 있습니다. +`C:\Python27` 디렉터리에 Python이 설치되었다는 가정하에 다음 명령을 실행하면 됩니다: + +```bash +$ /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에서 빌드를 진행해야 합니다. diff --git a/docs-translations/ko/development/build-system-overview.md b/docs-translations/ko/development/build-system-overview.md new file mode 100644 index 000000000000..34f93a8881bd --- /dev/null +++ b/docs-translations/ko/development/build-system-overview.md @@ -0,0 +1,56 @@ +# 빌드 시스템 개요 + +Electron은 프로젝트 생성을 위해 `gyp`를 사용하며 `ninja`를 이용하여 빌드합니다. +프로젝트 설정은 `.gyp` 와 `.gypi` 파일에서 볼 수 있습니다. + +## gyp 파일 + +Electron을 빌드 할 때 `gyp` 파일들은 다음과 같은 규칙을 따릅니다: + +* `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`빌드 시엔 정적 라이브러리 버전의 컴포넌트를 사용합니다. +이렇게 각 빌드의 단점을 상호 보완하여 디버그 시 빌드 속도는 향상되고 배포판 빌드 시 공유 라이브러리의 단점은 제거했습니다. + +## 부트스트랩 최소화 + +Prebuilt된 모든 Chromium 바이너리들은 부트스트랩 스크립트가 실행될 때 다운로드됩니다. +기본적으로 공유 라이브러리와 정적 라이브러리 모두 다운로드되며 최종 전체 파일 크기는 플랫폼에 따라 800MB에서 2GB까지 차지합니다. + +기본적으로 (`libchromiumcontent`)는 Amazon Web Service를 통해 다운로드 됩니다. +만약 `LIBCHROMIUMCONTENT_MIRROR` 환경 변수가 설정되어 있으면 부트스트랩은 해당 링크를 사용하여 바이너리를 다운로드 합니다. +[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-phrase) + +Electron은 `Release`와 `Debug` 빌드가 서로 다른 라이브러리 링크 방식을 사용합니다. +하지만 `gyp`는 따로 빌드 설정을 분리하여 라이브러리 링크 방식을 정의하는 것을 지원하지 않습니다. + +이 문제를 해결하기 위해 Electron은 링크 설정을 제어하는 `gyp` 변수 `libchromiumcontent_component`를 사용하고 `gyp`를 실행할 때 단 하나의 타겟만 생성합니다. + +## 타겟 이름 + +많은 프로젝트에서 타겟 이름을 `Release` 와 `Debug`를 사용하는데 반해 Electron은 `R`과 `D`를 대신 사용합니다. +이유는 가끔 알 수 없는 이유(randomly)로 `Release` 와 `Debug` 중 하나만 빌드 설정에 정의되어 있을때 `gyp`가 크래시를 일으키는데 +전술한 바와 같이 Electron은 한번에 한개의 타겟만을 생성할 수 있기 때문입니다. + +이 문제는 개발자에게만 영향을 미칩니다. 만약 단순히 Electron을 rebranding 하기 위해 빌드 한다면 이 문제에 신경 쓸 필요가 없습니다. diff --git a/docs-translations/ko/development/coding-style.md b/docs-translations/ko/development/coding-style.md new file mode 100644 index 000000000000..0dc16ba0ad68 --- /dev/null +++ b/docs-translations/ko/development/coding-style.md @@ -0,0 +1,32 @@ +# 코딩 스타일 + +이 가이드는 Electron의 코딩 스타일에 관해 설명합니다. + +## C++과 Python + +C++과 Python 스크립트는 Chromium의 [코딩 스타일](http://www.chromium.org/developers/coding-style)을 따릅니다. +파이선 스크립트 `script/cpplint.py`를 사용하여 모든 파일이 해당 코딩스타일에 맞게 코딩 되었는지 확인할 수 있습니다. + +Python 버전은 2.7을 사용합니다. + +C++ 코드는 많은 Chromium의 추상화와 타입을 사용합니다. 따라서 Chromium 코드에 대해 잘 알고 있어야 합니다. +이와 관련하여 시작하기 좋은 장소로 Chromium의 [Important Abstractions and Data Structures] +(https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures) 문서가 있습니다. +이 문서에선 몇가지 특별한 타입과 스코프 타입(스코프 밖으로 나가면 자동으로 메모리에서 할당을 해제합니다. 스마트 포인터로 보시면 됩니다) +그리고 로깅 메커니즘 등을 언급하고 있습니다. + +## CoffeeScript + +CoffeeScript의 경우 GitHub의 [스타일 가이드](https://github.com/styleguide/javascript)를 기본으로 따릅니다. +그리고 다음 규칙을 따릅니다: + +* Google의 코딩 스타일에도 맞추기 위해 파일의 끝에는 **절대** 개행을 삽입해선 안됩니다. +* 파일 이름의 공백은 `_`대신에 `-`을 사용하여야 합니다. 예를 들어 `file_name.coffee`를 +`file-name.coffee`로 고쳐야합니다. 왜냐하면 [github/atom](https://github.com/github/atom) 에서 사용되는 모듈의 이름은 +보통 `module-name`형식이기 때문입니다. 이 규칙은 '.coffee' 파일에만 적용됩니다. + +## API 이름 + +새로운 API를 만들 땐 getter, setter스타일 대신 jQuery의 one-function스타일을 사용해야 합니다. 예를 들어 +`.getText()`와 `.setText(text)`대신에 `.text([text])`형식으로 설계하면 됩니다. +포럼에 이 문제에 대한 [논의](https://github.com/atom/electron/issues/46)가 있습니다. diff --git a/docs-translations/ko/development/setting-up-symbol-server.md b/docs-translations/ko/development/setting-up-symbol-server.md new file mode 100644 index 000000000000..ed43e0fae295 --- /dev/null +++ b/docs-translations/ko/development/setting-up-symbol-server.md @@ -0,0 +1,44 @@ +# 디버거에서 디버그 심볼 서버 설정 + +디버그 심볼은 디버깅 세션을 더 좋게 개선해 줍니다. 디버그 심볼은 실행 파일과 동적 링크 라이브러리에서 메서드에 대한 정보를 담고 있으며 명료한 함수 호출 스텍 정보를 제공합니다. +심볼 서버는 유저가 크기가 큰 디버깅용 파일을 필수적으로 다운로드 받지 않고도 디버거가 알맞은 심볼, 바이너리 그리고 소스를 자동적으로 로드할 수 있도록 해줍니다. +서버 사용법은 [Microsoft의 심볼 서버](http://support.microsoft.com/kb/311503)와 비슷합니다. 이 문서를 참조하세요. + +참고로 릴리즈된 Electron 빌드는 자체적으로 많은 최적화가 되어 있는 관계로 경우에 따라 디버깅이 쉽지 않을 수 있습니다. +Inlining, tail call 등의 컴파일러 최적화에 의해 디버거가 모든 변수의 컨텐츠를 보여줄 수 없는 경우도 있고 실행 경로가 이상하게 보여질 수 있습니다. +유일한 해결 방법은 최적화되지 않은 로컬 빌드를 하는 것입니다. + +공식적인 Electron의 심볼 서버의 URL은 http://54.249.141.255:8086/atom-shell/symbols 입니다. +일단 이 URL에 직접적으로 접근할 수는 없습니다: 디버깅 툴에 심볼의 경로를 추가해야합니다. +아래의 예제를 참고하면 로컬 캐시 디렉터리는 서버로부터 중복되지 않게 PDB를 가져오는데 사용됩니다. +`c:\code\symbols` 캐시 디렉터리를 사용중인 OS에 맞춰 적당한 경로로 변경하세요. + +## Windbg에서 심볼 서버 사용하기 + +Windbg 심볼 경로는 구분자와 *(별) 문자로 설정되어 있습니다. +Electron 심볼 서버만을 사용하려면 심볼 경로의 엔트리를 추가해야 합니다 (__참고:__ `c:\code\symbols` 디렉터리 경로를 PC가 원하는 경로로 수정할 수 있습니다): + +``` +SRV*c:\code\symbols\*http://54.249.141.255:8086/atom-shell/symbols +``` + +Windbg 메뉴 또는 `.sympath` 커맨드를 이용하여 환경에 `_NT_SYMBOL_PATH` 문자열을 설정합니다. +만약 Microsoft의 심볼서버로 부터 심볼을 받아오려면 다음과 같이 리스팅을 먼저 해야합니다: + +``` +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에서 심볼 서버 사용하기 + + + + +## 문제 해결: Symbols will not load + +Windbg에서 다음의 커맨드를 입력하여 왜 심볼이 로드되지 않았는지에 대한 오류 내역을 출력합니다: + +``` +> !sym noisy +> .reload /f chromiumcontent.dll +``` diff --git a/docs-translations/ko/development/source-code-directory-structure.md b/docs-translations/ko/development/source-code-directory-structure.md new file mode 100644 index 000000000000..f31d0847db75 --- /dev/null +++ b/docs-translations/ko/development/source-code-directory-structure.md @@ -0,0 +1,51 @@ +# 소스 코드 디렉터리 구조 + +Electron의 소스 코드는 몇 개의 파트로 분리되어 있습니다. 그리고 Chromium의 분리 규칙(separation conventions)을 주로 따르고 있습니다. + +이미 [Chromium의 멀티 프로세스 아키텍쳐](http://dev.chromium.org/developers/design-documents/multi-process-architecture)에 익숙해져 있다면 소스 코드를 이해하기 쉬울 것입니다. + +## 소스 코드 구조 + +``` +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 소스 코드. +├── docs - 참조 문서. +├── spec - 자동화된 테스트. +├── atom.gyp - Electron의 빌드 규칙. +└── common.gypi - 컴파일러 설정 및 `node` 와 `breakpad` 등의 구성요소를 위한 빌드 규칙. +``` + +## 그외 디렉터리 구조 + +* **script** - 개발목적으로 사용되는 빌드, 패키징, 테스트, 기타등을 실행하는 스크립트. +* **tools** - gyp 파일에서 사용되는 헬퍼 스크립트 `script`와는 다르게 유저로부터 직접 실행되지 않는 스크립트들을 이곳에 넣습니다. +* **vendor** - 소스코드의 서드파티 종속성 코드 소스 코드 디렉터리가 겹쳐 혼란을 일으킬 수 있기 때문에 + 우리는 `third_party`와 같은 Chromium 소스 코드 디렉터리에서 사용된 폴더 이름을 사용하지 않았습니다. +* **node_modules** - 빌드에 사용되는 node 서드파티 모듈. +* **out** - `ninja`의 임시 출력 디렉터리. +* **dist** - 배포용 바이너리를 빌드할 때 사용하는 `script/create-dist.py` 스크립트로부터 만들어지는 임시 디렉터리. +* **external_binaries** - `gyp` 빌드를 지원하지 않아 따로 다운로드된 서드파티 프레임워크 바이너리들. diff --git a/docs-translations/ko/styleguide.md b/docs-translations/ko/styleguide.md new file mode 100644 index 000000000000..aaa9274cd0b7 --- /dev/null +++ b/docs-translations/ko/styleguide.md @@ -0,0 +1,84 @@ +# Electron 문서 스타일 가이드 + +[Electron 문서 읽기](#electron-문서-읽기) 와 [Electron 문서 작성하기](#electron-문서-작성하기) 중 이해가 필요한 부분을 찾아 참고하세요: + +## Electron 문서 작성하기 + +Electron 문서를 작성하는 규칙은 다음과 같습니다. + +- `h1` 제목은 페이지당 한 개만 사용할 수 있습니다. +- 코드 블럭에서 터미널 언어 선택시 `cmd` 대신 `bash`를 사용합니다. (syntax highlighter를 사용하기 위해서) +- 문서의 `h1` 제목은 반드시 현재 객체 이름과 같게 해야 합니다. (예시: `browser-window` → `BrowserWindow`) + - 하이픈(-)으로 구분되었던 어떻게 되었던 간에 예시와 같이 작성합니다. +- 헤더 밑에 헤더를 바로 사용하지 않습니다. 한 줄이라도 좋으니 헤더와 헤더 사이에 설명 추가합니다. +- 메서드 헤더는 `code` 틱으로 표시합니다. +- 이벤트 헤더는 한 '따옴표'로 표시합니다. +- 리스트를 2 단계 이상 중첩하지 않습니다. (안타깝게도 markdown 랜더러가 지원하지 않습니다) +- 섹션에 대한 제목을 추가합니다: Events, Class 메서드 그리고 인스턴스 메서드등. +- 어떤 '것'의 사용 결과를 설명할 때 '될 것입니다' 형식을 사용하여 설명합니다. +- 이벤트와 메서드에는 `h3` 헤더를 사용합니다. +- 선택적 인수는 `function (required[, optional])` 형식으로 작성합니다. +- 선택적 인수는 목록에서 호출되면 표시합니다. +- 문장의 길이는 한 줄당 80 칸을 유지합니다. +- 플랫폼 특정 메서드 헤더는 이탈릭체로 표시합니다. + - ```### `method(foo, bar)` _OS X_``` +- 'on' 표현 대신 'in the ___ process' 형식의 표현을 지향합니다. + +### 번역된 참조 문서 + +번역된 Electron의 참조 문서는 `docs-translations` 디렉터리에 있습니다. + +아직 번역되지 않은 언어를 추가하려면 (일부분 포함): + +- 언어의 약어(예: en, ja, ko등)로 서브 디렉터리를 만듭니다. +- 서브 디렉터리에 `docs` 디렉터리를 복사합니다. 파일 이름과 디렉터리 구조는 모두 유지합니다. +- 문서를 번역합니다. +- `README.md`에 번역한 문서의 링크를 추가하고 업데이트 합니다. +- 메인 Electron의 [README](https://github.com/atom/electron#documentation-translations)에 번역된 디렉터리의 링크를 추가합니다. + +## Electron 문서 읽기 + +Electron 문서 구조를 이해하는 데 참고할 수 있는 유용한 도움말입니다. + +### Methods + +[Method](https://developer.mozilla.org/en-US/docs/Glossary/Method) 문서의 예제입니다: + +--- + +`methodName(required[, optional]))` + +* `require` String, **필수** +* `optional` Integer + +--- + +메서드 이름은 인수가 무엇을 받는지에 따라 결정됩니다. 선택적 인수는 브라켓([, ])으로 묶어 +이 인수가 다른 인수뒤에서 선택적으로 사용될 수 있다는 것을 표시합니다. + +메서드의 밑에선 각 인수에 대해 자세한 설명을 합니다. 인수의 타입은 일반적인 타입 중 하나를 받거나: +[`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) +Electron의 [`webContent`](api/web-content.md)같은 커스텀 타입을 받습니다. + +### Events + +[Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) 문서의 예제입니다: + +--- + +Event: 'wake-up' + +Returns: + +* `time` String + +--- + +이벤트는 `.on` 리스너 메서드로 사용할 수 있습니다. 만약 이벤트에서 값을 반환한다면 문서에서 표시된 대로 +일정한 타입의 값을 반환합니다. 이벤트를 처리하거나 응답하려 한다면 다음과 같이 사용할 수 있습니다: + +```javascript +Alarm.on('wake-up', function(time) { + console.log(time) +}) +``` diff --git a/docs-translations/ko/tutorial/application-distribution.md b/docs-translations/ko/tutorial/application-distribution.md new file mode 100644 index 000000000000..69269500733a --- /dev/null +++ b/docs-translations/ko/tutorial/application-distribution.md @@ -0,0 +1,141 @@ +# 어플리케이션 배포 + +Electron 어플리케이션을 배포하는 방법은 간단합니다. + +먼저 폴더 이름을 `app`로 지정한 후 Electron 리소스 디렉터리에 폴더를 통째로 집어넣기만 하면 됩니다. +리소스 디렉터리는 OS X: `Electron.app/Contents/Resources/` Windows와 Linux: `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` 폴더(Electron.app)를 배포하면 됩니다. + +## asar로 앱 패키징 하기 + +소스파일 전체를 복사해서 배포하는 것과는 별개로 [asar](https://github.com/atom/asar) 아카이브를 통해 +어플리케이션의 소스코드가 사용자에게 노출되는 것을 방지할 수 있습니다. + +`asar` 아카이브를 사용할 땐 단순히 `app` 폴더 대신에 어플리케이션을 패키징한 `app.asar` 파일로 대체하면됩니다. +Electron은 자동으로 `app`폴더 대신 asar 아카이브를 기반으로 어플리케이션을 실행합니다. + +OS X의 경우: + +```text +electron/Electron.app/Contents/Resources/ +└── app.asar +``` + +Windows 와 Linux의 경우: + +```text +electron/resources/ +└── app.asar +``` + +자세한 내용은 [어플리케이션 패키징](application-packaging.md)에서 찾아볼 수 있습니다. + +## 다운로드한 바이너리의 리소스를 앱에 맞게 수정하기 + +어플리케이션을 Electron에 번들링한 후 해당 어플리케이션에 맞게 리브랜딩 할 수 있습니다. + +### Windows + +`electron.exe`을 원하는 이름으로 변경할 수 있습니다. +그리고 [rcedit](https://github.com/atom/rcedit) 또는 [ResEdit](http://www.resedit.net)를 사용하여 아이콘을 변경할 수 있습니다. + +### OS X + +`Electron.app`을 원하는 이름으로 변경할 수 있습니다. 그리고 다음 표시된 어플리케이션 내부 파일에서 +`CFBundleDisplayName`, `CFBundleIdentifier` and `CFBundleName` 필드를 원하는 이름으로 변경해야합니다: + +* `Electron.app/Contents/Info.plist` +* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist` + +또한 helper 앱이 프로세스 모니터에 `Electron Helper`로 나오지 않도록 이름을 변경할 수 있습니다. +하지만 반드시 내부 및 모든 helper 앱의 이름을 변경해야 합니다. + +어플리케이션 아이콘은 `Electron.app/Contents/Resources/atom.icns`을 원하는 아이콘으로 변경하면 됩니다. + +원하는 이름으로 바꾼 어플리케이션 예제: + +``` +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`의 이름을 원하는 대로 바꿀 수 있습니다. +리눅스 어플리케이션의 아이콘은 [.desktop](https://developer.gnome.org/integration-guide/stable/desktop-files.html.en) 파일을 사용하여 지정할 수 있습니다. + +### 역주-자동화 + +어플리케이션 배포시 Electron의 리소스를 일일이 수정하는 것은 매우 귀찮고 복잡합니다. +하지만 이 작업을 자동화 시킬 수 있는 몇가지 방법이 있습니다: + +* [electron-builder](https://github.com/loopline-systems/electron-builder) +* [electron-packager](https://github.com/maxogden/electron-packager) + +## Electron 소스코드를 다시 빌드하여 리소스 수정하기 + +또한 Electron 소스코드를 다시 빌드할 때 어플리케이션 이름을 변경할 수 있습니다. + +`GYP_DEFINES` 환경변수를 사용하여 다음과 같이 다시 빌드할 수 있습니다: + +__Windows__ + +```bash +> set "GYP_DEFINES=project_name=myapp product_name=MyApp" +> python script\clean.py +> python script\bootstrap.py +> python script\build.py -c R -t myapp +``` + +__Bash__ + +```bash +$ export GYP_DEFINES="project_name=myapp product_name=MyApp" +$ script/clean.py +$ script/bootstrap.py +$ script/build.py -c Release -t myapp +``` + +### grunt-build-atom-shell + +Electron의 소스코드를 수정하고 다시 빌드하는 작업은 상당히 복잡합니다. +일일이 소스코드를 수정하는 대신 [grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell)을 사용하여 빌드를 자동화 시킬 수 있습니다. + +이 툴을 사용하면 자동으로 `.gyp`파일을 수정하고 다시 빌드합니다. 그리고 어플리케이션의 네이티브 Node 모듈 또한 새로운 실행파일 이름으로 매치 시킵니다. diff --git a/docs-translations/ko/tutorial/application-packaging.md b/docs-translations/ko/tutorial/application-packaging.md new file mode 100644 index 000000000000..d7a96dbb4b43 --- /dev/null +++ b/docs-translations/ko/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은 Node.js로 부터 제공된 Node API와 Chromium으로부터 제공된 Web API 두 가지 API를 가지고 있습니다. +따라서 `asar` 아카이브는 두 API 모두 사용할 수 있도록 지원합니다. + +### Node API + +Electron에선 `fs.readFile`과 `require` 같은 Node API들을 지원하기 위해 `asar` 아카이브가 가상의 디렉터리 구조를 가지도록 +패치했습니다. 그래서 아카이브 내부 리소스들을 정상적인 파일 시스템처럼 접근할 수 있습니다. + +예를 들어 `/path/to`라는 경로에 `example.asar`라는 아카이브가 있다고 가정하면: + +```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 +var fs = require('fs'); +fs.readFileSync('/path/to/example.asar/file.txt'); +``` + +아카이브 내의 루트 디렉터리를 리스팅합니다: + +```javascript +var fs = require('fs'); +fs.readdirSync('/path/to/example.asar'); +``` + +아카이브 안의 모듈 사용하기: + +```javascript +require('/path/to/example.asar/dir/module.js'); +``` + +`BrowserWindow` 클래스를 이용해 원하는 웹 페이지도 표시할 수 있습니다: + +```javascript +var BrowserWindow = require('browser-window'); +var win = new BrowserWindow({width: 800, height: 600}); +win.loadUrl('file:///path/to/example.asar/static/index.html'); +``` + +### Web API + +웹 페이지 내에선 아카이브 내의 파일을 `file:` 프로토콜을 사용하여 요청할 수 있습니다. +이 또한 Node API와 같이 가상 디렉터리 구조를 가집니다. + +예를 들어 jQuery의 `$.get`을 사용하여 파일을 가져올 수 있습니다: + +```html + +``` + +### `asar` 아카이브를 일반 파일로 취급하기 + +`asar` 아카이브의 체크섬(checksum)등을 검사하기 위해선 `asar` 아카이브를 파일 그대로 읽어들여야 할 필요가 있습니다. +이 작업을 하기 위해 `original-fs`라고 하는 빌트인 모듈을 `fs` 모듈 대신에 사용할 수 있습니다. +이 모듈은 `asar` 지원이 빠져있습니다. 즉 파일 그대로를 읽어들입니다: + +```javascript +var originalFs = require('original-fs'); +originalFs.readFileSync('/path/to/example.asar'); +``` + +## Node API의 한계 + +`asar` 아카이브를 Node API가 최대한 디렉터리 구조로 작동하도록 노력해왔지만 여전히 저수준(low-level nature) Node API 때문에 한계가 있습니다. + +### 아카이브는 읽기 전용입니다 + +아카이브는 수정할 수 없으며 기본적으로는 Node API로 파일을 수정할 수 있지만 `asar` 아카이브에선 작동하지 않습니다. + +### 아카이브 안의 디렉터리를 작업 경로로 설정하면 안됩니다 + +`asar` 아카이브는 디렉터리처럼 사용할 수 있도록 구현되었지만 그것은 실제 파일시스템의 디렉터리가 아닌 가상의 디렉터리입니다. +그런 이유로 몇몇 API에서 지원하는 `cwd` 옵션을 `asar` 아카이브 안의 디렉터리 경로로 지정하면 나중에 문제가 발생할 수 있습니다. + +### 특정 API로 인한 예외적인 압축 해제 + +많은 `fs` API가 `asar` 아카이브의 압축을 해제하지 않고 바로 아카이브를 읽거나 정보를 가져올 수 있으나 +몇몇 API는 시스템의 실제 파일의 경로를 기반으로 작동하므로 이 API들을 사용할 땐 Electron은 +이 API가 원할하게 작동할 수 있도록 하기 위해 임시경로에 해당 파일들의 압축을 해제합니다. 이 작업은 약간의 오버헤드를 불러 일으킬 수 있습니다. + +해당하는 API 메서드는 다음과 같습니다: + +* `child_process.execFile` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - Used by `require` on native modules + +### `fs.stat`의 잘못된 스테이터스 정보 + +`fs.stat` 로 부터 반환되는 `Stats` 객체와 비슷한 API들은 `asar` 아카이브를 타겟으로 할 경우 예측된 디렉터리 피일 정보를 가집니다. +왜냐하면 아카이브의 디렉터리 경로는 실제 파일시스템에 존재하지 않기 때문입니다. +그러한 이유로 파일 크기와 파일 타입 등을 확인할 때 `Stats` 객체를 신뢰해선 안됩니다. + +## `asar` 아카이브에 미리 압축 해제된 파일 추가하기 + +전술한 바와 같이 몇몇 Node API는 호출 시 해당 파일을 임시폴더에 압축을 해제합니다. +이 방법은 성능문제가 발생할 수 있습니다. 그리고 설계상 백신 소프트웨어에서 잘못 진단하여 바이러스로 판단 할 수도 있습니다. + +이 문제를 해결하려면 `--unpack` 옵션을 활용하여 파일을 압축이 풀려진 상태로 유지해야 합니다. +다음의 예제는 node 네이티브 모듈의 공유 라이브러리를 unpack 상태로 유지합니다: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +커맨드를 실행한 후 같은 디렉터리에 `app.asar` 파일 외에 `app.asar.unpacked` 폴더가 같이 생성됩니다. +이 폴더안에 unpack 옵션에서 설정한 파일들이 압축이 풀린 상태로 포함되어 있습니다. +유저에게 어플리케이션을 배포할 때 반드시 해당 폴더도 같이 배포하여야합니다. + +[asar]: https://github.com/atom/asar diff --git a/docs-translations/ko/tutorial/debugging-main-process.md b/docs-translations/ko/tutorial/debugging-main-process.md new file mode 100644 index 000000000000..c11f3db04f3a --- /dev/null +++ b/docs-translations/ko/tutorial/debugging-main-process.md @@ -0,0 +1,48 @@ +# 메인 프로세스 디버깅하기 + +브라우저 창의 개발자 콘솔은 웹 페이지 같은 랜더러 프로세스의 스크립트만 디버깅이 가능합니다. +대신 Electron은 메인 프로세스의 디버깅을 위해 `--debug` 과 `--debug-brk` 스위치들을 제공합니다. + +## 커맨드 라인 스위치(command line switches) + +다음 스위치들을 사용하여 Electron의 메인 프로세스를 디버깅 할 수 있습니다: + +### `--debug=[port]` + +이 스위치를 사용하면 Electron은 지정한 `port`에 V8 디버거 프로토콜을 리스닝합니다. 기본 `port`는 `5858` 입니다. + +### `--debug-brk=[port]` + +`--debug`와 비슷하지만 스크립트의 첫번째 라인에서 일시정지합니다. + +## node-inspector로 디버깅 하기 + +__참고:__ Electron은 node v0.11.13 버전을 사용합니다. +그리고 현재 node-inspector 유틸리티와 호환성 문제가 있습니다. +추가로 node-inspector 콘솔 내에서 메인 프로세스의 `process` 객체를 탐색할 경우 크래시가 발생할 수 있습니다. + +### 1. [node-inspector][node-inspector] 서버 시작 + +```bash +$ node-inspector +``` + +### 2. Electron용 디버그 모드 활성화 + +다음과 같이 debung 플래그로 Electron을 실행할 수 있습니다: + +```bash +$ electron --debug=5858 your/app +``` + +또는 스크립트 첫번째 라인에서 일시 정지할 수 있습니다: + +```bash +$ electron --debug-brk=5858 your/app +``` + +### 3. 디버그 UI 로드 + +Chrome 브라우저에서 http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 주소에 접속합니다. (기본포트 또는 지정한 포트로 접속) + +[node-inspector]: https://github.com/node-inspector/node-inspector diff --git a/docs-translations/ko/tutorial/desktop-environment-integration.md b/docs-translations/ko/tutorial/desktop-environment-integration.md new file mode 100644 index 000000000000..94f0ffa013a7 --- /dev/null +++ b/docs-translations/ko/tutorial/desktop-environment-integration.md @@ -0,0 +1,222 @@ +# 데스크톱 환경 통합 + +어플리케이션을 배포할 서로 다른 운영체제 시스템의 환경과 기능에 맞춰 사용 환경을 통합할 수 있습니다. +예를 들어 Windows에선 태스크바의 JumpList에 바로가기를 추가할 수 있고 Mac(OS X)에선 dock menu에 커스텀 메뉴를 추가할 수 있습니다. + +이 가이드는 Electron API를 이용하여 각 운영체제 시스템의 기능을 활용하는 방법을 설명합니다. + +## 최근 사용한 문서 (Windows & OS X) + +알다 싶이 Windows와 OS X는 JumpList 또는 dock 메뉴를 통해 최근 문서 리스트에 쉽게 접근할 수 있습니다. + +__JumpList:__ + +![JumpList 최근 문서](http://i.msdn.microsoft.com/dynimg/IC420538.png) + +__어플리케이션 dock menu:__ + + + +파일을 최근 문서에 추가하려면 [app.addRecentDocument][addrecentdocument] API를 사용할 수 있습니다: + +```javascript +var app = require('app'); +app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); +``` + +그리고 [app.clearRecentDocuments][clearrecentdocuments] API로 최근 문서 리스트를 비울 수 있습니다: + +```javascript +app.clearRecentDocuments(); +``` + +### Windows에서 주의할 점 + +이 기능을 Windows에서 사용할 땐 운영체제 시스템에 어플리케이션에서 사용하는 파일 확장자가 등록되어 있어야 합니다. +그렇지 않은 경우 파일을 JumpList에 추가해도 추가되지 않습니다. +어플리케이션 등록에 관련된 API의 모든 내용은 [Application Registration][app-registration]에서 찾아볼 수 있습니다. + +유저가 JumpList에서 파일을 클릭할 경우 클릭된 파일의 경로가 커맨드 라인 인자로 추가되어 새로운 인스턴스의 어플리케이션이 실행됩니다. + +### OS X에서 주의할 점 + +파일이 최근 문서 메뉴에서 요청될 경우 `app` 모듈의 `open-file` 이벤트가 호출됩니다. + +## 커스텀 독 메뉴 (OS X) + +OS X는 개발자가 dock에 커스텀 메뉴를 만들 수 있도록 허용하고 있습니다. +보통 어플리케이션의 특정 기능 바로가기를 만들 때 사용합니다: + +__Terminal.app의 dock menu:__ + + + +커스텀 dock menu를 설정하려면 `app.dock.setMenu` API를 사용하면 됩니다. OS X에서만 사용 가능합니다: + +```javascript +var app = require('app'); +var Menu = require('menu'); +var dockMenu = Menu.buildFromTemplate([ + { label: 'New Window', click: function() { console.log('New Window'); } }, + { label: 'New Window with Settings', submenu: [ + { label: 'Basic' }, + { label: 'Pro'} + ]}, + { label: 'New Command...'} +]); +app.dock.setMenu(dockMenu); +``` + +## 사용자 작업 (Windows) + +Windows에선 JumpList의 `Tasks` 카테고리에 원하는 작업을 설정할 수 있습니다. +MSDN에선 해당 기능을 다음과 같이 설명하고 있습니다: + +> 어플리케이션 작업은 프로그램의 기능 그리고 주요사양 두가지를 기반으로 유저의 행동을 예측하여 정의합니다. +> 실행할 필요가 없는 어플리케이션 작업은 작동하지 않을 때 반드시 context-free를 유지해야 합니다. +> 작업은 일반 사용자가 프로그램을 실행하거나 이메일 프로그램으로 이메일을 작성하거나 달력을 불러오고 +> 워드 프로세서로 새 문서를 작성, 특정 모드, 부속 명령으로 프로그램을 실행하는 등의 +> 통계적, 일반적으로 가장 많이 사용되는 작업인지를 고려해야 합니다. +> 어플리케이션 작업은 일반 유저가 필요로 하지 않는 고급 기능을 조잡하게 채우거나 등록과 같은 일회성의 작업을 포함해선 안됩니다. +> 작업에 특별 이벤트 또는 업그레이드 등의 홍보성 작업을 추가하면 안됩니다. +> +> 작업 리스트는 가능한 한 정적으로 유지되는 것을 적극 권장합니다. +> 이것은 동일한 상태 또는 응용 프로그램의 상태에 관계없이 유지되어야 합니다. +> 작업 목록은 동적으로 변경할 수 있지만 몇몇 유저는 예상하지 못한 작업 목록 변경에 혼동할 수 있다는 점을 고려해야 합니다. + +__Internet Explorer의 작업:__ + +![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) + +OS X의 dock menu(진짜 메뉴)와는 달리 Windows의 사용자 작업은 어플리케이션 바로 가기처럼 작동합니다. +유저가 작업을 클릭할 때 설정한 인자와 함께 새로운 어플리케이션이 실행됩니다. + +사용자 작업을 설정하려면 [app.setUserTasks][setusertaskstasks] 메서드를 통해 구현할 수 있습니다: + +```javascript +var app = require('app'); +app.setUserTasks([ + { + program: process.execPath, + arguments: '--new-window', + iconPath: process.execPath, + iconIndex: 0, + title: 'New Window', + description: 'Create a new window' + } +]); +``` + +작업 리스트를 비우려면 간단히 `app.setUserTasks` 메서드의 첫번째 인자에 빈 배열을 넣어 호출하면 됩니다: + +```javascript +app.setUserTasks([]); +``` + + +사용자 작업 리스트는 어플리케이션이 삭제되지 않는 한 종료되어도 태스크바에 보존됩니다. 이러한 이유로 반드시 프로그램 경로와 아이콘 경로를 지정해야 합니다. + +## 섬네일 툴바 + +Windows에선 작업 표시줄의 어플리케이션 선택시 나오는 미리보기에 특정한 섬네일 툴바를 추가할 수 있습니다. +이 기능은 유저가 윈도우를 활성화 하지 않고 특정한 커맨드를 실행시킬 수 있도록 할 수 있습니다. + +MSDN의 설명에 의하면: + +> 이 툴바는 표준 툴바의 공통 컨트롤과 비슷한 역할을 합니다. 버튼은 최대 7개 까지 만들 수 있습니다. +> 각 버튼의 구조엔 ID, 이미지, 툴팁, 상태등이 정의되어있습니다. 태스크바에 구조가 전달되면 어플리케이션이 +> 상태에 따라 버튼을 숨기거나, 활성화하거나, 비활성화 할 수 있습니다. +> +> 예를 들어, 윈도우 미디어 플레이어는(WMP) 기본적으로 +> 미디어 플레이어가 공통적으로 사용하는 재생, 일시정지, 음소거, 정지등의 컨트롤을 포함하고 있습니다. + +__Windows Media Player의 섬네일 툴바:__ + +![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png) + +[BrowserWindow.setThumbarButtons][setthumbarbuttons] API를 통해 어플리케이션에 섬네일 툴바를 설정할 수 있습니다: + +```javascript +var BrowserWindow = require('browser-window'); +var path = require('path'); +var win = new BrowserWindow({ + width: 800, + height: 600 +}); +win.setThumbarButtons([ + { + tooltip: "button1", + icon: path.join(__dirname, 'button1.png'), + click: function() { console.log("button2 clicked"); } + }, + { + tooltip: "button2", + icon: path.join(__dirname, 'button2.png'), + flags:['enabled', 'dismissonclick'], + click: function() { console.log("button2 clicked."); } + } +]); +``` + +섬네일 툴바를 비우려면 간단히 `BrowserWindow.setThumbarButtons` API에 빈 배열을 전달하면 됩니다: + +```javascript +win.setThumbarButtons([]); +``` + +## Unity 런처 숏컷 기능 (Linux) + +Unity 환경에선 `.desktop` 파일을 수정함으로써 런처에 새로운 커스텀 엔트리를 추가할 수 있습니다. [Adding Shortcuts to a Launcher][unity-launcher] 가이드를 참고하세요. + +__Audacious의 런처 숏컷:__ + +![audacious](https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles?action=AttachFile&do=get&target=shortcuts.png) + +## Taskbar progress 기능 (Windows & Unity) + +Windows에선 태스크바의 어플리케이션 버튼에 progress bar를 추가할 수 있습니다. +이 기능은 사용자가 어플리케이션의 창을 열지 않고도 어플리케이션의 작업의 상태 정보를 시각적으로 보여줄 수 있도록 해줍니다. + +또한 Unity DE도 런처에 progress bar를 부착할 수 있습니다. + +__태스크바 버튼의 progress bar:__ + +![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를 사용하여 구현할 수 있습니다: + +```javascript +var window = new BrowserWindow({...}); +window.setProgressBar(0.5); +``` + +## 윈도우 대표 파일 제시 (OS X) + +OS X는 윈도우에서 대표 파일을 설정할 수 있습니다. 쉽게 말해 타이틀바에서 파일 아이콘을 볼 수 있을 때 사용자가 Command-Click 또는 Control-Click 할 경우 파일 경로 팝업이 보여집니다. +또한 윈도우의 상태도 지정할 수 있습니다. 쉽게 말해 로드된 문서의 수정여부를 타이틀바 파일 아이콘에 표시할 수 있습니다. + +__대표 파일 팝업 메뉴:__ + + + +대표 파일 관련 API는 [BrowserWindow.setRepresentedFilename][setrepresentedfilename] 과 [BrowserWindow.setDocumentEdited][setdocumentedited]를 사용할 수 있습니다: + +```javascript +var window = new BrowserWindow({...}); +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 +[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 diff --git a/docs-translations/ko/tutorial/devtools-extension.md b/docs-translations/ko/tutorial/devtools-extension.md new file mode 100644 index 000000000000..e6a9b559c35f --- /dev/null +++ b/docs-translations/ko/tutorial/devtools-extension.md @@ -0,0 +1,47 @@ +# 개발자 콘솔 확장 + +어플리케이션의 디버깅을 쉽게 하기 위해 Electron은 기본적으로 [Chrome DevTools Extension][devtools-extension]을 지원합니다. + +개발자 콘솔 확장 기능은 간단하게 사용할 확장 기능 플러그인의 소스 코드를 다운로드한 후 `BrowserWindow.addDevToolsExtension` API를 이용하여 +어플리케이션 내에 로드할 수 있습니다. 한가지 주의할 점은 확장 기능 사용시 창이 생성될 때 마다 일일이 해당 API를 호출할 필요는 없습니다. + +예시로 [React DevTools Extension](https://github.com/facebook/react-devtools)을 사용합니다. + +먼저 소스코드를 다운로드 받습니다: + +```bash +$ cd /some-directory +$ git clone --recursive https://github.com/facebook/react-devtools.git +``` + +그리고 개발자 콘솔이 열린 창에서 다음의 코드를 콘솔에 입력하면 확장 기능을 로드할 수 있습니다: + +```javascript +require('remote').require('browser-window').addDevToolsExtension('/some-directory/react-devtools'); +``` + +확장 기능을 unload 하고 콘솔을 다시 열 때 해당 확장 기능이 로드되지 않도록 하려면 `BrowserWindow.removeDevToolsExtension` API를 사용하면 됩니다: + +```javascript +require('remote').require('browser-window').removeDevToolsExtension('React Developer Tools'); +``` + +## 개발자 콘솔 확장 기능의 구성 형식 + +모든 개발자 콘솔 확장은 완벽히 Chrome 브라우저를 위해 작성되었기 때문에 Electron에서도 로드할 수 있습니다. +하지만 반드시 확장 기능은 소스코드 그대로의 디렉터리(폴더) 형태여야 합니다. 그래서 `crx` 등의 포맷으로 패키징된 확장 기능의 경우 +사용자가 직접 해당 패키지의 압축을 풀어서 로드하지 않는 이상은 Electron에서 해당 확장 기능의 압축을 풀 방법이 없습니다. + +## 백그라운드 페이지 + +현재 Electron은 Chrome에서 지원하는 백그라운드 페이지(background pages)를 지원하지 않습니다. +몇몇 확장 기능은 이 기능에 의존하는 경우가 있는데 이 경우 해당 확장 기능은 Electron에서 작동하지 않을 수 있습니다. + +## `chrome.*` API + +몇몇 Chrome 확장 기능은 특정 기능을 사용하기 위해 `chrome.*` API를 사용하는데, 이 API들을 구현하기 위해 노력했지만 안타깝게도 아직 모든 API가 구현되지는 않았습니다. + +아직 모든 API가 구현되지 않았기 때문에 확장 기능에서 `chrome.devtools.*` 대신 `chrome.*` API를 사용할 경우 확장 기능이 제대로 작동하지 않을 수 있음을 감안해야 합니다. +만약 문제가 발생할 경우 Electron의 GitHub 저장소에 관련 이슈를 올리면 해당 API를 추가하는데 많은 도움이 됩니다. + +[devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs-translations/ko/tutorial/online-offline-events.md b/docs-translations/ko/tutorial/online-offline-events.md new file mode 100644 index 000000000000..cc8ae4855d55 --- /dev/null +++ b/docs-translations/ko/tutorial/online-offline-events.md @@ -0,0 +1,80 @@ +# 온라인/오프라인 이벤트 감지 + +온라인/오프라인 이벤트는 다음 예제와 같이 랜더러 프로세스에서 표준 HTML5 API를 이용하여 구현할 수 있습니다. + +_main.js_ + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` + +메인 프로세스에서 이 이벤트를 처리할 필요가 있는 경우 이벤트를 메인 프로세스로 보낼 수 있습니다. +메인 프로세스는 `navigator` 객체를 가지고 있지 않기 때문에 이 이벤트를 직접 사용할 수 없습니다. + +대신 다음 예제와 같이 Electron의 inter-process communication(ipc) 유틸리티를 사용하면 이벤트를 메인 프로세스로 전달할 수 있습니다. + +_main.js_ + +```javascript +var app = require('app'); +var ipc = require('ipc'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); + +ipc.on('online-status-changed', function(event, status) { + console.log(status); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` diff --git a/docs-translations/ko/tutorial/quick-start.md b/docs-translations/ko/tutorial/quick-start.md new file mode 100644 index 000000000000..8c2e043061ec --- /dev/null +++ b/docs-translations/ko/tutorial/quick-start.md @@ -0,0 +1,178 @@ +# 시작하기 + +## 소개 + +Electron은 자바스크립트와 함께 제공된 풍부한 네이티브 API를 사용하여 멋진 데스크탑 어플리케이션을 만들 수 있도록 해주는 프레임워크입니다. +이 프레임워크의 io.js(node.js)는 웹 서버 개발이 아닌 데스크탑 어플리케이션 개발에 초점을 맞췄습니다. + +이 말은 Electron이 GUI 라이브러리의 자바스크립트 바인딩이라는 뜻이 아닙니다. +대신, Electron은 웹 페이지의 GUI를 사용합니다. 쉽게 말해 Electron은 자바스크립트를 사용하여 조작하는 작은 Chromium 브라우저로 볼 수 있습니다. + +### 메인 프로세스 + +Electron은 실행될 때 __메인 프로세스__로 불리는 `package.json`의 `main` 스크립트를 호출합니다. +이 스크립트는 메인 프로세스에서 작동합니다. GUI 컴포넌트를 조작하거나 웹 페이지 창을 생성할 수 있습니다. + +### 랜더러 프로세스 + +Electron이 웹페이지를 보여줄 때 Chromium의 multi-processes 구조도 같이 사용됩니다. +Electron 프로세스 내에서 작동하는 웹 페이지를 __랜더러 프로세스__ 라고 불립니다. + +보통 일반 브라우저의 웹 페이지들은 샌드박스가 적용된 환경에서 작동하며 네이티브 리소스에는 접근할 수 없도록 되어 있습니다. +하지만 Electron은 웹 페이지 내에서 io.js(node.js) API를 사용하여 low-level 수준으로 운영체제와 상호작용할 수 있습니다. + +### 메인 프로세스와 랜더러 프로세스의 차이점 + +메인 프로세스는 `BrowserWindow` Class를 사용하여 새로운 창을 만들 수 있습니다. +`BrowserWindow` 인스턴스는 따로 분리된 프로세스에서 랜더링 되며 이 프로세스를 랜더러 프로세스라고 합니다. +`BrowserWindow` 인스턴스가 소멸할 때 그 창의 랜더러 프로세스도 같이 소멸합니다. + +메인 프로세스는 모든 웹 페이지와 랜더러 프로세스를 관리하며 랜더러 프로세스는 각각의 프로세스에 고립되며 웹 페이지의 작동에만 영향을 끼칩니다. + +웹 페이지 내에선 기본적으로 네이티브 GUI와 관련된 API를 호출할 수 없도록 설계 되어 있습니다. +왜냐하면 웹 페이지 내에서 네이티브 GUI 리소스를 관리하는 것은 보안에 취약하고 리소스를 누수시킬 수 있기 때문입니다. +꼭 웹 페이지 내에서 API를 사용해야 한다면 메인 프로세스에서 그 작업을 처리할 수 있도록 메인 프로세스와 통신을 해야 합니다. + +Electron에는 메인 프로세스와 랜더러 프로세스 사이에 통신을 할 수 있도록 [ipc](../api/ipc-renderer.md) 모듈을 제공하고 있습니다. +또는 [remote](../api/remote.md) 모듈을 사용하여 RPC 스타일로 통신할 수도 있습니다. + +## 첫번째 Electron 앱 만들기 + +보통 Electron 앱은 다음과 같은 폴더 구조를 가집니다: + +```text +your-app/ +├── package.json +├── main.js +└── index.html +``` + +`package.json`은 node 모듈의 package.json과 같습니다. +그리고 `main` 필드에 스크립트 파일을 지정하면 메인 프로세스의 엔트리 포인트로 사용합니다. +예를 들어 사용할 수 있는 `package.json`은 다음과 같습니다: + +```json +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +__알림__: 만약 `main` 필드가 `package.json`에 설정되어 있지 않으면 Electron은 자동으로 같은 디렉터리의 `index.js`를 로드합니다. + +반드시 `main.js`에서 창을 만들고 시스템 이밴트를 처리해야합니다. 대표적인 예제로 다음과 같이 작성할 수 있습니다: + +```javascript +var app = require('app'); // 어플리케이션 기반을 조작 하는 모듈. +var BrowserWindow = require('browser-window'); // 네이티브 브라우저 창을 만드는 모듈. + +// Electron 개발자에게 crash-report를 보냄. +require('crash-reporter').start(); + +// 윈도우 객체를 전역에 유지합니다. 만약 이렇게 하지 않으면 +// 자바스크립트 GC가 일어날 때 창이 자동으로 닫혀버립니다. +var mainWindow = null; + +// 모든 창이 닫히면 어플리케이션 종료. +app.on('window-all-closed', function() { + // OS X의 대부분의 어플리케이션은 유저가 Cmd + Q 커맨드로 확실하게 종료하기 전까지 + // 메뉴바에 남아 계속 실행됩니다. + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// 이 메서드는 Electron의 초기화가 모두 끝나고 +// 브라우저 창을 열 준비가 되었을 때 호출됩니다. +app.on('ready', function() { + // 새로운 브라우저 창을 생성합니다. + mainWindow = new BrowserWindow({width: 800, height: 600}); + + // 그리고 현재 디렉터리의 index.html을 로드합니다. + mainWindow.loadUrl('file://' + __dirname + '/index.html'); + + // 개발자 콘솔을 엽니다. + mainWindow.openDevTools(); + + // 창이 닫히면 호출됩니다. + mainWindow.on('closed', function() { + // 윈도우 객체의 참조를 삭제합니다 보통 멀티 윈도우 지원을 위해 + // 윈도우 객체를 배열에 저장하는 경우가 있는데 이 경우 + // 해당하는 모든 윈도우 객체의 참조를 삭제해 주어야 합니다. + mainWindow = null; + }); +}); +``` + +마지막으로, 사용자에게 보여줄 `index.html` 웹 페이지의 예제입니다: + +```html + + + + 헬로 월드! + + +

헬로 월드!

+ 이 어플리케이션은 io.js 과 + Electron 을 사용합니다. + + +``` + +## 앱 실행하기 + +앱을 작성한 후 [어플리케이션 배포](application-distribution.md) 가이드를 따라 앱을 패키징 하고 패키징한 앱을 실행할 수 있습니다. +또한 Electron 실행파일을 다운로드 받아 바로 실행해 볼 수도 있습니다. + +### electron-prebuilt + +`npm`을 통해 `electron-prebuilt` 패키지를 전역에 설치하면 간단한 명령으로 앱을 실행할 수 있습니다. + +앱 디렉터리 내에서 이렇게 실행합니다: + +```bash +electron . +``` + +또는 앱 디렉터리 밖에서 앱 디렉터리를 입력해도 됩니다: + +```bash +electron app +``` + +npm 모듈을 로컬에 설치했다면 이렇게 실행할 수 있습니다: + +```bash +./node_modules/.bin/electron . +``` + +### 임의로 다운로드 받은 Electron + +만약 Electron 바이너리를 임의로 다운로드 받았다면 다음과 같이 앱 디렉터리에 놓고 실행하면 됩니다. + +#### Windows + +```bash +$ .\electron\electron.exe your-app\ +``` + +#### Linux + +```bash +$ ./electron/electron your-app/ +``` + +#### OS X + +```bash +$ ./Electron.app/Contents/MacOS/Electron your-app/ +``` + +어플리케이션 실행파일은 `Electron`의 release 패키지에 포함되어 있습니다. +[여기](https://github.com/atom/electron/releases)에서 다운로드 받을 수 있습니다. + +### 배포용 파일 만들기 + +어플리케이션 작성을 완료했다면 [어플리케이션 배포](application-distribution.md) 가이드를 통해 제작한 앱을 본격적으로 배포할 수 있습니다. \ No newline at end of file diff --git a/docs-translations/ko/tutorial/using-native-node-modules.md b/docs-translations/ko/tutorial/using-native-node-modules.md new file mode 100644 index 000000000000..913c300be881 --- /dev/null +++ b/docs-translations/ko/tutorial/using-native-node-modules.md @@ -0,0 +1,54 @@ +# 네이티브 node 모듈 사용하기 + +Electron에선 node.js 네이티브 모듈이 지원됩니다. 하지만 Electron은 공식 node.js의 V8 엔진과는 다른 V8 버전을 사용합니다. +이러한 이유로 네이티브 모듈을 사용하기 위해선 Electron의 V8 버전에 맞춰 네이티브 모듈을 다시 빌드하고 헤더를 변경해야 합니다. + +## 네이티브 node 모듈 호환성 + +Node v0.11.x 버전부터는 V8 API의 중대한 변경이 있었습니다. 하지만 대부분의 네이티브 모듈은 Node v0.10.x 버전을 타겟으로 작성 되었기 때문에 +새로운 Node 또는 io.js 버전에서 작동하지 않을 수 있습니다. Electron은 내부적으로 __io.js v3.1.0__ 버전을 사용하기 때문에 호환성 문제가 발생할 수 있습니다. + +이 문제를 해결하기 위해선 모듈이 v0.11.x 또는 최신 버전을 지원할 수 있도록 변경해야 합니다. +현재 [많은 모듈들](https://www.npmjs.org/browse/depended/nan)이 안정적으로 두 버전 모두 지원하고 있지만 오래된 모듈의 경우 여전히 Node v0.10.x 버전만을 지원하고 있습니다. +예를 들어 [nan](https://github.com/rvagg/nan) 모듈을 사용해야 한다면 Node v0.11.x 또는 최신 버전의 Node와 io.js로 포팅 할 필요가 있습니다. + +## 네이티브 모듈 설치하는 방법 + +네이티브 모듈을 설치하는 방법은 세 가지 종류가 있습니다. + +### 쉬운 방법 + +[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) 패키지를 사용하면 빠르고 간단하게 네이티브 모듈을 다시 빌드할 수 있습니다. + +다음 예제는 `electron-rebuild`를 통해 자동으로 모듈의 헤더를 다운로드하고 네이티브 모듈을 빌드합니다: + +```sh +npm install --save-dev electron-rebuild + +# 필요한 네이티브 모듈을 `npm install`로 설치한 후 다음 명령을 실행하세요: +node ./node_modules/.bin/electron-rebuild +``` + +### node-gyp을 이용한 방법 + +Node 모듈을 `node-gyp`를 사용하여 Electron을 타겟으로 빌드할 때는 `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`은 Electron의 버전입니다. +`--dist-url=...`은 헤더를 다운로드 하는 주소입니다. `--arch=x64`는 64비트 시스템을 타겟으로 빌드 한다는 것을 `node-gyp`에게 알려줍니다. + +### npm을 이용한 방법 + +또한 `npm`을 통해 설치할 수도 있습니다. +환경변수가 필요한 것을 제외하고 일반 Node 모듈을 설치하는 방법과 완전히 똑같습니다: + +```bash +export npm_config_disturl=https://atom.io/download/atom-shell +export npm_config_target=0.29.1 +export npm_config_arch=x64 +HOME=~/.electron-gyp npm install module-name +``` diff --git a/docs-translations/ko/tutorial/using-pepper-flash-plugin.md b/docs-translations/ko/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 000000000000..f47f20d8a71e --- /dev/null +++ b/docs-translations/ko/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,60 @@ +# Pepper 플래시 플러그인 사용하기 + +Electron은 Pepper 플래시 플러그인을 지원합니다. +Pepper 플래시 플러그인을 사용하려면 Pepper 플래시 플러그인의 위치를 지정해야 합니다. + +## 플래시 플러그인 준비하기 + +크롬 브라우저의 `chrome://plugins` 페이지에 접속한 후 `세부정보`에서 플래시 플러그인의 위치와 버전을 찾을 수 있습니다. +Electron에서 플래시 플러그인을 지원하기 위해선 이 두 가지를 복사해 와야 합니다. + +## Electron 스위치 추가 + +플러그인을 사용하려면 Electron 커맨드 라인에 `--ppapi-flash-path` 와 `ppapi-flash-version` 플래그를 app의 ready 이벤트가 호출되기 전에 추가해야 합니다. +그리고 `browser-window`에 `plugins` 스위치도 추가해야합니다. + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +// Report crashes to our server. +require('crash-reporter').start(); + +var mainWindow = null; + +// Quit when all windows are closed. +app.on('window-all-closed', function() { + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// 플래시 플러그인의 위치를 설정합니다. +// Windows의 경우, /path/to/pepflashplayer.dll +// Mac의 경우, /path/to/PepperFlashPlayer.plugin +// 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/ko/tutorial/using-selenium-and-webdriver.md b/docs-translations/ko/tutorial/using-selenium-and-webdriver.md new file mode 100644 index 000000000000..33dc6311ce35 --- /dev/null +++ b/docs-translations/ko/tutorial/using-selenium-and-webdriver.md @@ -0,0 +1,117 @@ +# Selenium 과 WebDriver 사용하기 + +[ChromeDriver - WebDriver for Chrome][chrome-driver]로 부터 인용: + +> WebDriver는 많은 브라우저에서 웹 앱을 자동적으로 테스트하는 툴입니다. +> 이 툴킷은 웹 페이지를 자동으로 탐색하고 유저 폼을 사용하거나 자바스크립트를 실행하는 등의 작업을 수행할 수 있습니다. +> ChromeDriver는 Chromium의 WebDriver wire 프로토콜 스텐드얼론 서버 구현입니다. +> Chromium 과 WebDriver 팀 멤버에 의해 개발되었습니다. + +Electron에서 `chromedriver`를 사옹하려면 드라이버에서 Electron을 찾을 수 있도록 해야 하며 +Electron은 Chrome 브라우저와 비슷하다는 점을 기억해야 합니다. + +## WebDriverJs 설정하기 + +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs)는 WebDriver를 사용하여 테스트 할 수 있도록 도와주는 node 패키지입니다. +다음 예제를 참고하세요. + +### 1. 크롬 드라이버 시작 + +먼저, `chromedriver` 바이너리를 다운로드 받고 실행합니다: + +```bash +$ ./chromedriver +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +포트 `9515`는 나중에 사용하므로 기억해 놓습니다. + +### 2. WebDriverJS 설치 + +```bash +$ npm install selenium-webdriver +``` + +### 3. 크롬 드라이버에 연결 + +`selenium-webdriver`를 Electron과 같이 사용하는 방법은 기본적으로 upstream과 같습니다. +한가지 다른점이 있다면 수동으로 크롬 드라이버 연결에 대해 설정하고 Electron 실행파일의 위치를 전달합니다: + +```javascript +var webdriver = require('selenium-webdriver'); + +var driver = new webdriver.Builder() + // 작동하고 있는 크롬 드라이버의 포트 "9515"를 사용합니다. + .usingServer('http://localhost:9515') + .withCapabilities({chromeOptions: { + // 여기에 사용중인 Electron 바이너리의 경로를 지정하세요. + 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 패키지입니다. + +### 1. 크롬 드라이버 시작 + +먼저, `chromedriver` 바이너리를 다운로드 받고 실행합니다: + +```bash +$ chromedriver --url-base=/wd/hub --port=9515 +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +포트 `9515`는 나중에 사용하므로 기억해 놓읍시다 + +### 2. WebDriverIO 설치 + +```bash +$ npm install webdriverio +``` + +### 3. 크롬 드라이버에 연결 +```javascript +var webdriverio = require('webdriverio'); +var options = { + host: "localhost", // localhost에서 작동중인 크롬 드라이버 서버를 사용합니다. + port: 9515, // 연결할 크롬 드라이버 서버의 포트를 설정합니다. + desiredCapabilities: { + browserName: 'chrome', + chromeOptions: {binary: '/Path-to-Your-App.app/Electron'} // Electron 바이너리의 위치 + } +}; + +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을 다시 빌드하지 않는 경우 간단히 어플리케이션을 Electron의 리소스 디렉터리에 +[배치](application-distribution.md)하여 바로 테스트 할 수 있습니다. + +[chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ diff --git a/docs-translations/pt-BR/README.md b/docs-translations/pt-BR/README.md new file mode 100644 index 000000000000..e60d9505a24a --- /dev/null +++ b/docs-translations/pt-BR/README.md @@ -0,0 +1,71 @@ +## Guias + +* [Distribuir Aplicação](tutorial/application-distribution.md) +* [Empacotamento da aplicação](tutorial/application-packaging.md) +* [Usando módulos nativos](../../docs/tutorial/using-native-node-modules.md) +* [Depuração do processo principal](../../docs/tutorial/debugging-main-process.md) +* [Usando Selenium e WebDriver](../../docs/tutorial/using-selenium-and-webdriver.md) +* [Extensão DevTools](../../docs/tutorial/devtools-extension.md) +* [Usando o plugin papper flash](../../docs/tutorial/using-pepper-flash-plugin.md) + +## Tutoriais + +* [Introdução](../../docs/tutorial/quick-start.md) +* [A integração com o ambiente de desenvolvimento](../../docs/tutorial/desktop-environment-integration.md) +* [Evento de detecção on-line/off-line](../../docs/tutorial/online-offline-events.md) + +## API - Referencias + +* [Sinopse](../../docs/api/synopsis.md) +* [Processos](api/process.md) +* [Aceleradores (Teclas de Atalho)](api/accelerator.md) +* [Parâmetros CLI suportados (Chrome)](../../docs/api/chrome-command-line-switches.md) + +DOM elementos personalizados: + +* [Objeto `File`](../../docs/api/file-object.md) +* [Tag ``](../../docs/api/web-view-tag.md) +* [Função `window.open`](../../docs/api/window-open.md) + +Os principais módulos: + +* [app](../../docs/api/app.md) +* [auto-updater](../../docs/api/auto-updater.md) +* [browser-window](../../docs/api/browser-window.md) +* [content-tracing](../../docs/api/content-tracing.md) +* [dialog](../../docs/api/dialog.md) +* [global-shortcut](../../docs/api/global-shortcut.md) +* [ipc (main process)](../../docs/api/ipc-main-process.md) +* [menu](../../docs/api/menu.md) +* [menu-item](../../docs/api/menu-item.md) +* [power-monitor](../../docs/api/power-monitor.md) +* [power-save-blocker](../../docs/api/power-save-blocker.md) +* [protocol](../../docs/api/protocol.md) +* [session](../../docs/api/session.md) +* [webContents](../../docs/api/web-contents.md) +* [tray](../../docs/api/tray.md) + +Módulos do renderizador (web page): + +* [ipc (renderer)](../../docs/api/ipc-renderer.md) +* [remote](../../docs/api/remote.md) +* [web-frame](../../docs/api/web-frame.md) + +Módulos de ambos os processos: + +* [clipboard](../../docs/api/clipboard.md) +* [crash-reporter](../../docs/api/crash-reporter.md) +* [native-image](../../docs/api/native-image.md) +* [screen](../../docs/api/screen.md) +* [shell](api/shell.md) + +## Desenvolvimento + +* [Estilo de código](../../docs/development/coding-style.md) +* [Estrutura de diretórios padrão](../../docs/development/source-code-directory-structure.md) +* [Diferenças técnicas do NW.js (antigo node-webkit)](../../docs/development/atom-shell-vs-node-webkit.md) +* [Visão geral do build](../../docs/development/build-system-overview.md) +* [Instrução de build (Mac)](../../docs/development/build-instructions-osx.md) +* [Instrução de build (Windows)](../../docs/development/build-instructions-windows.md) +* [Instrução de build (Linux)](../../docs/development/build-instructions-linux.md) +* [Configurando um symbol server no debugger](../../docs/development/setting-up-symbol-server.md) \ No newline at end of file diff --git a/docs-translations/pt-BR/api/accelerator.md b/docs-translations/pt-BR/api/accelerator.md new file mode 100644 index 000000000000..87987258b11c --- /dev/null +++ b/docs-translations/pt-BR/api/accelerator.md @@ -0,0 +1,46 @@ +# Acelerador (teclas de atalhos) + +Um acelerador é uma string que representa um atalho de tecla. Isso pode conter +multiplos modificadores e códigos chaves, combinado pelo caracter `+`. + +Exemplos: + +* `Command+A` +* `Ctrl+Shift+Z` + +## Aviso sobre plataformas + +No Linux e no Windows a tecla `Command` não tem nenhum efeito, +então use `CommandOrControl` que representa a tecla `Command` existente no OSX e +`Control` no Linux e no Windows para definir aceleradores (atalhos). + +A chave `Super` está mapeada para a tecla `Windows` para Windows e Linux, +e para a tecla `Cmd` para OSX. + +## Modificadores disponiveis + +* `Command` (ou `Cmd` abreviado) +* `Control` (ou `Ctrl` abreviado) +* `CommandOrControl` (ou `CmdOrCtrl` abreviado) +* `Alt` +* `Shift` +* `Super` + +## Códigos chaves disponiveis + +* `0` to `9` +* `A` to `Z` +* `F1` to `F24` +* Punctuations like `~`, `!`, `@`, `#`, `$`, etc. +* `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` \ No newline at end of file diff --git a/docs-translations/pt-BR/api/process.md b/docs-translations/pt-BR/api/process.md new file mode 100644 index 000000000000..3da0dc58391b --- /dev/null +++ b/docs-translations/pt-BR/api/process.md @@ -0,0 +1,22 @@ +# process +O objeto `process` no Electron tem as seguintes diferenças de um upstream node: + +* `process.type` String - Tipo de processo, pode ser `browser` (i.e. main process) +ou `renderer`. +* `process.versions['electron']` String - Versão do Electron. +* `process.versions['chrome']` String - Versão do Chromium. +* `process.resourcesPath` String - Caminho para os códigos fontes JavaScript. + +# Métodos +O objeto `process` tem os seguintes método: + +### `process.hang` + +Afeta a thread principal do processo atual. + +## process.setFdLimit(MaxDescritores) _OS X_ _Linux_ + +* `maxDescriptors` Integer + +Define o limite do arquivo descritor para `maxDescriptors` ou para o limite do OS, +o que for menor para o processo atual. \ No newline at end of file diff --git a/docs-translations/pt-BR/api/shell.md b/docs-translations/pt-BR/api/shell.md new file mode 100644 index 000000000000..7c3f24ade509 --- /dev/null +++ b/docs-translations/pt-BR/api/shell.md @@ -0,0 +1,43 @@ +# shell + +O módulo `shell` fornece funções relacionadas intereções com o OS do usuário. + +Um exemplo para abrir uma URL no browser padrão do usuário: + +```javascript +var shell = require('shell'); +shell.openExternal('https://github.com'); +``` + +## Métodos + +O módulo `shell` tem os seguintes métodos: + +### `shell.showItemInFolder(fullPath)` + +* `fullPath` String + +Exibe o arquivo no gerenciador de arquivos padrão do sistema. Se possivel, seleciona o arquivo automaticamente. + +### `shell.openItem(fullPath)` + +* `fullPath` String + +Abre o arquivo em seu programa padrão. + +### `shell.openExternal(url)` + +* `url` String + +Abre o arquivo seguido de um protocol em seu programa padrão. (Por +exemplo, mailto:foo@bar.com.) + +### `shell.moveItemToTrash(fullPath)` + +* `fullPath` String + +Move o arquivo para a lixeira e retorna um boolean com o resultado da operação. + +### `shell.beep()` + +Toca um som beep. \ No newline at end of file diff --git a/docs-translations/pt-BR/styleguide.md b/docs-translations/pt-BR/styleguide.md new file mode 100644 index 000000000000..817fa3c81e39 --- /dev/null +++ b/docs-translations/pt-BR/styleguide.md @@ -0,0 +1,77 @@ +# Styleguide do Electron + +Localize a seção apropriada para a sua tarefa: [Lendo a documentação do Electron](#) +ou [Escrevendo documentação para o Electron](#). + +## Escrevendo documentação para o Electron + +Estas são as formas que escrevemos a documentação do Electron. + +- No Máximo um `h1` por página. +- Usar `bash` ao invés de` cmd` em blocos de código (por causa do syntax highlighter). +- Títulos `h1` deve coincidir com o nome do objeto (i.e. `browser-window` → +`BrowserWindow`). +- Nomes de arquivos separados por hífen. +- Adicionar pelo menos uma descrição a cada frase. +- Métodos de cabeçalhos são envolto em `code`. +- Cabeçalhos de eventos são envolto em single 'quotation' marks. +- Não há listas com identação com mais de 2 níveis (por causa do markdown). +- Adicione títulos nas seções: Events, Class Methods e Instance Methods. +- Use 'will' ao invéis de 'would' ao descrever os resultados. +- Eventos e métodos são com cabeçalhos `h3`. +- Argumentos opcionais escritos como `function (required[, optional])`. +- Argumentos opcionais são indicados quando chamado na lista. +- Comprimento da linha é de 80 caracteres com colunas quebradas. +- Métodos específicos para uma plataforma são postos em itálico seguindo o cabeçalho do método. + - ```### `method(foo, bar)` _OS X_``` + +## Lendo a documentação do Electron + +Aqui estão algumas dicas de como entender a sintaxe da documentacão do Electron. + +### Métodos + +Um exemplo de [method](https://developer.mozilla.org/en-US/docs/Glossary/Method) +documentação: + +--- + +`methodName(required[, optional]))` + +* `require` String, **required** +* `optional` Integer + +--- + +O nome do método é seguido pelos seus argumentos. Argumentos opcionais são +simbolizada por colchetes que cercam o argumento opcional, bem como a vírgula +requerido se este argumento opcional segue outro argumento. + +Abaixo o método é para obter informações detalhadas sobre cada um dos argumentos. O tipo +de argumento é simbolizada por qualquer um dos tipos mais comuns: [`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 Referência/Global_Objects/Object), [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) +ou um tipo personalizado como o de Electron [`webContent`](api/web-content.md). + +### Eventos + +Um exemplo de [evento](https://developer.mozilla.org/en-US/docs/Web/API/Event) +documentação: + +--- + +Event: 'wake-up' + +Returns: + +* `time` String + +--- + +O evento é uma cadeia que é utilizada após um `.on` em um método listner. Se ela retorna +-lhe um valor e seu tipo é observado abaixo. Se você quiser um método para esctuar e responder +crie algo parecido com o exemplo abaixo: + +```javascript +Alarm.on('wake-up', function(time) { + console.log(time) +}) +``` \ No newline at end of file diff --git a/docs-translations/pt-BR/tutorial/application-distribution.md b/docs-translations/pt-BR/tutorial/application-distribution.md new file mode 100644 index 000000000000..41f2457c408b --- /dev/null +++ b/docs-translations/pt-BR/tutorial/application-distribution.md @@ -0,0 +1,118 @@ +# 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 +`Electron.app/Contents/Resources/`, +no Linux e no Windows é em `resources/`): + +No OSX: + +```text +electron/Electron.app/Contents/Resources/app/ +├── package.json +├── main.js +└── index.html +``` + +No Windows e Linux: + +```text +electron/resources/app +├── package.json +├── main.js +└── index.html +``` + +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 +usuários finais. + +## Empacotando sua aplicação em um arquivo. + +Além de copiar todos os seus arquivos fontes para a distribuição, você também pode +empacotar seu aplicativo em um arquivo [asar](https://github.com/atom/asar) para evitar +de expor seu código fonte aos usuários finais. + +Para usar um arquivo `asar` ao invés da pasta `app` você precisa mudar o nome do +arquivo para `app.asar` e colocá-lo sob o diretório de recursos do Electron como +mostrado abaixo, então o Electron vai ler o arquivo e iniciar a aplicação a partir dele. + +No OSX: + +```text +electron/Electron.app/Contents/Resources/ +└── app.asar +``` + +No Windows e Linux: + +```text +electron/resources/ +└── app.asar +``` + +Mais detalhes podem ser encontrados em [Empacotamento da aplicação](../../../docs/tutorial/application-packaging.md). + +## Renomeando a marca Electron na sua distribuição + +Depois de empacotar seu aplicativo Electron, você vai querer renomear a marca Electron +antes de distribuí-lo aos usuários. + +### Janelas + +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). + +### OS X + +Você pode renomear `Electron.app` para o nome que desejar e também pode mudar o nome +do `CFBundleDisplayName`, `CFBundleIdentifier` e os campos em `CFBundleName` +nos seguinte arquivos: + +* `Electron.app/Contents/Info.plist` +* `Electron.app/Contents/frameworks/Electron Helper.app/Contents/Info.plist` + +Você também pode renomear o arquivo de ajuda para evitar a exibição de `Electron Helper` no +Monitor de Atividades, mas certifique-se de também renomear o arquivo de ajuda no executável do +aplicativo. + +A estrutura de uma aplicação renomada seria assim: + +``` +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 + +Você pode renomear o executável `electron` para o nome que desejar. + +## Renomeando a marca Electron do código fonte. + +Também é possível fazer renomear a marca Electron do código fonte, alterando o nome do produto e +reconstruí-lo a partir da fonte, para fazer isso você precisa modificar o arquivo `atom.gyp`. + +### grunt-build-atom-shell + +A modificação do código fonte do Electron para ganhar a sua marca pode ser muito complexa, por isso, +uma tarefa para o Grunt foi criado e irá cuidar desta tarefa automaticamente para você: +[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 diff --git a/docs-translations/pt-BR/tutorial/application-packaging.md b/docs-translations/pt-BR/tutorial/application-packaging.md new file mode 100644 index 000000000000..f55cbb2f7d64 --- /dev/null +++ b/docs-translations/pt-BR/tutorial/application-packaging.md @@ -0,0 +1,158 @@ +# Empacotamento da aplicação + +Para proteger os recursos e o código fonte da sua aplicação você pode optar por +empacotar a sua aplicação em um arquivo [asar](https://github.com/atom/asar), isto é possível com poucas +alterações em seu código. + +## Gerando um arquivo `asar` + +Um arquivo [asar][asar] é um formato parecido com tar ou zip bem simples que concatena arquivos +em um único arquivo. O Electron pode ler arquivos arbitrários a partir dele sem descompacatar +o arquivo inteiro. + +Passos para empacotar a sua aplicação em um arquivo `asar`: + +### 1. Instale o utilitário asar + +```bash +$ npm install -g asar +``` + +### 2. Empacote a sua aplicação + +```bash +$ asar pack your-app app.asar +``` + +## Usando arquivos `asar` + +No Electron existem dois conjuntos de APIs: Node APIs fornecidas pelo Node.js e Web +APIs fornecidas pelo Chromium. Ambas as APIs suportam a leitura de arquivos `asar`. + +### Node API + +As API's do Node como `fs.readFile` e `require` tratam os pacotes `asar` +como diretórios virtuais e os arquivos dentro dele como arquivos normais. + +Por exemplo, temos um arquivo `example.asar` sob `/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 +``` + +Lendo um arquivo em pacote `asar`: + +```javascript +var fs = require('fs'); +fs.readFileSync('/path/to/example.asar/file.txt'); +``` + +Listando todos os arquivos a partir da raiz: + +```javascript +var fs = require('fs'); +fs.readdirSync('/path/to/example.asar'); +``` + +Utilizando um módulo dentro do pacote `asar`: + +```javascript +require('/path/to/example.asar/dir/module.js'); +``` + +Você também pode renderizar uma página web apartir de um arquivo `asar` utilizando o módulo `BrowserWindow`: + +```javascript +var BrowserWindow = require('browser-window'); +var win = new BrowserWindow({width: 800, height: 600}); +win.loadUrl('file:///path/to/example.asar/static/index.html'); +``` + +### API Web + +Em uma página web, arquivos em um pacote `asar` pode ser solicitado com o protocolo `file:`. +Como a API Node, arquivos `asar` são tratadas como diretórios. + +Por exemplo, para obter um arquivo com `$ .get`: + +```html + +``` + +### Tratando um pacote `asar` como um arquivo normal + +Para alguns casos, precisamos verificar o checksum de um pacote `asar`, para fazer isto, precisamos ler +o arquivo `asar` como um arquivo normal. Para isto, você pode usar o built-in +`original-fs` que fornece a API `fs`, sem apoio a arquivos asar`: + +```javascript +var originalFs = require('original-fs'); +originalFs.readFileSync('/path/to/example.asar'); +``` + +## Limitaçõs na API Node + +Mesmo fazendo grandes esforços para pacotes `asar` ser tratado no Node como diretórios, +ainda existem limitações devido a natureza de baixo nível do Node + +### Arquivos `asar` são somente leitura + +Os arquivos `asar` não podem ser modificados. + +### Diretório de trabalho não pode ser comportar como diretório de arquivos + +Embora pacotes `asar` são tratadas como diretórios, não há +diretórios reais no sistema de arquivos, assim você nunca pode definir o diretório de trabalho para +diretórios em pacotes `asar`, passando-os como a opção `cwd` de algumas APIs +também irá causar erros. + +### Descompactação extra em algumas APIs + +A maioria das APIs `fs` pode ler um arquivo ou obter informações de um arquivo a partir de pacotes `asar` +sem descompacta-lo, mas para algumas APIs da rota real o Electron irá extrair o arquivo necessário para um +arquivo temporário e passar o caminho do arquivo temporário para as APIs, +isso adiciona um pouco de sobrecarga para essas APIs. + +APIs que requer descompactação extras são: + +* `child_process.execFile` +* `fs.open` +* `fs.openSync` +* `process.dlopen` - Usado por `require` em módulos nativos + +### Falsas informações de status do módulo `fs.stat` + +O objeto `Stats` retornado por` fs.stat` e outras funções relacionadas não são informações confiáveis, +você não deve confiar no objeto `Stats` exceto para obter o +tamanho do arquivo e verificação de tipo de arquivo. + +## Adicionando arquivos em um pacote `asar` + +Como dito acima, algumas APIs deo Node irá descompactar o arquivo para quando o filesystem +requsistar, além dos problemas de desempenho, ele também pode levar a falsos alertas +de vírus. + +Para contornar isso, você pode descompactar alguns arquivos usando a +opção `--unpack`, um exemplo de exclusão de bibliotecas compartilhadas de módulos nativos +é: + +```bash +$ asar pack app app.asar --unpack *.node +``` + +Depois de executar o comando, além do `app.asar`, há também +`app.asar.unpacked` pasta gerada que contém os arquivos descompactados, você +deve copiá-lo juntamente com `app.asar` quando enviá-lo para os usuários. + +Mais informações no repositório [asar](https://github.com/atom/asar) \ No newline at end of file diff --git a/docs-translations/zh-CN/README.md b/docs-translations/zh-CN/README.md new file mode 100644 index 000000000000..124d030c46ee --- /dev/null +++ b/docs-translations/zh-CN/README.md @@ -0,0 +1,70 @@ +## 向导 + +* [应用部署](tutorial/application-distribution.md) +* [应用打包](tutorial/application-packaging.md) +* [使用原生模块](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/quick-start.md) +* [桌面环境集成](tutorial/desktop-environment-integration.md) +* [在线/离线事件探测](tutorial/online-offline-events.md) + +## API文档 + +* [简介](api/synopsis.md) +* [进程对象](api/process.md) +* [支持的Chrome命令行开关](api/chrome-command-line-switches.md) + +定制的DOM元素: + +* [`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) +* [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) +* [protocol](api/protocol.md) +* [session](api/session.md) +* [webContents](api/web-contents.md) +* [tray](api/tray.md) + +渲染进程(网页)可用的模块: + +* [ipc (renderer)](api/ipc-renderer.md) +* [remote](api/remote.md) +* [web-frame](api/web-frame.md) + +两种进程都可用的模块: + +* [clipboard](api/clipboard.md) +* [crash-reporter](api/crash-reporter.md) +* [native-image](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.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) diff --git a/docs-translations/zh-CN/api/accelerator.md b/docs-translations/zh-CN/api/accelerator.md new file mode 100644 index 000000000000..8858d18e856e --- /dev/null +++ b/docs-translations/zh-CN/api/accelerator.md @@ -0,0 +1,46 @@ +# Accelerator + +An accelerator is a string that represents a keyboard shortcut. It can contain +multiple modifiers and key codes, combined by the `+` character. + +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. + +The `Super` key is mapped to the `Windows` key on Windows and Linux and +`Cmd` on OS X. + +## Available modifiers + +* `Command` (or `Cmd` for short) +* `Control` (or `Ctrl` for short) +* `CommandOrControl` (or `CmdOrCtrl` for short) +* `Alt` +* `Shift` +* `Super` + +## Available key codes + +* `0` to `9` +* `A` to `Z` +* `F1` to `F24` +* Punctuations like `~`, `!`, `@`, `#`, `$`, etc. +* `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` diff --git a/docs-translations/zh-CN/api/global-shortcut.md b/docs-translations/zh-CN/api/global-shortcut.md new file mode 100644 index 000000000000..ff0b28832a10 --- /dev/null +++ b/docs-translations/zh-CN/api/global-shortcut.md @@ -0,0 +1,60 @@ +# global-shortcut + +`global-shortcut` 模块可以便捷的为您设置(注册/注销)各种自定义操作的快捷键. + +**Note**: 使用此模块注册的快捷键是系统全局的(QQ截图那种), 不要在应用模块(app module)响应 `ready` +消息前使用此模块(注册快捷键). + +```javascript +var app = require('app'); +var globalShortcut = require('global-shortcut'); + +app.on('ready', function() { + // Register a 'ctrl+x' shortcut listener. + var ret = globalShortcut.register('ctrl+x', function() { + console.log('ctrl+x is pressed'); + }) + + if (!ret) { + console.log('registration failed'); + } + + // Check whether a shortcut is registered. + console.log(globalShortcut.isRegistered('ctrl+x')); +}); + +app.on('will-quit', function() { + // Unregister a shortcut. + globalShortcut.unregister('ctrl+x'); + + // Unregister all shortcuts. + globalShortcut.unregisterAll(); +}); +``` + +## Methods + +`global-shortcut` 模块包含以下函数: + +### `globalShortcut.register(accelerator, callback)` + +* `accelerator` [Accelerator](accelerator.md) +* `callback` Function + +注册 `accelerator` 快捷键. 当用户按下注册的快捷键时将会调用 `callback` 函数. + +### `globalShortcut.isRegistered(accelerator)` + +* `accelerator` [Accelerator](accelerator.md) + +查询 `accelerator` 快捷键是否已经被注册过了,将会返回 `true`(已被注册) 或 `false`(未注册). + +### `globalShortcut.unregister(accelerator)` + +* `accelerator` [Accelerator](accelerator.md) + +注销全局快捷键 `accelerator`. + +### `globalShortcut.unregisterAll()` + +注销本应用注册的所有全局快捷键. diff --git a/docs-translations/zh-CN/api/shell.md b/docs-translations/zh-CN/api/shell.md new file mode 100644 index 000000000000..a30334dcdc9c --- /dev/null +++ b/docs-translations/zh-CN/api/shell.md @@ -0,0 +1,45 @@ +# shell + +`shell` 模块提供了集成其他桌面客户端的关联功能. + + +在用户默认浏览器中打开URL的示例: + +```javascript +var shell = require('shell'); + +shell.openExternal('https://github.com'); +``` + +## Methods + +`shell` 模块包含以下函数: + +### `shell.showItemInFolder(fullPath)` + +* `fullPath` String + +打开文件所在文件夹,一般情况下还会选中它. + +### `shell.openItem(fullPath)` + +* `fullPath` String + +以默认打开方式打开文件. + +### `shell.openExternal(url)` + +* `url` String + +以系统默认设置打开外部协议.(例如,mailto: somebody@somewhere.io会打开用户默认的邮件客户端) + + +### `shell.moveItemToTrash(fullPath)` + +* `fullPath` String + +删除指定路径文件,并返回此操作的状态值(boolean类型). + +### `shell.beep()` + +播放 beep 声音. diff --git a/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md b/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md new file mode 100644 index 000000000000..cf6859d50645 --- /dev/null +++ b/docs-translations/zh-CN/development/atom-shell-vs-node-webkit.md @@ -0,0 +1,29 @@ +# Electron 和 NW.js (原名 node-webkit) 在技术上的差异 + +__备注:Electron 的原名是 Atom Shell。__ + +与 NW.js 相似,Electron 提供了一个能通过 JavaScript 和 HTML 创建桌面应用的平台,同时集成 Node 来授予网页访问底层系统的权限。 + +但是这两个项目也有本质上的区别,使得 Electron 和 NW.js 成为两个相互独立的产品。 + +__1. 应用的入口__ + +在 NW.js 中,一个应用的主入口是一个页面。你在 `package.json` 中指定一个主页面,它会作为应用的主窗口被打开。 + +在 Electron 中,入口是一个 JavaScript 脚本。不同于直接提供一个URL,你需要手动创建一个浏览器窗口,然后通过 API 加载 HTML 文件。你还可以监听窗口事件,决定何时让应用退出。 + +Electron 的工作方式更像 Node.js 运行时。 Electron 的 APIs 更加底层,因此你可以它替代 [PhantomJS](http://phantomjs.org/) 做浏览器测试。 + +__2. 构建系统__ + +为了避免构建整个 Chromium 带来的复杂度,Electron 通过 [`libchromiumcontent`](https://github.com/brightray/libchromiumcontent) 来访问 Chromium 的 Content API。`libchromiumcontent` 是一个独立的、引入了 Chromium Content 模块及其所有依赖的共享库。用户不需要一个强劲的机器来构建 Electron。 + +__3. Node 集成__ + +在 NW.js,网页中的 Node 集成需要通过给 Chromium 打补丁来实现。但在 Electron 中,我们选择了另一种方式:通过各个平台的消息循环与 libuv 的循环集成,避免了直接在 Chromium 上做改动。你可以看 [`node_bindings`](../../atom/common/) 来了解这是如何完成的。 + +__4. 多上下文__ + +如果你是有经验的 NW.js 用户,你应该会熟悉 Node 上下文和 web 上下文的概念。这些概念的产生源于 NW.js 的实现方式。 + +通过使用 Node 的[多上下文](http://strongloop.com/strongblog/whats-new-node-js-v0-12-multiple-context-execution/)特性,Electron不需要在网页中引入新的 JavaScript 上下文。 diff --git a/docs-translations/zh-CN/development/coding-style.md b/docs-translations/zh-CN/development/coding-style.md new file mode 100644 index 000000000000..82219467b16e --- /dev/null +++ b/docs-translations/zh-CN/development/coding-style.md @@ -0,0 +1,30 @@ +# 编码规范 + +以下是 Electron 项目中代码书写规范的指导方针。 + +## C++ 和 Python + +对于 C++ 和 Python,我们追随 Chromium 的[Coding +Style](http://www.chromium.org/developers/coding-style)。你可以通过 `script/cpplint.py` 来检验所有文件是否符合要求。 + +我们使用的 Pyhton 版本是 Python 2.7。 + +其中 C++ 代码中用到了许多 Chromium 的抽象和类型,我们希望你对其有所熟悉。一个好的去处是 +Chromium 的[重要的抽象和数据结构](https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures)。这个文档提到了一些特殊的类型、域内类型(当超出作用域时会自动释放内存)、日志机制等等。 + +## CoffeeScript + +对于 CoffeeScript,我们追随 GitHub 的[Style +Guide](https://github.com/styleguide/javascript) 及如下规则: + +* 文件不应该以换行结尾,因为我们要匹配 Google 的规范。 +* 文件名应该以 `-` 作连接而不是 `_`,等等。 + `file-name.coffee` 而不是 `file_name.coffee`,因为在 + [github/atom](https://github.com/github/atom) 模块名通常都是 `module-name` 的形式。这条规则仅应用于 `.coffee` 文件。 + +## API 名称 + +当新建一个API时,我们应该倾向于 getters 和 setters 的方式,而不是 +jQuery 的单函数形式。例如,`.getText()` 和 `.setText(text)` + 优于 `.text([text])`。这里是相关的 +[讨论记录](https://github.com/atom/electron/issues/46)。 diff --git a/docs-translations/zh-CN/tutorial/desktop-environment-integration.md b/docs-translations/zh-CN/tutorial/desktop-environment-integration.md new file mode 100644 index 000000000000..f8fbdaaaf548 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/desktop-environment-integration.md @@ -0,0 +1,167 @@ +# 桌面环境集成 +不同的操作系统在各自的桌面应用上提供了不同的特性。例如,在 windows 上应用曾经打开的文件会出现在任务栏的跳转列表,在 Mac 上,应用可以把自定义菜单放在鱼眼菜单上。 + +本章将会说明怎样使用 Electron APIs 把你的应用和桌面环境集成到一块。 + +## 最近文档 (Windows & OS X) +Windows 和 OS X 提供获取最近文档列表的便捷方式,那就是打开跳转列表或者鱼眼菜单。 + +跳转列表: +![JumpList][1] + +鱼眼菜单: +![Dock Menu][2] + +为了增加一个文件到最近文件列表,你可以使用 [app.addRecentDocument][3] API: + +```` +var app = require('app'); +app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); +```` +或者你也可以使用 [app.clearRecentDocuments][4] API 来清空最近文件列表。 +```` +app.clearRecentDocuments(); +```` +## Windows 需注意 +为了这个特性在 Windows 上表现正常,你的应用需要被注册成为一种文件类型的句柄,否则,在你注册之前,文件不会出现在跳转列表。你可以在 [Application Registration][5] 里找到任何关于注册事宜的说明。 + +## OS X 需注意 +当一个文件被最近文件列表请求时,`app` 模块里的 `open-file` 事件将会被发出。 + +## 自定义的鱼眼菜单(OS X) +OS X 可以让开发者定制自己的菜单,通常会包含一些常用特性的快捷方式。 +### 菜单中的终端 +[Dock menu of Terminal.app][6] + +使用 `app.dock.setMenu` API 来设置你的菜单,这仅在 OS X 上可行: +```` +var app = require('app'); +var Menu = require('menu'); +var dockMenu = Menu.buildFromTemplate([ + { label: 'New Window', click: function() { console.log('New Window'); } }, + { label: 'New Window with Settings', submenu: [ + { label: 'Basic' }, + { label: 'Pro'} + ]}, + { label: 'New Command...'} +]); +app.dock.setMenu(dockMenu); +```` + +## 用户任务(Windows) +在 Windows,你可以特别定义跳转列表的 `Tasks` 目录的行为,引用 MSDN 的文档: + +> Applications define tasks based on both the program's features and the key things a user is expected to do with them. Tasks should be context-free, in that the application does not need to be running for them to work. They should also be the statistically most common actions that a normal user would perform in an application, such as compose an email message or open the calendar in a mail program, create a new document in a word processor, launch an application in a certain mode, or launch one of its subcommands. An application should not clutter the menu with advanced features that standard users won't need or one-time actions such as registration. Do not use tasks for promotional items such as upgrades or special offers. + +> It is strongly recommended that the task list be static. It should remain the same regardless of the state or status of the application. While it is possible to vary the list dynamically, you should consider that this could confuse the user who does not expect that portion of the destination list to change. + +### IE 的任务 +![IE][7] +不同于 OS X 的鱼眼菜单,Windows 上的用户任务表现得更像一个快捷方式,比如当用户点击一个任务,一个程序将会被传入特定的参数并且运行。 + +你可以使用 [app.setUserTasks][8] API 来设置你的应用中的用户任务: +```` +var app = require('app'); +app.setUserTasks([ + { + program: process.execPath, + arguments: '--new-window', + iconPath: process.execPath, + iconIndex: 0, + title: 'New Window', + description: 'Create a new window' + } +]); +```` +调用 `app.setUserTasks` 并传入空数组就可以清除你的任务列表: +```` +app.setUserTasks([]); +```` +当你的应用关闭时,用户任务会仍然会出现,在你的应用被卸载前,任务指定的图标和程序的路径必须是存在的。 + +### 缩略图工具栏 +在 Windows,你可以在任务栏上添加一个按钮来当作应用的缩略图工具栏。它将提供用户一种用户访问常用窗口的方式,并且不需要恢复或者激活窗口。 + +在 MSDN,它被如是说: +> This toolbar is simply the familiar standard toolbar common control. It has a maximum of seven buttons. Each button's ID, image, tooltip, and state are defined in a structure, which is then passed to the taskbar. The application can show, enable, disable, or hide buttons from the thumbnail toolbar as required by its current state. + +> For example, Windows Media Player might offer standard media transport controls such as play, pause, mute, and stop. + +### Windows Media Player 的缩略图工具栏 +![Thumbnail toolbar of Windows Media Player][9] +你可以使用 [BrowserWindow.setThumbarButtons][10] 来设置你的应用的缩略图工具栏。 +```` +var BrowserWindow = require('browser-window'); +var path = require('path'); +var win = new BrowserWindow({ + width: 800, + height: 600 +}); +win.setThumbarButtons([ + { + tooltip: "button1", + icon: path.join(__dirname, 'button1.png'), + click: function() { console.log("button2 clicked"); } + }, + { + tooltip: "button2", + icon: path.join(__dirname, 'button2.png'), + flags:['enabled', 'dismissonclick'], + click: function() { console.log("button2 clicked."); } + } +]); +```` +调用 `BrowserWindow.setThumbarButtons` 并传入空数组即可清空缩略图工具栏: +```` +win.setThumbarButtons([]); +```` + +## Unity launcher 快捷方式(Linux) +在 Unity,你可以通过改变 `.desktop` 文件来增加自定义运行器的快捷方式,详情看 [Adding shortcuts to a launcher][11]。 +### Audacious 运行器的快捷方式: +![Launcher shortcuts of Audacious][12] + +## 任务栏的进度条(Windows & Unity) +在 Windows,进度条可以出现在一个任务栏按钮之上。这可以提供进度信息给用户而不需要用户切换应用窗口。 + +Unity DE 也具有同样的特性,在运行器上显示进度条。 +### 在任务栏上的进度条: +![Progress bar in taskbar button][13] + +### 在 Unity 运行器上的进度条 +![Progress bar in Unity launcher][14] + +给一个窗口设置进度条,你可以调用 [BrowserWindow.setProgressBar][15] API: +```` +var window = new BrowserWindow({...}); +window.setProgressBar(0.5); +```` +在 OS X,一个窗口可以设置它展示的文件,文件的图标可以出现在标题栏,当用户 Command-Click 或者 Control-Click 标题栏,文件路径弹窗将会出现。 +### 展示文件弹窗菜单: +![Represented file popup menu][16] + +你可以调用 [BrowserWindow.setRepresentedFilename][17] 和 [BrowserWindow.setDocumentEdited][18] APIs: +```` +var window = new BrowserWindow({...}); +window.setRepresentedFilename('/etc/passwd'); +window.setDocumentEdited(true); +```` + + [1]:https://camo.githubusercontent.com/3310597e01f138b1d687e07aa618c50908a88dec/687474703a2f2f692e6d73646e2e6d6963726f736f66742e636f6d2f64796e696d672f49433432303533382e706e67 + [2]: https://cloud.githubusercontent.com/assets/639601/5069610/2aa80758-6e97-11e4-8cfb-c1a414a10774.png + [3]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/app.md + [4]: https://github.com/atom/electron/blob/master/docs/tutorial/clearrecentdocuments + [5]: https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121%28v=vs.85%29.aspx + [6]: https://cloud.githubusercontent.com/assets/639601/5069962/6032658a-6e9c-11e4-9953-aa84006bdfff.png + [7]: https://camo.githubusercontent.com/30154e0cc36acfc968ac9ae076a8f0d6600dd736/687474703a2f2f692e6d73646e2e6d6963726f736f66742e636f6d2f64796e696d672f49433432303533392e706e67 + [8]: https://github.com/atom/electron/blob/master/docs/api/app.md#appsetusertaskstasks + [9]: https://camo.githubusercontent.com/098cb0f52f27084a80ec6429e51a195df3d8c333/68747470733a2f2f692d6d73646e2e7365632e732d6d7366742e636f6d2f64796e696d672f49433432303534302e706e67 + [10]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md + [11]: https://help.ubuntu.com/community/UnityLaunchersAndDesktopFiles#Adding_shortcuts_to_a_launcher + [12]: https://camo.githubusercontent.com/b6f54e2bc3206ebf8e08dd029529af9ec84d58ae/68747470733a2f2f68656c702e7562756e74752e636f6d2f636f6d6d756e6974792f556e6974794c61756e6368657273416e644465736b746f7046696c65733f616374696f6e3d41747461636846696c6526646f3d676574267461726765743d73686f7274637574732e706e67 + [13]: https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png + [14]: https://cloud.githubusercontent.com/assets/639601/5081747/4a0a589e-6f0f-11e4-803f-91594716a546.png + [15]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md + [16]: https://cloud.githubusercontent.com/assets/639601/5082061/670a949a-6f14-11e4-987a-9aaa04b23c1d.png + [17]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md + [18]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/browser-window.md \ No newline at end of file diff --git a/docs-translations/zh-CN/tutorial/online-offline-events.md b/docs-translations/zh-CN/tutorial/online-offline-events.md new file mode 100644 index 000000000000..de292490f0be --- /dev/null +++ b/docs-translations/zh-CN/tutorial/online-offline-events.md @@ -0,0 +1,72 @@ +# 在线/离线事件探测 +使用标准 HTML5 APIs 可以实现在线和离线事件的探测,就像以下例子: + +*main.js* +```` +var app = require('app'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); +```` + +*online-status.html* +```` + + + + + + +```` + +也会有人想要在主进程也有回应这些事件的实例。然后主进程没有 `navigator` 对象因此不能直接探测在线还是离线。使用 Electron 的进程间通讯工具,事件就可以在主进程被使,就像下面的例子: + +*main.js* +```` +var app = require('app'); +var ipc = require('ipc'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); + +ipc.on('online-status-changed', function(event, status) { + console.log(status); +}); +```` + +*online-status.html* +```` + + + + + + +```` \ 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 new file mode 100644 index 000000000000..165c30142e72 --- /dev/null +++ b/docs-translations/zh-CN/tutorial/quick-start.md @@ -0,0 +1,136 @@ +# 快速入门 + +## 简介 +Electron 可以让你使用纯 JavaScript 调用丰富的原生 APIs 来创造桌面应用。你可以把它看作是专注于桌面应用而不是 web 服务器的,io.js 的一个变体。 + +这不意味着 Electron 是绑定了 GUI 库的 JavaScript。相反,Electron 使用 web 页面作为它的 GUI,所以你能把它看作成一个被 JavaScript 控制的,精简版的 Chromium 浏览器。 + +## 主进程 +在 Electron 里,运行 `package.json` 里 `main` 脚本的进程被称为**主进程**。在主进程运行的脚本可以以创建 web 页面的形式展示 GUI。 + +## 渲染进程 +由于 Electron 使用 Chromium 来展示页面,所以 Chromium 的多进程结构也被充分利用。每个 Electron 的页面都在运行着自己的进程,这样的进程我们称之为**渲染进程**。 + +在一般浏览器中,网页通常会在沙盒环境下运行,并且不允许访问原生资源。然而,Electron 用户拥有在网页中调用 io.js 的 APIs 的能力,可以与底层操作系统直接交互。 + +## 主进程与渲染进程的区别 +主进程使用 BroswerWindow 实例创建网页。每个 BroswerWindow 实例都在自己的渲染进程里运行着一个网页。当一个 BroswerWindow 实例被销毁后,相应的渲染进程也会被终止。 + +主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的,并且只关心他们自己的网页。 + +由于在网页里管理原生 GUI 资源是非常危险而且容易造成资源泄露,所以在网页面调用 GUI 相关的 APIs 是不被允许的。如果你想在网页里使用 GUI 操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI 操作。 + +在 Electron,我们提供用于在主进程与渲染进程之间通讯的 [ipc][1] 模块。并且也有一个远程进程调用风格的通讯模块 [remote][2]。 + +# 打造你第一个 Electron 应用 +大体上,一个 Electron 应用的目录结构如下: +```` +your-app/ +├── package.json +├── main.js +└── index.html +```` +`package.json `的格式和 Node 的完全一致,并且那个被 `main` 字段声明的脚本文件是你的应用的启动脚本,它运行在主进程上。你应用里的 `package.json` 看起来应该像: +```` +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +```` +**注意**:如果 `main` 字段没有在 `package.json` 声明,Electron会优先加载 `index.js`。 + +`main.js` 应该用于创建窗口和处理系统时间,一个典型的例子如下: +```` +var app = require('app'); // 控制应用生命周期的模块。 +var BrowserWindow = require('browser-window'); // 创建原生浏览器窗口的模块 + +// 给我们的服务器发送异常报告。 +require('crash-reporter').start(); + +// 保持一个对于 window 对象的全局引用,不然,当 JavaScript 被 GC, +// window 会被自动地关闭 +var mainWindow = null; + +// 当所有窗口被关闭了,退出。 +app.on('window-all-closed', function() { + // 在 OS X 上,通常用户在明确地按下 Cmd + Q 之前 + // 应用会保持活动状态 + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// 当 Electron 完成了初始化并且准备创建浏览器窗口的时候 +// 这个方法就被调用 +app.on('ready', function() { + // 创建浏览器窗口。 + mainWindow = new BrowserWindow({width: 800, height: 600}); + + // 加载应用的 index.html + mainWindow.loadUrl('file://' + __dirname + '/index.html'); + + // 打开开发工具 + mainWindow.openDevTools(); + + // 当 window 被关闭,这个事件会被发出 + mainWindow.on('closed', function() { + // 取消引用 window 对象,如果你的应用支持多窗口的话, + // 通常会把多个 window 对象存放在一个数组里面, + // 但这次不是。 + mainWindow = null; + }); +}); +```` +最后,你想展示的 `index.html` : +```` + + + + Hello World! + + +

Hello World!

+ We are using io.js + and Electron . + + +```` + +# 运行你的应用 +一旦你创建了最初的 `main.js`, `index.html` 和 `package.json` 这几个文件,你可能会想尝试在本地运行并测试,看看是不是和期望的那样正常运行。 + +## electron-prebuild +如果你已经用 `npm` 全局安装了 `electron-prebuilt`,你只需要按照如下方式直接运行你的应用: +```` +electron . +```` +如果你是局部安装,那运行: +```` +./node_modules/.bin/electron . +```` + +## 手工下载 Electron 二进制文件 +如果你手工下载了 Electron 的二进制文件,你也可以直接使用其中的二进制文件直接运行你的应用。 +### Windows +```` +$ .\electron\electron.exe your-app\ +```` +### Linux +```` +$ ./electron/electron your-app/ +```` +### OS X +```` +$ ./Electron.app/Contents/MacOS/Electron your-app/ +```` +`Electron.app` 里面是 Electron 发布包,你可以在[这里][3]下载到。 + +# 以发行版本运行 +在你完成了你的应用后,你可以按照[应用部署][4]指导发布一个版本,并且以已经打包好的形式运行应用。 + + + [1]: https://github.com/atom/electron/blob/master/docs-translations/zh-CN/api/ipc-renderer.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-TW/README.md b/docs-translations/zh-TW/README.md new file mode 100644 index 000000000000..cc75ba51c144 --- /dev/null +++ b/docs-translations/zh-TW/README.md @@ -0,0 +1,70 @@ +## 導引 + +* [應用程式發布](tutorial/application-distribution.md) +* [應用程式打包](tutorial/application-packaging.md) +* [使用原生 node 模組](tutorial/using-native-node-modules.md) +* [主行程 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) + +## 教學 + +* [快速入門](tutorial/quick-start.md) +* [桌面環境整合](tutorial/desktop-environment-integration.md) +* [在線/離線事件偵測](tutorial/online-offline-events.md) + +## API 參考 + +* [提要](api/synopsis.md) +* [行程物件](api/process.md) +* [支援的 Chrome 命令行開關](api/chrome-command-line-switches.md) + +客製的 DOM 元素: + +* [`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) +* [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) +* [protocol](api/protocol.md) +* [session](api/session.md) +* [webContents](api/web-contents.md) +* [tray](api/tray.md) + +渲染行程可用的模組 (網頁): + +* [ipc (renderer)](api/ipc-renderer.md) +* [remote](api/remote.md) +* [web-frame](api/web-frame.md) + +兩種行程都可用的模組: + +* [clipboard](api/clipboard.md) +* [crash-reporter](api/crash-reporter.md) +* [native-image](api/native-image.md) +* [screen](api/screen.md) +* [shell](api/shell.md) + +## 開發 + +* [Coding style](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) +* [在 debugger 中使用 symbol server](development/setting-up-symbol-server.md) diff --git a/docs-translations/zh-TW/api/file-object.md b/docs-translations/zh-TW/api/file-object.md new file mode 100644 index 000000000000..4f523c489516 --- /dev/null +++ b/docs-translations/zh-TW/api/file-object.md @@ -0,0 +1,28 @@ +# `File` object + +DOM's File 介面提供一個將本地文件抽象化,並可以讓使用者對本地文件直接使用 HTML5 檔案 API +Electron 可以添加一個 `path` 屬性至 `File` 接口進而顯示檔案在檔案系統內的真實路徑。 + +範例,獲得一個檔案之真實路徑,將檔案拖拉至應用程式 (dragged-onto-the-app): + +```html +
+ Drag your file here +
+ + +``` diff --git a/docs-translations/zh-TW/api/ipc-main-process.md b/docs-translations/zh-TW/api/ipc-main-process.md new file mode 100644 index 000000000000..00e73efb4fc4 --- /dev/null +++ b/docs-translations/zh-TW/api/ipc-main-process.md @@ -0,0 +1,69 @@ +# ipc (主行程) + +當在主行程裡使用 `ipc` 模組,這個模組負責處理來自渲染行程(網頁)的非同步與同步訊息。 +來自渲染器的訊息將會被注入到這個模組裡。 + +## 傳送訊息 + +同樣的也可以透過主行程來傳送訊息到渲染行程,更多資訊請參考 [WebContents.send](browser-window.md#webcontentssendchannel-args) + +- 當傳送一個訊息, 事件名稱為 `channel` +- 回覆同步訊息,你需要設定 `event.returnValue` +- 送出一個非同步訊息回給發送端,你可以使用 `event.sender.send(...)` + +這裏是一個傳送和處理訊息的範例,在渲染行程與主行程之間: + +```javascript +// 在主行程裡 +var ipc = require('ipc'); +ipc.on('asynchronous-message', function(event, arg) { + console.log(arg); // 輸出 "ping" + event.sender.send('asynchronous-reply', 'pong'); +}); + +ipc.on('synchronous-message', function(event, arg) { + console.log(arg); // 輸出 "ping" + event.returnValue = 'pong'; +}); +``` + +```javascript +// 在渲染行程裡 (網頁). +var ipc = require('ipc'); +console.log(ipc.sendSync('synchronous-message', 'ping')); // 輸出 "pong" + +ipc.on('asynchronous-reply', function(arg) { + console.log(arg); // 輸出 "pong" +}); +ipc.send('asynchronous-message', 'ping'); +``` + +## 聆聽訊息 + +`ipc` 模組擁有下列幾個方法去聆聽事件: + +### `ipc.on(channel, callback)` + +* `channel` String - 事件名稱 +* `callback` Function + +當一個事件發生 `callback` 會帶著 `event` 物件和一個訊息 `arg` + +## IPC 事件 + +`event` 物件被傳入 `callback` 具有以下幾個方法: + +### `Event.returnValue` + +設定這個值可以回傳一個同步訊息 + +### `Event.sender` + +回傳 `WebContents` 可以送出訊息 + +### `Event.sender.send(channel[, arg1][, arg2][, ...])` + +* `channel` String - 事件名稱 +* `arg` (選用) + +此傳送訊息是非同步的訊息,至渲染行程,可以是一個一系列的參數 `arg` 可以是任何型態。 diff --git a/docs-translations/zh-TW/api/power-monitor.md b/docs-translations/zh-TW/api/power-monitor.md new file mode 100644 index 000000000000..e38dee3212da --- /dev/null +++ b/docs-translations/zh-TW/api/power-monitor.md @@ -0,0 +1,36 @@ +# power-monitor + +`power-monitor` 模組用來監看電源狀態的改變。你只能在主行程 (main process) 裡面使用。 +你應該要等到 `ready` 在 `app` 模組裡的事件被觸發 (emit),再使用這個模組。 + +舉例來說: + +```javascript +var app = require('app'); + +app.on('ready', function() { + require('power-monitor').on('suspend', function() { + console.log('The system is going to sleep'); + }); +}); +``` + +## 事件 (Events) + +`power-monitor` 模組會觸發 (emits) 以下幾個事件: + +### 事件: 'suspend' + +當系統進入 睡眠 (suspend) 時觸發。 + +### 事件: 'resume' + +當系統 resume 時觸發。 + +### 事件: 'on-ac' + +當系統改變使用交流電源 (AC) 時觸發。 + +### 事件: 'on-battery' + +當系統改變使用電池店員時觸發。 diff --git a/docs-translations/zh-TW/api/power-save-blocker.md b/docs-translations/zh-TW/api/power-save-blocker.md new file mode 100644 index 000000000000..a652751c0bb8 --- /dev/null +++ b/docs-translations/zh-TW/api/power-save-blocker.md @@ -0,0 +1,47 @@ +# powerSaveBlocker + +`power-save-blocker` 模組是用來防止系統進入省電模式 low-power (sleep) mode +因此讓應用程式可以保持系統和螢幕的活躍 (active)。 + +舉例來說: + +```javascript +var powerSaveBlocker = require('power-save-blocker'); + +var id = powerSaveBlocker.start('prevent-display-sleep'); +console.log(powerSaveBlocker.isStarted(id)); + +powerSaveBlocker.stop(id); +``` + +## 方法 (Methods) + +`power-save-blocker` 模組有以下幾個方法: + +### `powerSaveBlocker.start(type)` + +* `type` String - Power save blocker type. + * `prevent-app-suspension` - 防止一個應用程式進入睡眠 (suspended)。 將保持系統活躍, + 但允許螢幕被關閉。 使用案例:下載一個檔案或是播放音樂。 + * `prevent-display-sleep`- 防止螢幕進入睡眠。將保持系統和螢幕的活躍。 + 使用案例:播放影片 + +當防止系統進入省電模式 low-power (sleep) mode 。 會回傳一個識別的整數來代表 power save blocker + +**注意:** `prevent-display-sleep` 比 `prevent-app-suspension` 擁有較高的優先權。 +只有高的優先全力才會有效,換句話說 `prevent-display-sleep` 總是會優先於 `prevent-app-suspension` + +例如,一個 API 呼叫 A 請求去做 `prevent-app-suspension`,而另外一個 B 請求去做 `prevent-display-sleep` + `prevent-display-sleep` 將會被使用,直到 B 停止他的請求,`prevent-app-suspension` 才會被使用。 + +### `powerSaveBlocker.stop(id)` + +* `id` Integer - power save blocker 會回傳 id 透過 `powerSaveBlocker.start`. + +將指定的 id 停止 power save blocker + +### `powerSaveBlocker.isStarted(id)` + +* `id` Integer - power save blocker 會回傳 id 透過 `powerSaveBlocker.start`. + +不管對應的 `powerSaveBlocker` 是否已經啟動,將會回傳一個布林值 (boolean) diff --git a/docs-translations/zh-TW/api/process.md b/docs-translations/zh-TW/api/process.md new file mode 100644 index 000000000000..a4f45352b9c4 --- /dev/null +++ b/docs-translations/zh-TW/api/process.md @@ -0,0 +1,23 @@ +# process + +在 Electron 裡的 `process` 物件具有以下幾個與 upstream node 的不同點: + +* `process.type` String - Process 的型態,可以是 `browser` (i.e. 主行程) 或 `renderer`. +* `process.versions['electron']` String - Electron 的版本 +* `process.versions['chrome']` String - Chromium 的版本 +* `process.resourcesPath` String - JavaScript 源碼的路徑 + +# 方法 (Methods) + +`process` 物件具有以下的方法: + +### `process.hang` + +會導致目前行程的主執行緒停住 + +## process.setFdLimit(maxDescriptors) _OS X_ _Linux_ + +* `maxDescriptors` Integer + +設置文件描述符 (file descriptor) soft limit `maxDescriptors` 或 OS hard +limit ,以較低者為準當目前的行程。 diff --git a/docs-translations/zh-TW/api/shell.md b/docs-translations/zh-TW/api/shell.md new file mode 100644 index 000000000000..3f3c9ae951c2 --- /dev/null +++ b/docs-translations/zh-TW/api/shell.md @@ -0,0 +1,46 @@ +# shell + +`shell` 模組提供一些整合桌面應用的功能。 + + +一個範例示範如何利用使用者的預設瀏覽器開啟 URL: + +```javascript +var shell = require('shell'); + +shell.openExternal('https://github.com'); +``` + +## Methods + +`shell` 模組有以下幾個方法: + +### `shell.showItemInFolder(fullPath)` + +* `fullPath` String + +顯示在檔案管理中指定的檔案,如果可以的話,選擇該檔案。 + +### `shell.openItem(fullPath)` + +* `fullPath` String + +打開指定的檔案,在桌面的預設方式。 +Open the given file in the desktop's default manner. + +### `shell.openExternal(url)` + +* `url` String + +打開一個指定的外部協定 URL 在桌面的預設方式。(舉例來說 mailto: URLs 會打開使用者的預設信箱) + + +### `shell.moveItemToTrash(fullPath)` + +* `fullPath` String + +移動指定檔案至垃圾桶,並會對這個操作回傳一個 boolean 狀態 + +### `shell.beep()` + +播放 beep 聲音。 diff --git a/docs-translations/zh-TW/api/synopsis.md b/docs-translations/zh-TW/api/synopsis.md new file mode 100644 index 000000000000..484f5e353847 --- /dev/null +++ b/docs-translations/zh-TW/api/synopsis.md @@ -0,0 +1,41 @@ +# Synopsis + +所有的 [Node.js's 內建模組](http://nodejs.org/api/) 都可以在 Electron 使用,而且 +第三方的 node 模組同樣的全部支援(包含[原生模組](../tutorial/using-native-node-modules.md)) + +Electron 也提供一些額外的內建模組用來開發原生桌面應用程式,一些模組只可以使用在主行程上 +(main process) 一些只可以使用在渲染行程 (renderer process) 上 (網頁) ,另外還有一些 +模組在兩邊的行程都可以使用。 + +基本的規則是: 如果一個模組是 [GUI](https://zh.wikipedia.org/wiki/%E5%9B%BE%E5%BD%A2%E7%94%A8%E6%88%B7%E7%95%8C%E9%9D%A2) +或者是 low-level 與系統相關的,那麼它就應該只能在主行程上使用 (main process) 你必須要對熟悉 [main process vs. renderer process](../tutorial/quick-start.md#the-main-process) 的觀念,才能去使用這些模組。 + +主行程 (main process) 腳本是一個像一般 Node.js 的腳本: + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +var window = null; + +app.on('ready', function() { + window = new BrowserWindow({width: 800, height: 600}); + window.loadUrl('https://github.com'); +}); +``` + +渲染行程 (renderer process) 跟一般正常的網頁沒有差別,而且還能有使用 node 模組的能力: + +```html + + + + + + +``` + +執行你的應用程式,請閱讀[Run your app](../tutorial/quick-start.md#run-your-app). diff --git a/docs-translations/zh-TW/tutorial/online-offline-events.md b/docs-translations/zh-TW/tutorial/online-offline-events.md new file mode 100644 index 000000000000..a4366f88a03a --- /dev/null +++ b/docs-translations/zh-TW/tutorial/online-offline-events.md @@ -0,0 +1,80 @@ +# 在線/離線事件偵測 + +我們可以在渲染引擎 (renderer) 的行程裡用標準的 HTML5 API 來實作在線與離線事件的偵測。 +請參考以下範例: + +_main.js_ + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` + +您也許有時候也會有想要在主行程裡回應這些事件的情況。然而,在主行程裡並沒有 `navigator` 這個物件,因此不能直接地偵測這些事件。 +但我們可以使用 Electron 所提供的跨行程 (inter-process) 溝通的工具,事件就可以被傳送到主程序內並做您所需的處理。 +請參考以下範例: + +_main.js_ + +```javascript +var app = require('app'); +var ipc = require('ipc'); +var BrowserWindow = require('browser-window'); +var onlineStatusWindow; + +app.on('ready', function() { + onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false }); + onlineStatusWindow.loadUrl('file://' + __dirname + '/online-status.html'); +}); + +ipc.on('online-status-changed', function(event, status) { + console.log(status); +}); +``` + +_online-status.html_ + +```html + + + + + + +``` diff --git a/docs-translations/zh-TW/tutorial/quick-start.md b/docs-translations/zh-TW/tutorial/quick-start.md new file mode 100644 index 000000000000..aee2baa713bf --- /dev/null +++ b/docs-translations/zh-TW/tutorial/quick-start.md @@ -0,0 +1,165 @@ +# 快速入門 + +## 簡介 + +Electron 可以讓你使用純 JavaScript 提供豐富的原生的 APIs 來創造桌面應用程式。 +你可以把它視為一個 io.js 的變體,專注於桌面應用程式而不是 web 伺服器。 + +這不表示 Electron 是一個用 JavaScript 去綁定 GUI 函式庫。取而代之的,Electron 是使用網頁介面來作為它的 GUI , +所以你可以把它看作是一個被 JavaScript 所控制且精簡化的 Chromium 瀏覽器。 + +## 主行程 + +在 Electron 裡,有個行程會去運行 `package.json` 裡的 `main` 腳本被稱為 __主行程__ 。 +這個腳本運行在主行程裏可以顯示一個 GUI 就由創建一個 web 頁面。 + +## 渲染行程 + +因為 Electron 使用 Chromium 來顯示網頁,所以 Chromium 的多行程架構也被充分利用。 +每一個網頁在 Electron 裏執行各自的行程,被稱為 __渲染行程__。 + +在一般瀏覽器中,網頁通常會在沙盒環境下運行,並且不允許存取原生資源。然而, +Electron 的用戶擁有在網頁中呼叫 io.js APIs 的能力,允許低級別操作與作業系統的交互作用。 + +## 主行程與渲染行程的區別 + +主行程創造網頁透過創造 `BroswerWindow` 實例。每一個 `BroswerWindow` 實例都在自己的渲染行程裡運行著一個網頁。 +當一個 `BroswerWindow` 實例被銷毀,對應的渲染行程也會被終止。主行程管理所有網頁和與之對應的渲染行程。 +每一個渲染行程都是相互獨立的,並且只關心他們自己的網頁。 + +在網頁中,是不允許呼叫原生 GUI 相關 APIs 因為管理原生 GUI 資源在網頁上是非常危險而且容易造成資源洩露。 +如果你想要在網頁中呼叫 GUI 相關的 APIs 的操作,網頁的渲染行程必須與主行程進行通訊,請求主行程進行相關的操作。 + +在 Electron ,我們提供用於在主行程與渲染行程之間通訊的 [ipc][1] 模組。並且也有一個遠端模使用 RPC 通訊方式 [remote][2]。 + +# 打造你第一個 Electron 應用 + +一般來說,一個 Electron 應用程式的目錄結構像下面這樣: + +```text +your-app/ +├── package.json +├── main.js +└── index.html +``` + +`package.json ` 的格式與 Node 的模組完全一樣,並且有個腳本被指定為 `main` 是用來啟動你的應用程式,它運行在主行程上。 +你應用裡的 一個範例在你的 `package.json` 看起來可能像這樣: + +```json +{ + "name" : "your-app", + "version" : "0.1.0", + "main" : "main.js" +} +``` + +__注意__:如果 `main` 沒有在 `package.json` 裏, Electron會嘗試載入 `index.js`。 + +`main.js` 用於創建視窗和處理系統事件,一個典型的例子如下: + +``` javascript +var app = require('app'); // 控制應用程式生命週期的模組。 +var BrowserWindow = require('browser-window'); // 創造原生瀏覽器窗口的模組 + +// 對我們的伺服器傳送異常報告。 +require('crash-reporter').start(); + +// 保持一個對於 window 物件的全域的引用,不然,當 JavaScript 被GC, +// window 會被自動地關閉 +var mainWindow = null; + +// 當所有窗口被關閉了,退出。 +app.on('window-all-closed', function() { +  // 在OS X 上,通常使用者在明確地按下 Cmd + Q 之前 +  // 應用會保持活動狀態 +  if (process.platform != 'darwin') { +    app.quit(); +  } +}); + +// 當Electron 完成了初始化並且準備創建瀏覽器視窗的時候 +// 這個方法就被調用 +app.on('ready', function() { +  // 創建瀏覽器視窗 +  mainWindow = new BrowserWindow({width: 800, height: 600}); + +  // 載入應用程式的 index.html +  mainWindow.loadUrl('file://' + __dirname + '/index.html'); + +  // 打開開發者工具 +  mainWindow.openDevTools(); + +  // 當window 被關閉,這個事件會被觸發 +  mainWindow.on('closed', function() { +    // 取消引用 window 物件,如果你的應用程式支援多視窗的話, +    // 通常會把多個 window 物件存放在一個數組裡面, +    // 但這次不是。 +    mainWindow = null; +  }); +}); +``` + +最後,你想顯示的 `index.html` : + +```html + + +   +    Hello World! +   +   +    

Hello World!

+    We are using io.js +    and Electron . +   + +``` + +# 運行你的應用程式 + +一旦你創造了最初的 `main.js` , `index.html` 和 `package.json` 這幾個文件,你可能會想嘗試在本地運行並測試,看看是不是和期望的那樣正常運行。 + +## electron-prebuild +如果你已經使用 `npm` 安裝了全域的 `electron-prebuilt`,那麼你只需要按照如下方式直接運行你的應用程式: + +```bash +electron . +``` + +如果你是局部性地安裝,那運行: + +```bash +./node_modules/.bin/electron . +``` + +## 手動下載 Electron Binary + +如果你手動下載了 Electron ,你可以直接使的 Binary 直接運行你的應用程式。 + +### Windows + +``` bash +$ .\electron\electron.exe your-app\ +``` + +### Linux + +``` bash +$ ./electron/electron your-app/ +``` + +### OS X + +``` bash +$ ./Electron.app/Contents/MacOS/Electron your-app/ +``` + +`Electron.app` 裡面是 Electron 釋出包,你可以在[這裡](https://github.com/atom/electron/releases)下載到。 + +# 作為版本發行 +在你完成了你的應用程式後,你可以依照 [應用部署](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) 指南發布一個版本,並且運行已經打包好的應用程式。 + +[1]: https://github.com/atom/electron/blob/master/docs/api/ipc-renderer.md + +[2]: https://github.com/atom/electron/blob/master/docs/api/remote.md diff --git a/docs/README.md b/docs/README.md index 34688848b354..b2f95fe4b579 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,31 +1,32 @@ ## Guides -* [Application distribution](tutorial/application-distribution.md) -* [Application packaging](tutorial/application-packaging.md) -* [Using native node modules](tutorial/using-native-node-modules.md) -* [Debugging main process](tutorial/debugging-main-process.md) +* [Application Distribution](tutorial/application-distribution.md) +* [Application Packaging](tutorial/application-packaging.md) +* [Using Native Node Modules](tutorial/using-native-node-modules.md) +* [Debugging Main Process](tutorial/debugging-main-process.md) * [Using Selenium and WebDriver](tutorial/using-selenium-and-webdriver.md) -* [DevTools extension](tutorial/devtools-extension.md) +* [DevTools Extension](tutorial/devtools-extension.md) +* [Using Pepper Flash Plugin](tutorial/using-pepper-flash-plugin.md) ## Tutorials -* [Quick start](tutorial/quick-start.md) -* [Desktop environment integration](tutorial/desktop-environment-integration.md) -* [Online/offline event detection](tutorial/online-offline-events.md) +* [Quick Start](tutorial/quick-start.md) +* [Desktop Environment Integration](tutorial/desktop-environment-integration.md) +* [Online/Offline Event Detection](tutorial/online-offline-events.md) -## API references +## API References * [Synopsis](api/synopsis.md) -* [Process object](api/process.md) -* [Supported Chrome command line switches](api/chrome-command-line-switches.md) +* [Process Object](api/process.md) +* [Supported Chrome Command Line Switches](api/chrome-command-line-switches.md) -Custom DOM elements: +### Custom DOM Elements: -* [`File` object](api/file-object.md) -* [`` tag](api/web-view-tag.md) -* [`window.open` function](api/window-open.md) +* [`File` Object](api/file-object.md) +* [`` Tag](api/web-view-tag.md) +* [`window.open` Function](api/window-open.md) -Modules for the main process: +### Modules for the Main Process: * [app](api/app.md) * [auto-updater](api/auto-updater.md) @@ -37,16 +38,19 @@ Modules for the main process: * [menu](api/menu.md) * [menu-item](api/menu-item.md) * [power-monitor](api/power-monitor.md) +* [power-save-blocker](api/power-save-blocker.md) * [protocol](api/protocol.md) +* [session](api/session.md) +* [web-contents](api/web-contents.md) * [tray](api/tray.md) -Modules for the renderer process (web page): +### Modules for the Renderer Process (Web Page): * [ipc (renderer)](api/ipc-renderer.md) * [remote](api/remote.md) * [web-frame](api/web-frame.md) -Modules for both processes: +### Modules for Both Processes: * [clipboard](api/clipboard.md) * [crash-reporter](api/crash-reporter.md) @@ -56,11 +60,11 @@ Modules for both processes: ## Development -* [Coding style](development/coding-style.md) -* [Source code directory structure](development/source-code-directory-structure.md) -* [Technical differences to NW.js (formerly node-webkit)](development/atom-shell-vs-node-webkit.md) -* [Build system overview](development/build-system-overview.md) -* [Build instructions (Mac)](development/build-instructions-mac.md) -* [Build instructions (Windows)](development/build-instructions-windows.md) -* [Build instructions (Linux)](development/build-instructions-linux.md) -* [Setting up symbol server in debugger](development/setting-up-symbol-server.md) +* [Coding Style](development/coding-style.md) +* [Source Code Directory Structure](development/source-code-directory-structure.md) +* [Technical Differences to NW.js (formerly node-webkit)](development/atom-shell-vs-node-webkit.md) +* [Build System Overview](development/build-system-overview.md) +* [Build Instructions (Mac)](development/build-instructions-osx.md) +* [Build Instructions (Windows)](development/build-instructions-windows.md) +* [Build Instructions (Linux)](development/build-instructions-linux.md) +* [Setting Up Symbol Server in debugger](development/setting-up-symbol-server.md) diff --git a/docs/api/accelerator.md b/docs/api/accelerator.md index a9755ed653b0..8858d18e856e 100644 --- a/docs/api/accelerator.md +++ b/docs/api/accelerator.md @@ -1,6 +1,6 @@ # Accelerator -An accelerator is string that represents a keyboard shortcut, it can contain +An accelerator is a string that represents a keyboard shortcut. It can contain multiple modifiers and key codes, combined by the `+` character. Examples: @@ -10,7 +10,7 @@ Examples: ## Platform notice -On Linux and Windows, the `Command` key would not have any effect, you can +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. diff --git a/docs/api/app.md b/docs/api/app.md index 31fa1c616101..e4a8d561e2f5 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -1,8 +1,9 @@ # app -The `app` module is responsible for controlling the application's life time. +The `app` module is responsible for controlling the application's lifecycle. -The example of quitting the whole application when the last window is closed: +The following example shows how to quit the application when the last window is +closed: ```javascript var app = require('app'); @@ -11,30 +12,36 @@ app.on('window-all-closed', function() { }); ``` -## Event: will-finish-launching +## Events -Emitted when application has done basic startup. On Windows and Linux it is the -same with `ready` event, on OS X this event represents the -`applicationWillFinishLaunching` message of `NSApplication`, usually you would -setup listeners to `open-file` and `open-url` events here, and start the crash -reporter and auto updater. +The `app` object emits the following events: -Under most cases you should just do everything in `ready` event. +### Event: 'will-finish-launching' -## Event: ready +Emitted when the application has finished basic startup. On Windows and Linux, +the `will-finish-launching` event is the same as the `ready` event; on OS X, +this event represents the `applicationWillFinishLaunching` notification of +`NSApplication`. You would usually set up listeners for the `open-file` and +`open-url` events here, and start the crash reporter and auto updater. -Emitted when Electron has done everything initialization. +In most cases, you should just do everything in the `ready` event handler. -## Event: window-all-closed +### Event: 'ready' + +Emitted when Electron has finished initialization. + +### Event: 'window-all-closed' Emitted when all windows have been closed. -This event is only emitted when the application is not going to quit. If a -user pressed `Cmd + Q`, or the developer called `app.quit()`, Electron would -first try to close all windows and then emit the `will-quit` event, and in -this case the `window-all-closed` would not be emitted. +This event is only emitted when the application is not going to quit. If the +user pressed `Cmd + Q`, or the developer called `app.quit()`, Electron will +first try to close all the windows and then emit the `will-quit` event, and in +this case the `window-all-closed` event would not be emitted. -## Event: before-quit +### Event: 'before-quit' + +Returns: * `event` Event @@ -42,7 +49,9 @@ Emitted before the application starts closing its windows. Calling `event.preventDefault()` will prevent the default behaviour, which is terminating the application. -## Event: will-quit +### Event: 'will-quit' + +Returns: * `event` Event @@ -50,223 +59,290 @@ Emitted when all windows have been closed and the application will quit. Calling `event.preventDefault()` will prevent the default behaviour, which is terminating the application. -See description of `window-all-closed` for the differences between `will-quit` -and it. +See the description of the `window-all-closed` event for the differences between +the `will-quit` and `window-all-closed` events. -## Event: quit +### Event: 'quit' -Emitted when application is quitting. +Emitted when the application is quitting. -## Event: open-file +### Event: 'open-file' + +Returns: * `event` Event * `path` String -Emitted when user wants to open a file with the application, it usually -happens when the application is already opened and then OS wants to reuse the -application to open file. +Emitted when the user wants to open a file with the application. The `open-file` +event is usually emitted when the application is already open and the OS wants +to reuse the application to open the file. `open-file` is also emitted when a +file is dropped onto the dock and the application is not yet running. Make sure +to listen for the `open-file` event very early in your application startup to +handle this case (even before the `ready` event is emitted). You should call `event.preventDefault()` if you want to handle this event. -## Event: open-url +### Event: 'open-url' + +Returns: * `event` Event * `url` String -Emitted when user wants to open a URL with the application, this URL scheme +Emitted when the user wants to open a URL with the application. The URL scheme must be registered to be opened by your application. You should call `event.preventDefault()` if you want to handle this event. -## Event: activate-with-no-open-windows +### Event: 'activate' _OS X_ -Emitted when the application is activated while there is no opened windows. It -usually happens when user has closed all of application's windows and then -click on the application's dock icon. +Returns: -## app.quit() +* `event` Event +* `hasVisibleWindows` Bool -Try to close all windows. The `before-quit` event will first be emitted. If all +Emitted when the application is activated, which usually happens when clicks on +the applications's dock icon. + +### Event: 'browser-window-blur' + +Returns: + +* `event` Event +* `window` BrowserWindow + +Emitted when a [browserWindow](browser-window.md) gets blurred. + +### Event: 'browser-window-focus' + +Returns: + +* `event` Event +* `window` BrowserWindow + +Emitted when a [browserWindow](browser-window.md) gets focused. + +### Event: 'browser-window-created' + +Returns: + +* `event` Event +* `window` BrowserWindow + +Emitted when a new [browserWindow](browser-window.md) is created. + +### Event: 'select-certificate' + +Emitted when a client certificate is requested. + +Returns: + +* `event` Event +* `webContents` [WebContents](browser-window.md#class-webcontents) +* `url` String +* `certificateList` [Objects] + * `data` PEM encoded data + * `issuerName` Issuer's Common Name +* `callback` Function + +```javascript +app.on('select-certificate', function(event, host, url, list, callback) { + event.preventDefault(); + callback(list[0]); +}) +``` + +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. + +### Event: 'gpu-process-crashed' + +Emitted when the gpu process crashes. + +## Methods + +The `app` object has the following methods: + +**Note:** Some methods are only available on specific operating systems and are labeled as such. + +### `app.quit()` + +Try to close all windows. The `before-quit` event will emitted first. If all windows are successfully closed, the `will-quit` event will be emitted and by -default the application would be terminated. +default the application will terminate. -This method guarantees all `beforeunload` and `unload` handlers are correctly -executed. It is possible that a window cancels the quitting by returning -`false` in `beforeunload` handler. +This method guarantees that all `beforeunload` and `unload` event handlers are +correctly executed. It is possible that a window cancels the quitting by +returning `false` in the `beforeunload` event handler. -## app.getPath(name) +### `app.getAppPath()` + +Returns the current application directory. + +### `app.getPath(name)` * `name` String Retrieves a path to a special directory or file associated with `name`. On -failure an `Error` would throw. +failure an `Error` is thrown. -You can request following paths by the names: +You can request the following paths by the name: -* `home`: User's home directory -* `appData`: Per-user application data directory, by default it is pointed to: +* `home` User's home directory. +* `appData` Per-user application data directory, which by default points to: * `%APPDATA%` on Windows * `$XDG_CONFIG_HOME` or `~/.config` on Linux * `~/Library/Application Support` on OS X -* `userData`: The directory for storing your app's configuration files, by - default it is the `appData` directory appended with your app's name -* `cache`: Per-user application cache directory, by default it is pointed to: - * `%APPDATA%` on Window, which doesn't has a universal place for cache +* `userData` The directory for storing your app's configuration files, which by + default it is the `appData` directory appended with your app's name. +* `cache` Per-user application cache directory, which by default points to: + * `%APPDATA%` on Windows (which doesn't have a universal cache location) * `$XDG_CACHE_HOME` or `~/.cache` on Linux * `~/Library/Caches` on OS X -* `userCache`: The directory for placing your app's caches, by default it is the - `cache` directory appended with your app's name -* `temp`: Temporary directory -* `userDesktop`: The current user's Desktop directory -* `exe`: The current executable file -* `module`: The `libchromiumcontent` library +* `userCache` The directory for placing your app's caches, by default it is the + `cache` directory appended with your app's name. +* `temp` Temporary directory. +* `userDesktop` The current user's Desktop directory. +* `exe` The current executable file. +* `module` The `libchromiumcontent` library. -## app.setPath(name, path) +### `app.setPath(name, path)` * `name` String * `path` String -Overrides the `path` to a special directory or file associated with `name`. if +Overrides the `path` to a special directory or file associated with `name`. If the path specifies a directory that does not exist, the directory will be -created by this method. On failure an `Error` would throw. +created by this method. On failure an `Error` is thrown. -You can only override paths of `name`s defined in `app.getPath`. +You can only override paths of a `name` defined in `app.getPath`. -By default web pages' cookies and caches will be stored under `userData` -directory, if you want to change this location, you have to override the -`userData` path before the `ready` event of `app` module gets emitted. +By default, web pages's cookies and caches will be stored under the `userData` +directory. If you want to change this location, you have to override the +`userData` path before the `ready` event of the `app` module is emitted. -## app.getVersion() +### `app.getVersion()` -Returns the version of loaded application, if no version is found in -application's `package.json`, the version of current bundle or executable would -be returned. +Returns the version of the loaded application. If no version is found in the +application's `package.json` file, the version of the current bundle or +executable is returned. -## app.getName() +### `app.getName()` -Returns current application's name, the name in `package.json` would be -used. +Returns the current application's name, which is the name in the application's +`package.json` file. Usually the `name` field of `package.json` is a short lowercased name, according -to the spec of npm modules. So usually you should also specify a `productName` -field, which is your application's full capitalized name, and it will be +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.resolveProxy(url, callback) +### `app.getLocale()` + +Returns the current application locale. + +### `app.resolveProxy(url, callback)` * `url` URL * `callback` Function -Resolves the proxy information for `url`, the `callback` would be called with -`callback(proxy)` when the request is done. +Resolves the proxy information for `url`. The `callback` will be called with +`callback(proxy)` when the request is performed. -## app.addRecentDocument(path) +### `app.addRecentDocument(path)` * `path` String -Adds `path` to recent documents list. +Adds `path` to the recent documents list. -This list is managed by the system, on Windows you can visit the list from task -bar, and on Mac you can visit it from dock menu. +This list is managed by the OS. On Windows you can visit the list from the task +bar, and on OS X you can visit it from dock menu. -## app.clearRecentDocuments() +### `app.clearRecentDocuments()` Clears the recent documents list. -## app.setUserTasks(tasks) +### `app.setUserTasks(tasks)` _Windows_ * `tasks` Array - Array of `Task` objects -Adds `tasks` to the [Tasks][tasks] category of JumpList on Windows. +Adds `tasks` to the [Tasks][tasks] category of the JumpList on Windows. -The `tasks` is an array of `Task` objects in following format: +`tasks` is an array of `Task` objects in following format: -* `Task` Object - * `program` String - Path of the program to execute, usually you should - specify `process.execPath` which opens current program - * `arguments` String - The arguments of command line when `program` is - executed - * `title` String - The string to be displayed in a JumpList - * `description` String - Description of this task - * `iconPath` String - The absolute path to an icon to be displayed in a - JumpList, it can be arbitrary resource file that contains an icon, usually - you can specify `process.execPath` to show the icon of the program - * `iconIndex` Integer - The icon index in the icon file. If an icon file - consists of two or more icons, set this value to identify the icon. If an - icon file consists of one icon, this value is 0 +`Task` Object +* `program` String - Path of the program to execute, usually you should + specify `process.execPath` which opens the current program. +* `arguments` String - The command line arguments when `program` is + executed. +* `title` String - The string to be displayed in a JumpList. +* `description` String - Description of this task. +* `iconPath` String - The absolute path to an icon to be displayed in a + JumpList, which can be an arbitrary resource file that contains an icon. You + can usually specify `process.execPath` to show the icon of the program. +* `iconIndex` Integer - The icon index in the icon file. If an icon file + consists of two or more icons, set this value to identify the icon. If an + icon file consists of one icon, this value is 0. -**Note:** This API is only available on Windows. +### `app.commandLine.appendSwitch(switch[, value])` -## app.commandLine.appendSwitch(switch, [value]) - -Append a switch [with optional value] to Chromium's command line. +Append a switch (with optional `value`) to Chromium's command line. **Note:** This will not affect `process.argv`, and is mainly used by developers to control some low-level Chromium behaviors. -## app.commandLine.appendArgument(value) +### `app.commandLine.appendArgument(value)` -Append an argument to Chromium's command line. The argument will quoted properly. +Append an argument to Chromium's command line. The argument will be quoted +correctly. **Note:** This will not affect `process.argv`. -## app.dock.bounce([type]) +### `app.dock.bounce([type])` _OS X_ -* `type` String - Can be `critical` or `informational`, the default is +* `type` String (optional) - Can be `critical` or `informational`. The default is `informational` When `critical` is passed, the dock icon will bounce until either the application becomes active or the request is canceled. -When `informational` is passed, the dock icon will bounce for one second. The -request, though, remains active until either the application becomes active or -the request is canceled. +When `informational` is passed, the dock icon will bounce for one second. +However, the request remains active until either the application becomes active +or the request is canceled. -An ID representing the request would be returned. +Returns an ID representing the request. -**Note:** This API is only available on Mac. - -## app.dock.cancelBounce(id) +### `app.dock.cancelBounce(id)` _OS X_ * `id` Integer Cancel the bounce of `id`. -**Note:** This API is only available on Mac. - -## app.dock.setBadge(text) +### `app.dock.setBadge(text)` _OS X_ * `text` String Sets the string to be displayed in the dock’s badging area. -**Note:** This API is only available on Mac. - -## app.dock.getBadge() +### `app.dock.getBadge()` _OS X_ Returns the badge string of the dock. -**Note:** This API is only available on Mac. - -## app.dock.hide() +### `app.dock.hide()` _OS X_ Hides the dock icon. -**Note:** This API is only available on Mac. - -## app.dock.show() +### `app.dock.show()` _OS X_ Shows the dock icon. -**Note:** This API is only available on Mac. - -## app.dock.setMenu(menu) +### `app.dock.setMenu(menu)` _OS X_ * `menu` Menu -Sets the application [dock menu][dock-menu]. - -**Note:** This API is only available on Mac. +Sets the application's [dock menu][dock-menu]. [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 diff --git a/docs/api/auto-updater.md b/docs/api/auto-updater.md index ab992e97e432..9734f592c8fe 100644 --- a/docs/api/auto-updater.md +++ b/docs/api/auto-updater.md @@ -1,11 +1,11 @@ -# auto-updater +# autoUpdater **This module has only been implemented for OS X.** -Check out [atom/grunt-atom-shell-installer](https://github.com/atom/grunt-atom-shell-installer) -for building a Windows installer for your app. +Check out [atom/grunt-electron-installer](https://github.com/atom/grunt-electron-installer) +to build a Windows installer for your app. -The `auto-updater` module is a simple wrap around the +The `auto-updater` module is a simple wrapper around the [Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac) framework. Squirrel.Mac requires that your `.app` folder is signed using the @@ -26,11 +26,11 @@ body so that your server has the context it needs in order to supply the most suitable update. The update JSON Squirrel requests should be dynamically generated based on -criteria in the request, and whether an update is required. Squirrel relies -on server side support for determining whether an update is required, see +criteria in the request and whether an update is required. Squirrel relies +on server-side support to determine whether an update is required. See [Server Support](#server-support). -Squirrel's installer is also designed to be fault tolerant, and ensure that any +Squirrel's installer is designed to be fault tolerant and ensures that any updates installed are valid. ## Update Requests @@ -40,11 +40,11 @@ update checking. `Accept: application/json` is added to the request headers because Squirrel is responsible for parsing the response. For the requirements imposed on the responses and the body format of an update -response see [Server Support](#server-support). +response, see [Server Support](#server-support). Your update request must *at least* include a version identifier so that the server can determine whether an update for this specific version is required. It -may also include other identifying criteria such as operating system version or +may also include other identifying criteria, such as operating system version or username, to allow the server to deliver as fine grained an update as you would like. @@ -53,7 +53,7 @@ server that you are requesting updates from. A common approach is to use query parameters, like this: ```javascript -// On the main process +// In the main process var app = require('app'); var autoUpdater = require('auto-updater'); autoUpdater.setFeedUrl('http://mycompany.com/myapp/latest?version=' + app.getVersion()); @@ -64,7 +64,7 @@ autoUpdater.setFeedUrl('http://mycompany.com/myapp/latest?version=' + app.getVer Your server should determine whether an update is required based on the [Update Request](#update-requests) your client issues. -If an update is required your server should respond with a status code of +If an update is required, your server should respond with a status code of [200 OK](http://tools.ietf.org/html/rfc2616#section-10.2.1) and include the [update JSON](#update-json-format) in the body. Squirrel **will** download and install this update, even if the version of the update is the same as the @@ -85,40 +85,52 @@ to the update request provided: "url": "http://mycompany.com/myapp/releases/myrelease", "name": "My Release Name", "notes": "Theses are some release notes innit", - "pub_date": "2013-09-18T12:29:53+01:00", + "pub_date": "2013-09-18T12:29:53+01:00" } ``` -The only required key is "url", the others are optional. +The only required key is "url"; the others are optional. Squirrel will request "url" with `Accept: application/zip` and only supports installing ZIP updates. If future update formats are supported their MIME type will be added to the `Accept` header so that your server can return the appropriate format. -`pub_date` if present must be formatted according to ISO 8601. +`pub_date` (if present) must be formatted according to ISO 8601. -## Event: error +## Update server implementations + +[Nuts](https://github.com/GitbookIO/nuts) is an open source implementation of the update server described above, it integrates beautifully with GitHub releases. Nuts manages downloads and updates, it’s compatible with `Squirrel.Mac` and `Squirrel.Windows` so you get cross-platform support out of the box. + +## Events + +The `autoUpdater` object emits the following events: + +### Event: 'error' + +Returns: * `event` Event * `message` String -Emitted when there is an error updating. +Emitted when there is an error while updating. -## Event: checking-for-update +### Event: 'checking-for-update' -Emitted when checking for update has started. +Emitted when checking if an update has started. -## Event: update-available +### Event: 'update-available' -Emitted when there is an available update, the update would be downloaded +Emitted when there is an available update. The update is downloaded automatically. -## Event: update-not-available +### Event: 'update-not-available' Emitted when there is no available update. -## Event: update-downloaded +### Event: 'update-downloaded' + +Returns: * `event` Event * `releaseNotes` String @@ -127,17 +139,21 @@ Emitted when there is no available update. * `updateUrl` String * `quitAndUpdate` Function -Emitted when update has been downloaded, calling `quitAndUpdate()` would restart -the application and install the update. +Emitted when an update has been downloaded. Calling `quitAndUpdate()` will +restart the application and install the update. -## autoUpdater.setFeedUrl(url) +## Methods + +The `autoUpdater` object has the following methods: + +### `autoUpdater.setFeedUrl(url)` * `url` String -Set the `url` and initialize the auto updater. The `url` could not be changed +Set the `url` and initialize the auto updater. The `url` cannot be changed once it is set. -## autoUpdater.checkForUpdates() +### `autoUpdater.checkForUpdates()` -Ask the server whether there is an update, you have to call `setFeedUrl` before +Ask the server whether there is an update. You must call `setFeedUrl` before using this API. diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index f8ff8e7b9e0c..f1705c291756 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1,7 +1,7 @@ -# browser-window +# BrowserWindow -The `BrowserWindow` class gives you ability to create a browser window, an -example is: +The `BrowserWindow` class gives you the ability to create a browser window. For +example: ```javascript var BrowserWindow = require('browser-window'); @@ -23,87 +23,116 @@ You can also create a window without chrome by using `BrowserWindow` is an [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). -### new BrowserWindow(options) +It creates a new `BrowserWindow` with native properties as set by the `options`. +Properties `width` and `height` are required. -* `options` Object - * `x` Integer - Window's left offset to screen - * `y` Integer - Window's top offset to screen - * `width` Integer - Window's width - * `height` Integer - Window's height - * `use-content-size` Boolean - The `width` and `height` would be used as web - page's size, which means the actual window's size will include window - frame's size and be slightly larger. - * `center` Boolean - Show window in the center of the screen - * `min-width` Integer - Minimum width - * `min-height` Integer - Minimum height - * `max-width` Integer - Maximum width - * `max-height` Integer - Maximum height - * `resizable` Boolean - Whether window is resizable - * `always-on-top` Boolean - Whether the window should always stay on top of - other windows - * `fullscreen` Boolean - Whether the window should show in fullscreen, when - set to `false` the fullscreen button would also be hidden on OS X - * `skip-taskbar` Boolean - Do not show window in Taskbar - * `zoom-factor` Number - The default zoom factor of the page, zoom factor is - zoom percent / 100, so `3.0` represents `300%` - * `kiosk` Boolean - The kiosk mode - * `title` String - Default window title - * `icon` [NativeImage](native-image.md) - The window icon, when omitted on - Windows the executable's icon would be used as window icon - * `show` Boolean - Whether window should be shown when created - * `frame` Boolean - Specify `false` to create a - [Frameless Window](frameless-window.md) - * `node-integration` Boolean - Whether node integration is enabled, default - is `true` - * `accept-first-mouse` Boolean - Whether the web view accepts a single - mouse-down event that simultaneously activates the window - * `disable-auto-hide-cursor` Boolean - Do not hide cursor when typing - * `auto-hide-menu-bar` Boolean - Auto hide the menu bar unless the `Alt` - key is pressed. - * `enable-larger-than-screen` Boolean - Enable the window to be resized larger - than screen. - * `dark-theme` Boolean - Forces using dark theme for the window, only works on - some GTK+3 desktop environments +### `new BrowserWindow(options)` + +`options` Object, properties: + +* `width` Integer (**required**) - Window's width. +* `height` Integer (**required**) - Window's height. +* `x` Integer - Window's left offset from screen. +* `y` Integer - Window's top offset from screen. +* `use-content-size` Boolean - The `width` and `height` would be used as web + page's size, which means the actual window's size will include window + frame's size and be slightly larger. +* `center` Boolean - Show window in the center of the screen. +* `min-width` Integer - Window's minimum width. +* `min-height` Integer - Window's minimum height. +* `max-width` Integer - Window's maximum width. +* `max-height` Integer - Window's maximum height. +* `resizable` Boolean - Whether window is resizable. +* `always-on-top` Boolean - Whether the window should always stay on top of + other windows. +* `fullscreen` Boolean - Whether the window should show in fullscreen. When + set to `false` the fullscreen button will be hidden or disabled on OS X. +* `skip-taskbar` Boolean - Whether to show the window in taskbar. +* `kiosk` Boolean - The kiosk mode. +* `title` String - Default window title. +* `icon` [NativeImage](native-image.md) - The window icon, when omitted on + Windows the executable's icon would be used as window icon. +* `show` Boolean - Whether window should be shown when created. +* `frame` Boolean - Specify `false` to create a +[Frameless Window](frameless-window.md). +* `accept-first-mouse` Boolean - Whether the web view accepts a single + mouse-down event that simultaneously activates the window. +* `disable-auto-hide-cursor` Boolean - Whether to hide cursor when typing. +* `auto-hide-menu-bar` Boolean - Auto hide the menu bar unless the `Alt` + key is pressed. +* `enable-larger-than-screen` Boolean - Enable the window to be resized larger + than screen. +* `dark-theme` Boolean - Forces using dark theme for the window, only works on + some GTK+3 desktop environments. +* `transparent` Boolean - Makes the window [transparent](frameless-window.md). +* `type` String - Specifies the type of the window, possible types are + `desktop`, `dock`, `toolbar`, `splash`, `notification`. This only works on + Linux. +* `standard-window` Boolean - Uses the OS X's standard window instead of the + textured window. Defaults to `true`. +* `title-bar-style` String, OS X - specifies the style of window title bar. + This option is supported on OS X 10.10 Yosemite and newer. There are three + possible values: + * `default` or not specified results in the standard gray opaque Mac title + bar. + * `hidden` results in a hidden title bar and a full size content window, yet + the title bar still has the standard window controls ("traffic lights") in + the top left. + * `hidden-inset` results in a hidden title bar with an alternative look + where the traffic light buttons are slightly more inset from the window edge. +* `web-preferences` Object - Settings of web page's features, properties: + * `node-integration` Boolean - Whether node integration is enabled. Default + is `true`. * `preload` String - Specifies a script that will be loaded before other - scripts run in the window. This script will always have access to node APIs - no matter whether node integration is turned on for the window, and the path + scripts run in the page. This script will always have access to node APIs + no matter whether node integration is turned on for the page, and the path of `preload` script has to be absolute path. - * `transparent` Boolean - Makes the window [transparent](frameless-window.md) - * `type` String - Specifies the type of the window, possible types are - `desktop`, `dock`, `toolbar`, `splash`, `notification`. This only works on - Linux. - * `standard-window` Boolean - Uses the OS X's standard window instead of the - textured window. Defaults to `true`. - * `web-preferences` Object - Settings of web page's features - * `javascript` Boolean - * `web-security` Boolean - * `images` Boolean - * `java` Boolean - * `text-areas-are-resizable` Boolean - * `webgl` Boolean - * `webaudio` Boolean - * `plugins` Boolean - Whether plugins should be enabled, currently only - `NPAPI` plugins are supported. - * `extra-plugin-dirs` Array - Array of paths that would be searched for - plugins. Note that if you want to add a directory under your app, you - should use `__dirname` or `process.resourcesPath` to join the paths to - make them absolute, using relative paths would make Electron search - under current working directory. - * `experimental-features` Boolean - * `experimental-canvas-features` Boolean - * `subpixel-font-scaling` Boolean - * `overlay-scrollbars` Boolean - * `overlay-fullscreen-video` Boolean - * `shared-worker` Boolean - * `direct-write` Boolean - Whether the DirectWrite font rendering system on - Windows is enabled + * `partition` String - Sets the session used by the page. If `partition` + starts with `persist:`, the page will use a persistent session available to + all pages in the app with the same `partition`. if there is no `persist:` + prefix, the page will use an in-memory session. By assigning the same + `partition`, multiple pages can share the same session. If the `partition` + is unset then default session of the app will be used. + * `zoom-factor` Number - The default zoom factor of the page, `3.0` represents + `300%`. + * `javascript` Boolean + * `web-security` Boolean - When setting `false`, it will disable the + same-origin policy (Usually using testing websites by people), and set + `allow_displaying_insecure_content` and `allow_running_insecure_content` to + `true` if these two options are not set by user. + * `allow-displaying-insecure-content` Boolean - Allow an https page to display + content like images from http URLs. + * `allow-running-insecure-content` Boolean - Allow a https page to run + JavaScript, CSS or plugins from http URLs. + * `images` Boolean + * `java` Boolean + * `text-areas-are-resizable` Boolean + * `webgl` Boolean + * `webaudio` Boolean + * `plugins` Boolean - Whether plugins should be enabled. + * `experimental-features` Boolean + * `experimental-canvas-features` Boolean + * `subpixel-font-scaling` Boolean + * `overlay-scrollbars` Boolean + * `overlay-fullscreen-video` Boolean + * `shared-worker` Boolean + * `direct-write` Boolean - Whether the DirectWrite font rendering system on + Windows is enabled. + * `page-visibility` Boolean - Page would be forced to be always in visible + or hidden state once set, instead of reflecting current window's + visibility. Users can set it to `true` to prevent throttling of DOM + timers. -Creates a new `BrowserWindow` with native properties set by the `options`. -Usually you only need to set the `width` and `height`, other properties will -have decent default values. +## Events + +The `BrowserWindow` object emits the following events: + +**Note:** Some events are only available on specific operating systems and are labeled as such. ### Event: 'page-title-updated' +Returns: + * `event` Event Emitted when the document changed its title, calling `event.preventDefault()` @@ -111,26 +140,28 @@ would prevent the native window's title to change. ### Event: 'close' +Returns: + * `event` Event Emitted when the window is going to be closed. It's emitted before the -`beforeunload` and `unload` event of DOM, calling `event.preventDefault()` -would cancel the close. +`beforeunload` and `unload` event of the DOM. Calling `event.preventDefault()` +will cancel the close. Usually you would want to use the `beforeunload` handler to decide whether the window should be closed, which will also be called when the window is reloaded. In Electron, returning an empty string or `false` would cancel the -close. An example is: +close. For example: ```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 the power completely - // to the developers, return empty string or false would prevent the unloading - // now. You can also use the dialog API to let user confirm it. - return false; + // 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; }; ``` @@ -149,11 +180,11 @@ Emitted when the unresponsive web page becomes responsive again. ### Event: 'blur' -Emitted when window loses focus. +Emitted when the window loses focus. ### Event: 'focus' -Emitted when window gains focus. +Emitted when the window gains focus. ### Event: 'maximize' @@ -161,232 +192,327 @@ Emitted when window is maximized. ### Event: 'unmaximize' -Emitted when window exits from maximized state. +Emitted when the window exits from maximized state. ### Event: 'minimize' -Emitted when window is minimized. +Emitted when the window is minimized. ### Event: 'restore' -Emitted when window is restored from minimized state. +Emitted when the window is restored from minimized state. + +### Event: 'resize' + +Emitted when the window is getting resized. + +### Event: 'move' + +Emitted when the window is getting moved to a new position. + +__Note__: On OS X this event is just an alias of `moved`. + +### Event: 'moved' _OS X_ + +Emitted once when the window is moved to a new position. ### Event: 'enter-full-screen' -Emitted when window enters full screen state. +Emitted when the window enters full screen state. ### Event: 'leave-full-screen' -Emitted when window leaves full screen state. +Emitted when the window leaves full screen state. + +### Event: 'enter-html-full-screen' + +Emitted when the window enters full screen state triggered by html api. + +### Event: 'leave-html-full-screen' + +Emitted when the window leaves full screen state triggered by html api. ### Event: 'devtools-opened' -Emitted when devtools is opened. +Emitted when DevTools is opened. ### Event: 'devtools-closed' -Emitted when devtools is closed. +Emitted when DevTools is closed. ### Event: 'devtools-focused' -Emitted when devtools is focused / opened. +Emitted when DevTools is focused / opened. -### Class Method: BrowserWindow.getAllWindows() +### Event: 'app-command': + +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. + +```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(); + } +}); +``` + +## Methods + +The `BrowserWindow` object has the following methods: + +### `BrowserWindow.getAllWindows()` Returns an array of all opened browser windows. -### Class Method: BrowserWindow.getFocusedWindow() +### `BrowserWindow.getFocusedWindow()` Returns the window that is focused in this application. -### Class Method: BrowserWindow.fromWebContents(webContents) +### `BrowserWindow.fromWebContents(webContents)` -* `webContents` WebContents +* `webContents` [WebContents](web-contents.md) -Find a window according to the `webContents` it owns +Find a window according to the `webContents` it owns. -### Class Method: BrowserWindow.fromId(id) +### `BrowserWindow.fromId(id)` * `id` Integer Find a window according to its ID. -### Class Method: BrowserWindow.addDevToolsExtension(path) +### `BrowserWindow.addDevToolsExtension(path)` * `path` String -Adds devtools extension located at `path`, and returns extension's name. +Adds DevTools extension located at `path`, and returns extension's name. The extension will be remembered so you only need to call this API once, this API is not for programming use. -### Class Method: BrowserWindow.removeDevToolsExtension(name) +### `BrowserWindow.removeDevToolsExtension(name)` * `name` String -Remove the devtools extension whose name is `name`. +Remove the DevTools extension whose name is `name`. -### BrowserWindow.webContents +## Instance Properties + +Objects created with `new BrowserWindow` have the following properties: + +```javascript +var BrowserWindow = require('browser-window'); + +// In this example `win` is our instance +var win = new BrowserWindow({ width: 800, height: 600 }); + +``` + +### `win.webContents` The `WebContents` object this window owns, all web page related events and -operations would be done via it. +operations will be done via it. + +See the [`webContents` documentation](web-contents.md) for its methods and +events. **Note:** Users should never store this object because it may become `null` when the renderer process (web page) has crashed. -### BrowserWindow.devToolsWebContents +### `win.devToolsWebContents` -Get the `WebContents` of devtools of this window. +Get the `WebContents` of DevTools for this window. **Note:** Users should never store this object because it may become `null` -when the devtools has been closed. +when the DevTools has been closed. -### BrowserWindow.id +### `win.id` -Get the unique ID of this window. +The unique ID of this window. -### BrowserWindow.destroy() +## Instance Methods + +Objects created with `new BrowserWindow` have the following instance methods: + +**Note:** Some methods are only available on specific operating systems and are labeled as such. + +```javascript +var BrowserWindow = require('browser-window'); + +// In this example `win` is our instance +var win = new BrowserWindow({ width: 800, height: 600 }); + +``` + +### `win.destroy()` Force closing the window, the `unload` and `beforeunload` event won't be emitted -for the web page, and `close` event would also not be emitted -for this window, but it would guarantee the `closed` event to be emitted. +for the web page, and `close` event will also not be emitted +for this window, but it guarantees the `closed` event will be emitted. -You should only use this method when the renderer process (web page) has crashed. +You should only use this method when the renderer process (web page) has +crashed. -### BrowserWindow.close() +### `win.close()` Try to close the window, this has the same effect with user manually clicking the close button of the window. The web page may cancel the close though, see the [close event](#event-close). -### BrowserWindow.focus() +### `win.focus()` Focus on the window. -### BrowserWindow.isFocused() +### `win.isFocused()` -Returns whether the window is focused. +Returns a boolean, whether the window is focused. -### BrowserWindow.show() +### `win.show()` Shows and gives focus to the window. -### BrowserWindow.showInactive() +### `win.showInactive()` Shows the window but doesn't focus on it. -### BrowserWindow.hide() +### `win.hide()` Hides the window. -### BrowserWindow.isVisible() +### `win.isVisible()` -Returns whether the window is visible to the user. +Returns a boolean, whether the window is visible to the user. -### BrowserWindow.maximize() +### `win.maximize()` Maximizes the window. -### BrowserWindow.unmaximize() +### `win.unmaximize()` Unmaximizes the window. -### BrowserWindow.isMaximized() +### `win.isMaximized()` -Returns whether the window is maximized. +Returns a boolean, whether the window is maximized. -### BrowserWindow.minimize() +### `win.minimize()` Minimizes the window. On some platforms the minimized window will be shown in the Dock. -### BrowserWindow.restore() +### `win.restore()` Restores the window from minimized state to its previous state. -### BrowserWindow.isMinimized() +### `win.isMinimized()` -Returns whether the window is minimized. +Returns a boolean, whether the window is minimized. -### BrowserWindow.setFullScreen(flag) +### `win.setFullScreen(flag)` * `flag` Boolean Sets whether the window should be in fullscreen mode. -### BrowserWindow.isFullScreen() +### `win.isFullScreen()` -Returns whether the window is in fullscreen mode. +Returns a boolean, whether the window is in fullscreen mode. -### BrowserWindow.setBounds(options) +### `win.setAspectRatio(aspectRatio[, extraSize])` _OS X_ -* `options` Object - * `x` Integer - * `y` Integer +* `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: * `width` Integer * `height` Integer +This will have a window maintain an aspect ratio. The extra size allows a +developer to have space, specified in pixels, not included within the aspect +ratio calculations. This API already takes into account the difference between a +window's size and its content size. + +Consider a normal window with an HD video player and associated controls. +Perhaps there are 15 pixels of controls on the left edge, 25 pixels of controls +on the right edge and 50 pixels of controls below the player. In order to +maintain a 16:9 aspect ratio (standard aspect ratio for HD @1920x1080) within +the player itself we would call this function with arguments of 16/9 and +[ 40, 50 ]. The second argument doesn't care where the extra width and height +are within the content view--only that they exist. Just sum any extra width and +height areas you have within the overall content view. + +### `win.setBounds(options)` + +`options` Object, properties: + +* `x` Integer +* `y` Integer +* `width` Integer +* `height` Integer + Resizes and moves the window to `width`, `height`, `x`, `y`. -### BrowserWindow.getBounds() +### `win.getBounds()` Returns an object that contains window's width, height, x and y values. -### BrowserWindow.setSize(width, height) +### `win.setSize(width, height)` * `width` Integer * `height` Integer Resizes the window to `width` and `height`. -### BrowserWindow.getSize() +### `win.getSize()` Returns an array that contains window's width and height. -### BrowserWindow.setContentSize(width, height) +### `win.setContentSize(width, height)` * `width` Integer * `height` Integer Resizes the window's client area (e.g. the web page) to `width` and `height`. -### BrowserWindow.getContentSize() +### `win.getContentSize()` Returns an array that contains window's client area's width and height. -### BrowserWindow.setMinimumSize(width, height) +### `win.setMinimumSize(width, height)` * `width` Integer * `height` Integer Sets the minimum size of window to `width` and `height`. -### BrowserWindow.getMinimumSize() +### `win.getMinimumSize()` Returns an array that contains window's minimum width and height. -### BrowserWindow.setMaximumSize(width, height) +### `win.setMaximumSize(width, height)` * `width` Integer * `height` Integer Sets the maximum size of window to `width` and `height`. -### BrowserWindow.getMaximumSize() +### `win.getMaximumSize()` Returns an array that contains window's maximum width and height. -### BrowserWindow.setResizable(resizable) +### `win.setResizable(resizable)` * `resizable` Boolean Sets whether the window can be manually resized by user. -### BrowserWindow.isResizable() +### `win.isResizable()` Returns whether the window can be manually resized by user. -### BrowserWindow.setAlwaysOnTop(flag) +### `win.setAlwaysOnTop(flag)` * `flag` Boolean @@ -394,164 +520,159 @@ Sets whether the window should show always on top of other windows. After setting this, the window is still a normal window, not a toolbox window which can not be focused on. -### BrowserWindow.isAlwaysOnTop() +### `win.isAlwaysOnTop()` Returns whether the window is always on top of other windows. -### BrowserWindow.center() +### `win.center()` Moves window to the center of the screen. -### BrowserWindow.setPosition(x, y) +### `win.setPosition(x, y)` * `x` Integer * `y` Integer Moves window to `x` and `y`. -### BrowserWindow.getPosition() +### `win.getPosition()` Returns an array that contains window's current position. -### BrowserWindow.setTitle(title) +### `win.setTitle(title)` * `title` String Changes the title of native window to `title`. -### BrowserWindow.getTitle() +### `win.getTitle()` Returns the title of the native window. **Note:** The title of web page can be different from the title of the native window. -### BrowserWindow.flashFrame(flag) +### `win.flashFrame(flag)` * `flag` Boolean Starts or stops flashing the window to attract user's attention. -### BrowserWindow.setSkipTaskbar(skip) +### `win.setSkipTaskbar(skip)` * `skip` Boolean -Makes the window do not show in Taskbar. +Makes the window not show in the taskbar. -### BrowserWindow.setKiosk(flag) +### `win.setKiosk(flag)` * `flag` Boolean Enters or leaves the kiosk mode. -### BrowserWindow.isKiosk() +### `win.isKiosk()` Returns whether the window is in kiosk mode. -### BrowserWindow.setRepresentedFilename(filename) +### `win.setRepresentedFilename(filename)` _OS X_ * `filename` String Sets the pathname of the file the window represents, and the icon of the file will show in window's title bar. -__Note__: This API is available only on OS X. - -### BrowserWindow.getRepresentedFilename() +### `win.getRepresentedFilename()` _OS X_ Returns the pathname of the file the window represents. -__Note__: This API is available only on OS X. - -### BrowserWindow.setDocumentEdited(edited) +### `win.setDocumentEdited(edited)` _OS X_ * `edited` Boolean Specifies whether the window’s document has been edited, and the icon in title bar will become grey when set to `true`. -__Note__: This API is available only on OS X. - -### BrowserWindow.IsDocumentEdited() +### `win.IsDocumentEdited()` _OS X_ Whether the window's document has been edited. -__Note__: This API is available only on OS X. +### `win.openDevTools([options])` -### BrowserWindow.openDevTools([options]) - -* `options` Object - * `detach` Boolean - opens devtools in a new window +* `options` Object (optional). Properties: + * `detach` Boolean - opens DevTools in a new window Opens the developer tools. -### BrowserWindow.closeDevTools() +### `win.closeDevTools()` Closes the developer tools. -### BrowserWindow.toggleDevTools() +### `win.isDevToolsOpened()` -Toggle the developer tools. +Returns whether the developer tools are opened. -### BrowserWindow.inspectElement(x, y) +### `win.toggleDevTools()` + +Toggles the developer tools. + +### `win.isDevToolsFocused()` + +Returns whether the developer tools is focused. + +### `win.inspectElement(x, y)` * `x` Integer * `y` Integer Starts inspecting element at position (`x`, `y`). -### BrowserWindow.focusOnWebView() +### `win.inspectServiceWorker()` -### BrowserWindow.blurWebView() +Opens the developer tools for the service worker context present in the web +contents. -### BrowserWindow.capturePage([rect, ]callback) +### `win.focusOnWebView()` -* `rect` Object - The area of page to be captured +### `win.blurWebView()` + +### `win.capturePage([rect, ]callback)` + +* `rect` Object (optional)- The area of page to be captured, properties: * `x` Integer * `y` Integer * `width` Integer * `height` Integer * `callback` Function -Captures the snapshot of page within `rect`, upon completion `callback` would be -called with `callback(image)`, the `image` is an instance of -[NativeImage](native-image.md) that stores data of the snapshot. Omitting the -`rect` would capture the whole visible page. +Captures a snapshot of the page within `rect`. Upon completion `callback` will +be called with `callback(image)`. The `image` is an instance of +[NativeImage](native-image.md) that stores data of the snapshot. Omitting +`rect` will capture the whole visible page. -**Note:** Be sure to read documents on remote buffer in -[remote](remote.md) if you are going to use this API in renderer -process. +### `win.print([options])` -### BrowserWindow.print([options]) +Same as `webContents.print([options])` -* `options` Object - * `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`. +### `win.printToPDF(options, callback)` -Prints window's web page. When `silent` is set to `false`, Electron will pick -up system's default printer and default settings for printing. +Same as `webContents.printToPDF(options, callback)` -Calling `window.print()` in web page is equivalent to call -`BrowserWindow.print({silent: false, printBackground: false})`. +### `win.loadUrl(url[, options])` -### BrowserWindow.loadUrl(url) +Same as `webContents.loadUrl(url[, options])`. -Same with `webContents.loadUrl(url)`. +### `win.reload()` -### BrowserWindow.reload() +Same as `webContents.reload`. -Same with `webContents.reload`. - -### BrowserWindow.setMenu(menu) +### `win.setMenu(menu)` _OS X_ * `menu` Menu -Sets the `menu` as the window top menu. +Sets the `menu` as the window's menu bar, setting it to `null` will remove the +menu bar. -__Note:__ This API is not available on OS X. - -### BrowserWindow.setProgressBar(progress) +### `win.setProgressBar(progress)` * `progress` Double @@ -564,25 +685,56 @@ On Linux platform, only supports Unity desktop environment, you need to specify the `*.desktop` file name to `desktopName` field in `package.json`. By default, it will assume `app.getName().desktop`. -### BrowserWindow.setOverlayIcon(overlay, description) +### `win.setOverlayIcon(overlay, description)` _Windows 7+_ * `overlay` [NativeImage](native-image.md) - the icon to display on the bottom -right corner of the Taskbar icon. If this parameter is `null`, the overlay is +right corner of the taskbar icon. If this parameter is `null`, the overlay is 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 sort of application status or to passively notify the user. +Sets a 16px overlay onto the current taskbar icon, usually used to convey some +sort of application status or to passively notify the user. -__Note:__ This API is only available on Windows, Win7 or above -### BrowserWindow.showDefinitionForSelection() +### `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 + +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 +whether the thumbnail has been added successfully. + +The number of buttons in thumbnail toolbar should be no greater than 7 due to +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. + +### `win.showDefinitionForSelection()` _OS X_ Shows pop-up dictionary that searches the selected word on the page. -__Note__: This API is available only on OS X. - -### BrowserWindow.setAutoHideMenuBar(hide) +### `win.setAutoHideMenuBar(hide)` * `hide` Boolean @@ -592,22 +744,22 @@ menu bar will only show when users press the single `Alt` key. If the menu bar is already visible, calling `setAutoHideMenuBar(true)` won't hide it immediately. -### BrowserWindow.isMenuBarAutoHide() +### `win.isMenuBarAutoHide()` Returns whether menu bar automatically hides itself. -### BrowserWindow.setMenuBarVisibility(visible) +### `win.setMenuBarVisibility(visible)` * `visible` Boolean Sets whether the menu bar should be visible. If the menu bar is auto-hide, users can still bring up the menu bar by pressing the single `Alt` key. -### BrowserWindow.isMenuBarVisible() +### `win.isMenuBarVisible()` Returns whether the menu bar is visible. -### BrowserWindow.setVisibleOnAllWorkspaces(visible) +### `win.setVisibleOnAllWorkspaces(visible)` * `visible` Boolean @@ -615,307 +767,8 @@ Sets whether the window should be visible on all workspaces. **Note:** This API does nothing on Windows. -### BrowserWindow.isVisibleOnAllWorkspaces() +### `win.isVisibleOnAllWorkspaces()` Returns whether the window is visible on all workspaces. **Note:** This API always returns false on Windows. - -## Class: WebContents - -A `WebContents` is responsible for rendering and controlling a web page. - -`WebContents` is an -[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). - -### Event: 'did-finish-load' - -Emitted when the navigation is done, i.e. the spinner of the tab will stop -spinning, and the `onload` event was dispatched. - -### Event: 'did-fail-load' - -* `event` Event -* `errorCode` Integer -* `errorDescription` String - -This event is like `did-finish-load`, but emitted when the load failed or was -cancelled, e.g. `window.stop()` is invoked. - -### Event: 'did-frame-finish-load' - -* `event` Event -* `isMainFrame` Boolean - -Emitted when a frame has done navigation. - -### Event: 'did-start-loading' - -Corresponds to the points in time when the spinner of the tab starts spinning. - -### Event: 'did-stop-loading' - -Corresponds to the points in time when the spinner of the tab stops spinning. - -### Event: 'did-get-response-details' - -* `event` Event -* `status` Boolean -* `newUrl` String -* `originalUrl` String -* `httpResponseCode` Integer -* `requestMethod` String -* `referrer` String - -Emitted when details regarding a requested resource is available. -`status` indicates the socket connection to download the resource. - -### Event: 'did-get-redirect-request' - -* `event` Event -* `oldUrl` String -* `newUrl` String -* `isMainFrame` Boolean - -Emitted when a redirect was received while requesting a resource. - -### Event: 'dom-ready' - -* `event` Event - -Emitted when document in the given frame is loaded. - -### Event: 'page-favicon-updated' - -* `event` Event -* `favicons` Array - Array of Urls - -Emitted when page receives favicon urls. - -### Event: 'new-window' - -* `event` Event -* `url` String -* `frameName` String -* `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, - `new-window` and `other` - -Emitted when the page requested to open a new window for `url`. It could be -requested by `window.open` or an external link like ``. - -By default a new `BrowserWindow` will be created for the `url`. - -Calling `event.preventDefault()` can prevent creating new windows. - -### Event: 'will-navigate' - -* `event` Event -* `url` String - -Emitted when user or the page wants to start an navigation, it can happen when -`window.location` object is changed or user clicks a link in the page. - -This event will not emit when the navigation is started programmely with APIs -like `WebContents.loadUrl` and `WebContents.back`. - -Calling `event.preventDefault()` can prevent the navigation. - -### Event: 'crashed' - -Emitted when the renderer process is crashed. - -### Event: 'destroyed' - -Emitted when the WebContents is destroyed. - -### WebContents.loadUrl(url) - -* `url` URL - -Loads the `url` in the window, the `url` must contains the protocol prefix, -e.g. the `http://` or `file://`. - -### WebContents.getUrl() - -Returns URL of current web page. - -### WebContents.getTitle() - -Returns the title of web page. - -### WebContents.isLoading() - -Returns whether web page is still loading resources. - -### WebContents.isWaitingForResponse() - -Returns whether web page is waiting for a first-response for the main resource -of the page. - -### WebContents.stop() - -Stops any pending navigation. - -### WebContents.reload() - -Reloads current page. - -### WebContents.reloadIgnoringCache() - -Reloads current page and ignores cache. - -### WebContents.canGoBack() - -Returns whether the web page can go back. - -### WebContents.canGoForward() - -Returns whether the web page can go forward. - -### WebContents.canGoToOffset(offset) - -* `offset` Integer - -Returns whether the web page can go to `offset`. - -### WebContents.goBack() - -Makes the web page go back. - -### WebContents.goForward() - -Makes the web page go forward. - -### WebContents.goToIndex(index) - -* `index` Integer - -Navigates to the specified absolute index. - -### WebContents.goToOffset(offset) - -* `offset` Integer - -Navigates to the specified offset from the "current entry". - -### WebContents.isCrashed() - -Whether the renderer process has crashed. - -### WebContents.setUserAgent(userAgent) - -* `userAgent` String - -Overrides the user agent for this page. - -### WebContents.insertCSS(css) - -* `css` String - -Injects CSS into this page. - -### WebContents.executeJavaScript(code) - -* `code` String - -Evaluates `code` in page. - -### WebContents.undo() - -Executes editing command `undo` in page. - -### WebContents.redo() - -Executes editing command `redo` in page. - -### WebContents.cut() - -Executes editing command `cut` in page. - -### WebContents.copy() - -Executes editing command `copy` in page. - -### WebContents.paste() - -Executes editing command `paste` in page. - -### WebContents.delete() - -Executes editing command `delete` in page. - -### WebContents.selectAll() - -Executes editing command `selectAll` in page. - -### WebContents.unselect() - -Executes editing command `unselect` in page. - -### WebContents.replace(text) - -* `text` String - -Executes editing command `replace` in page. - -### WebContents.replaceMisspelling(text) - -* `text` String - -Executes editing command `replaceMisspelling` in page. - -### WebContents.hasServiceWorker(callback) - -* `callback` Function - -Checks if any serviceworker is registered and returns boolean as -response to `callback`. - -### WebContents.unregisterServiceWorker(callback) - -* `callback` Function - -Unregisters any serviceworker if present and returns boolean as -response to `callback` when the JS promise is fullfilled or false -when the JS promise is rejected. - -### WebContents.send(channel[, args...]) - -* `channel` String - -Send `args..` to the web page via `channel` in asynchronous message, the web -page can handle it by listening to the `channel` event of `ipc` module. - -An example of sending messages from the main process to the renderer process: - -```javascript -// On the main process. -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 -// index.html - - - - - -``` - -**Note:** - -1. The IPC message handler in web pages do not have a `event` parameter, which - is different from the handlers on the main process. -2. There is no way to send synchronous messages from the main process to a - renderer process, because it would be very easy to cause dead locks. diff --git a/docs/api/chrome-command-line-switches.md b/docs/api/chrome-command-line-switches.md index fe22bd2c08ab..2f995c99b219 100644 --- a/docs/api/chrome-command-line-switches.md +++ b/docs/api/chrome-command-line-switches.md @@ -1,7 +1,7 @@ # Supported Chrome command line switches -Following command lines switches in Chrome browser are also Supported in -Electron, you can use [app.commandLine.appendSwitch][append-switch] to append +This page lists the command line switches used by the Chrome browser that are also supported by +Electron. You can use [app.commandLine.appendSwitch][append-switch] to append them in your app's main script before the [ready][ready] event of [app][app] module is emitted: @@ -11,30 +11,43 @@ 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` + +Sets the `path` of client certificate file. + +## --ignore-connections-limit=`domains` + +Ignore the connections limit for `domains` list separated by `,`. + ## --disable-http-cache Disables the disk cache for HTTP requests. ## --remote-debugging-port=`port` -Enables remote debug over HTTP on the specified `port`. +Enables remote debugging over HTTP on the specified `port`. ## --proxy-server=`address:port` -Uses a specified proxy server, overrides system settings. This switch only +Use a specified proxy server, which overrides the system setting. This switch only affects HTTP and HTTPS requests. +## --proxy-pac-url=`url` + +Uses the PAC script at the specified `url`. + ## --no-proxy-server -Don't use a proxy server, always make direct connections. Overrides any other +Don't use a proxy server and always make direct connections. Overrides any other proxy server flags that are passed. ## --host-rules=`rules` -Comma-separated list of `rules` that control how hostnames are mapped. +A comma-separated list of `rules` that control how hostnames are mapped. For example: @@ -47,7 +60,7 @@ For example: "www.google.com". These mappings apply to the endpoint host in a net request (the TCP connect -and host resolver in a direct connection, and the `CONNECT` in an http proxy +and host resolver in a direct connection, and the `CONNECT` in an HTTP proxy connection, and the endpoint host in a `SOCKS` proxy connection). ## --host-resolver-rules=`rules` @@ -60,15 +73,19 @@ Like `--host-rules` but these `rules` only apply to the host resolver. ## --ignore-certificate-errors -Ignore certificate related errors. +Ignores certificate related errors. -## --ppapi-flash-path +## --ppapi-flash-path=`path` -Set path to pepper flash plugin for use. +Sets the `path` of the pepper flash plugin. -## --ppapi-flash-version +## --ppapi-flash-version=`version` -Set the pepper flash version. +Sets the `version` of the pepper flash plugin. + +## --log-net-log=`path` + +Enables net log events to be saved and writes them to `path`. ## --v=`log_level` @@ -85,7 +102,7 @@ source files `my_module.*` and `foo*.*`. Any pattern containing a forward or backward slash will be tested against the whole pathname and not just the module. E.g. `*/foo/bar/*=2` would change the -logging level for all code in source files under a `foo/bar` directory. +logging level for all code in the source files under a `foo/bar` directory. To disable all chromium related logs and only enable your application logs you can do: diff --git a/docs/api/clipboard.md b/docs/api/clipboard.md index c0f830491c1b..a99605baea18 100644 --- a/docs/api/clipboard.md +++ b/docs/api/clipboard.md @@ -1,14 +1,14 @@ # clipboard -The `clipboard` provides methods to do copy/paste operations. An example of -writing a string to clipboard: +The `clipboard` module provides methods to perform copy and paste operations. +The following example shows how to write a string to the clipboard: ```javascript var clipboard = require('clipboard'); clipboard.writeText('Example String'); ``` -On X Window systems, there is also a selection clipboard, to manipulate in it +On X Window systems, there is also a selection clipboard. To manipulate it you need to pass `selection` to each method: ```javascript @@ -17,52 +17,92 @@ clipboard.writeText('Example String', 'selection'); console.log(clipboard.readText('selection')); ``` -## clipboard.readText([type]) +## Methods -* `type` String +The `clipboard` module has the following methods: -Returns the content in clipboard as plain text. +**Note:** Experimental APIs are marked as such and could be removed in future. -## clipboard.writeText(text[, type]) +### `clipboard.readText([type])` + +* `type` String (optional) + +Returns the content in the clipboard as plain text. + +### `clipboard.writeText(text[, type])` * `text` String -* `type` String +* `type` String (optional) -Writes the `text` into clipboard as plain text. +Writes the `text` into the clipboard as plain text. -## clipboard.readImage([type]) +### `clipboard.readHtml([type])` -* `type` String +* `type` String (optional) -Returns the content in clipboard as [NativeImage](native-image.md). +Returns the content in the clipboard as markup. -## clipboard.writeImage(image[, type]) +### `clipboard.writeHtml(markup[, type])` + +* `markup` String +* `type` String (optional) + +Writes `markup` to the clipboard. + +### `clipboard.readImage([type])` + +* `type` String (optional) + +Returns the content in the clipboard as a [NativeImage](native-image.md). + +### `clipboard.writeImage(image[, type])` * `image` [NativeImage](native-image.md) -* `type` String +* `type` String (optional) -Writes the `image` into clipboard. +Writes `image` to the clipboard. -## clipboard.clear([type]) +### `clipboard.clear([type])` -* `type` String +* `type` String (optional) -Clears everything in clipboard. +Clears the clipboard content. -## clipboard.has(format[, type]) +### `clipboard.availableFormats([type])` -* `format` String -* `type` String +* `type` String (optional) -Returns whether clipboard has data in specified `format`. +Returns an array of supported formats for the clipboard `type`. -**Note:** This API is experimental and could be removed in future. +### `clipboard.has(data[, type])` _Experimental_ -## clipboard.read(format[, type]) +* `data` String +* `type` String (optional) -* `format` String -* `type` String +Returns whether the clipboard supports the format of specified `data`. -Reads the data in clipboard of the `format`. +```javascript +var clipboard = require('clipboard'); +console.log(clipboard.has('

selection

')); +``` -**Note:** This API is experimental and could be removed in future. +### `clipboard.read(data[, type])` _Experimental_ + +* `data` String +* `type` String (optional) + +Reads `data` from the clipboard. + +### `clipboard.write(data[, type])` + +* `data` Object + * `text` String + * `html` String + * `image` [NativeImage](native-image.md) +* `type` String (optional) + +```javascript +var clipboard = require('clipboard'); +clipboard.write({text: 'test', html: "test"}); +``` +Writes `data` to the clipboard. diff --git a/docs/api/content-tracing.md b/docs/api/content-tracing.md index ce66e3ff4ab8..2e05fc67668f 100644 --- a/docs/api/content-tracing.md +++ b/docs/api/content-tracing.md @@ -1,49 +1,55 @@ -# content-tracing +# contentTracing -The `content-trace` module is used to collect tracing data generated by the +The `content-tracing` module is used to collect tracing data generated by the underlying Chromium content module. This module does not include a web interface -so you need to open `chrome://tracing/` in Chrome browser and load the generated -file to view the result. +so you need to open `chrome://tracing/` in a Chrome browser and load the +generated file to view the result. ```javascript -var tracing = require('content-tracing'); -tracing.startRecording('*', tracing.DEFAULT_OPTIONS, function() { +var contentTracing = require('content-tracing'); + +contentTracing.startRecording('*', contentTracing.DEFAULT_OPTIONS, function() { console.log('Tracing started'); setTimeout(function() { - tracing.stopRecording('', function(path) { + contentTracing.stopRecording('', function(path) { console.log('Tracing data recorded to ' + path); }); }, 5000); }); ``` -## tracing.getCategories(callback) +## Methods + +The `content-tracing` module has the following methods: + +### `contentTracing.getCategories(callback)` * `callback` Function Get a set of category groups. The category groups can change as new code paths are reached. -Once all child processes have acked to the `getCategories` request, `callback` -is called back with an array of category groups. +Once all child processes have acknowledged the `getCategories` request the +`callback` is invoked with an array of category groups. -## tracing.startRecording(categoryFilter, options, callback) +### `contentTracing.startRecording(options, callback)` -* `categoryFilter` String -* `options` Integer +* `options` Object + * `categoryFilter` String + * `traceOptions` String * `callback` Function Start recording on all processes. -Recording begins immediately locally, and asynchronously on child processes -as soon as they receive the EnableRecording request. Once all child processes -have acked to the `startRecording` request, `callback` will be called back. +Recording begins immediately locally and asynchronously on child processes +as soon as they receive the EnableRecording request. The `callback` will be +called once all child processes have acknowledged the `startRecording` request. `categoryFilter` is a filter to control what category groups should be traced. A filter can have an optional `-` prefix to exclude category groups that contain a matching category. Having both included and excluded -category patterns in the same list would not be supported. +category patterns in the same list is not supported. Examples: @@ -51,78 +57,94 @@ Examples: * `test_MyTest*,test_OtherStuff`, * `"-excluded_category1,-excluded_category2` -`options` controls what kind of tracing is enabled, it could be a OR-ed -combination of `tracing.DEFAULT_OPTIONS`, `tracing.ENABLE_SYSTRACE`, -`tracing.ENABLE_SAMPLING` and `tracing.RECORD_CONTINUOUSLY`. +`traceOptions` controls what kind of tracing is enabled, it is a comma-delimited +list. Possible options are: -## tracing.stopRecording(resultFilePath, callback) +* `record-until-full` +* `record-continuously` +* `trace-to-console` +* `enable-sampling` +* `enable-systrace` + +The first 3 options are trace recoding modes and hence mutually exclusive. +If more than one trace recording modes appear in the `traceOptions` string, +the last one takes precedence. If none of the trace recording modes are +specified, recording mode is `record-until-full`. + +The trace option will first be reset to the default option (`record_mode` set to +`record-until-full`, `enable_sampling` and `enable_systrace` set to `false`) +before options parsed from `traceOptions` are applied on it. + +### `contentTracing.stopRecording(resultFilePath, callback)` * `resultFilePath` String * `callback` Function Stop recording on all processes. -Child processes typically are caching trace data and only rarely flush and send -trace data back to the main process. That is because it may be an expensive -operation to send the trace data over IPC, and we would like to avoid much -runtime overhead of tracing. So, to end tracing, we must asynchronously ask all -child processes to flush any pending trace data. +Child processes typically cache trace data and only rarely flush and send +trace data back to the main process. This helps to minimize the runtime overhead +of tracing since sending trace data over IPC can be an expensive operation. So, +to end tracing, we must asynchronously ask all child processes to flush any +pending trace data. -Once all child processes have acked to the `stopRecording` request, `callback` -will be called back with a file that contains the traced data. +Once all child processes have acknowledged the `stopRecording` request, +`callback` will be called with a file that contains the traced data. -Trace data will be written into `resultFilePath` if it is not empty, or into a +Trace data will be written into `resultFilePath` if it is not empty or into a temporary file. The actual file path will be passed to `callback` if it's not -null. +`null`. -## tracing.startMonitoring(categoryFilter, options, callback) +### `contentTracing.startMonitoring(options, callback)` -* `categoryFilter` String -* `options` Integer +* `options` Object + * `categoryFilter` String + * `traceOptions` String * `callback` Function Start monitoring on all processes. -Monitoring begins immediately locally, and asynchronously on child processes as +Monitoring begins immediately locally and asynchronously on child processes as soon as they receive the `startMonitoring` request. -Once all child processes have acked to the `startMonitoring` request, -`callback` will be called back. +Once all child processes have acknowledged the `startMonitoring` request the +`callback` will be called. -## tracing.stopMonitoring(callback); +### `contentTracing.stopMonitoring(callback)` * `callback` Function Stop monitoring on all processes. -Once all child processes have acked to the `stopMonitoring` request, `callback` -is called back. +Once all child processes have acknowledged the `stopMonitoring` request the +`callback` is called. -## tracing.captureMonitoringSnapshot(resultFilePath, callback) +### `contentTracing.captureMonitoringSnapshot(resultFilePath, callback)` * `resultFilePath` String * `callback` Function Get the current monitoring traced data. -Child processes typically are caching trace data and only rarely flush and send -trace data back to the main process. That is because it may be an expensive -operation to send the trace data over IPC, and we would like to avoid much -runtime overhead of tracing. So, to end tracing, we must asynchronously ask all -child processes to flush any pending trace data. +Child processes typically cache trace data and only rarely flush and send +trace data back to the main process. This is because it may be an expensive +operation to send the trace data over IPC and we would like to avoid unneeded +runtime overhead from tracing. So, to end tracing, we must asynchronously ask +all child processes to flush any pending trace data. -Once all child processes have acked to the `captureMonitoringSnapshot` request, -`callback` will be called back with a file that contains the traced data. +Once all child processes have acknowledged the `captureMonitoringSnapshot` +request the `callback` will be called with a file that contains the traced data. -## tracing.getTraceBufferUsage(callback) +### `contentTracing.getTraceBufferUsage(callback)` * `callback` Function -Get the maximum across processes of trace buffer percent full state. When the -TraceBufferUsage value is determined, the `callback` is called. +Get the maximum usage across processes of trace buffer as a percentage of the +full state. When the TraceBufferUsage value is determined the `callback` is +called. -## tracing.setWatchEvent(categoryName, eventName, callback) +### `contentTracing.setWatchEvent(categoryName, eventName, callback)` * `categoryName` String * `eventName` String @@ -131,7 +153,7 @@ TraceBufferUsage value is determined, the `callback` is called. `callback` will will be called every time the given event occurs on any process. -## tracing.cancelWatchEvent() +### `contentTracing.cancelWatchEvent()` -Cancel the watch event. If tracing is enabled, this may race with the watch -event callback. +Cancel the watch event. This may lead to a race condition with the watch event +callback if tracing is enabled. diff --git a/docs/api/crash-reporter.md b/docs/api/crash-reporter.md index 47e3666fa440..86670fcc90c9 100644 --- a/docs/api/crash-reporter.md +++ b/docs/api/crash-reporter.md @@ -1,9 +1,13 @@ -# crash-reporter +# crashReporter -An example of automatically submitting crash reporters to remote server: +The `crash-reporter` module enables sending your app's crash reports. + +The following is an example of automatically submitting a crash report to a +remote server: ```javascript -crashReporter = require('crash-reporter'); +var crashReporter = require('crash-reporter'); + crashReporter.start({ productName: 'YourName', companyName: 'YourCompany', @@ -12,38 +16,60 @@ crashReporter.start({ }); ``` -## crashReporter.start(options) +## Methods -* `options` Object - * `productName` String, default: Electron - * `companyName` String, default: GitHub, Inc - * `submitUrl` String, default: http://54.249.141.255:1127/post - * URL that crash reports would 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 which content will be send along with the report. - * Only string properties are send correctly. - * Nested objects are not supported. +The `crash-reporter` module has the following methods: -## crashReporter.getLastCrashReport() +### `crashReporter.start(options)` -Returns the date and ID of last crash report, when there was no crash report -sent or the crash reporter is not started, `null` will be returned. +`options` Object, properties: -# crash-reporter payload +* `productName` String, default: Electron. +* `companyName` String, default: GitHub, Inc. +* `submitUrl` String, default: http://54.249.141.255:1127/post. + * 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. + +You are required to call this method before using other `crashReporter` +APIs. + +**Note:** On OS X, Electron uses a new `crashpad` client, which is different +from `breakpad` on Windows and Linux. To enable the crash collection feature, +you are required to call the `crashReporter.start` API to initialize `crashpad` +in the main process and in each renderer process from which you wish to collect +crash reports. + +### `crashReporter.getLastCrashReport()` + +Returns the date and ID of the last crash report. If no crash reports have been +sent or the crash reporter has not been started, `null` is returned. + +### `crashReporter.getUploadedReports()` + +Returns all uploaded crash reports. Each report contains the date and uploaded +ID. + +## crash-reporter Payload The crash reporter will send the following data to the `submitUrl` as `POST`: -* `rept` String - e.g. 'electron-crash-service' -* `ver` String - The version of Electron -* `platform` String - e.g. 'win32' -* `process_type` String - e.g. 'renderer' +* `rept` String - e.g. 'electron-crash-service'. +* `ver` String - The version of Electron. +* `platform` String - e.g. 'win32'. +* `process_type` String - e.g. 'renderer'. * `ptime` Number -* `_version` String - The version in `package.json` -* `_productName` String - The product name in the crashReporter `options` object -* `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 crashreport as file -* All level one properties of the `extra` object in the crashReporter `options` object +* `_version` String - The version in `package.json`. +* `_productName` String - The product name in the `crashReporter` `options` + object. +* `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. +* 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 4f4403db6d8e..0fadfa37f80c 100644 --- a/docs/api/dialog.md +++ b/docs/api/dialog.md @@ -1,35 +1,42 @@ # dialog -The `dialog` module provides APIs to show native system dialogs, so web -applications can get the same user experience with native applications. +The `dialog` module provides APIs to show native system dialogs, such as opening +files or alerting, so web applications can deliver the same user experience as +native applications. An example of showing a dialog to select multiple files and directories: ```javascript -var win = ...; // window in which to show the dialog +var win = ...; // BrowserWindow in which to show the dialog var dialog = require('dialog'); console.log(dialog.showOpenDialog({ properties: [ 'openFile', 'openDirectory', 'multiSelections' ]})); ``` -**Note for OS X**: If you want to present dialogs as sheets, the only thing you have to do is to provide a `BrowserWindow` reference in the `browserWindow` parameter. +**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. -## dialog.showOpenDialog([browserWindow], [options], [callback]) +## Methods -* `browserWindow` BrowserWindow -* `options` Object +The `dialog` module has the following methods: + +### `dialog.showOpenDialog([browserWindow][, options][, callback])` + +* `browserWindow` BrowserWindow (optional) +* `options` Object (optional) * `title` String * `defaultPath` String * `filters` Array * `properties` Array - Contains which features the dialog should use, can contain `openFile`, `openDirectory`, `multiSelections` and `createDirectory` -* `callback` Function +* `callback` Function (optional) -On success, returns an array of file paths chosen by the user, otherwise -returns `undefined`. +On success this method returns an array of file paths chosen by the user, +otherwise it returns `undefined`. The `filters` specifies an array of file types that can be displayed or -selected, an example is: +selected when you want to limit the user to a specific type. For example: ```javascript { @@ -37,57 +44,74 @@ selected, an example is: { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] }, { name: 'Custom File Type', extensions: ['as'] }, - ], + { name: 'All Files', extensions: ['*'] } + ] } ``` -If a `callback` is passed, the API call would be asynchronous and the result -would be passed via `callback(filenames)` +The `extensions` array should contain extensions without wildcards or dots (e.g. +`'png'` is good but `'.png'` and `'*.png'` are bad). To show all files, use the +`'*'` wildcard (no other wildcard is supported). -**Note:** On Windows and Linux, an open dialog could not be both file selector -and directory selector at the same time, so if you set `properties` to -`['openFile', 'openDirectory']` on these platforms, a directory selector would -be showed. +If a `callback` is passed, the API call will be asynchronous and the result +will be passed via `callback(filenames)` -## dialog.showSaveDialog([browserWindow], [options], [callback]) +**Note:** On Windows and Linux an open dialog can not be both a file selector +and a directory selector, so if you set `properties` to +`['openFile', 'openDirectory']` on these platforms, a directory selector will be +shown. -* `browserWindow` BrowserWindow -* `options` Object +### `dialog.showSaveDialog([browserWindow][, options][, callback])` + +* `browserWindow` BrowserWindow (optional) +* `options` Object (optional) * `title` String * `defaultPath` String * `filters` Array -* `callback` Function +* `callback` Function (optional) -On success, returns the path of file chosen by the user, otherwise returns -`undefined`. +On success this method returns the path of the file chosen by the user, +otherwise it returns `undefined`. The `filters` specifies an array of file types that can be displayed, see `dialog.showOpenDialog` for an example. -If a `callback` is passed, the API call would be asynchronous and the result -would be passed via `callback(filename)` +If a `callback` is passed, the API call will be asynchronous and the result +will be passed via `callback(filename)` -## dialog.showMessageBox([browserWindow], options, [callback]) +### `dialog.showMessageBox([browserWindow][, options][, callback])` -* `browserWindow` BrowserWindow -* `options` Object - * `type` String - Can be `"none"`, `"info"` or `"warning"` - * `buttons` Array - Array of texts for buttons - * `title` String - Title of the message box, some platforms will not show it - * `message` String - Content of the message box - * `detail` String - Extra information of the message +* `browserWindow` BrowserWindow (optional) +* `options` Object (optional) + * `type` String - Can be `"none"`, `"info"`, `"error"`, `"question"` or + `"warning"`. On Windows, "question" displays the same icon as "info", unless + you set an icon using the "icon" option. + * `buttons` Array - Array of texts for buttons. + * `title` String - Title of the message box, some platforms will not show it. + * `message` String - Content of the message box. + * `detail` String - Extra information of the message. * `icon` [NativeImage](native-image.md) + * `cancelId` Integer - The value will be returned when user cancels the dialog + instead of clicking the buttons of the dialog. By default it is the index + of the buttons that have "cancel" or "no" as label, or 0 if there is no such + buttons. On OS X and Windows the index of "Cancel" button will always be + used as `cancelId`, not matter whether it is already specified. + * `noLink` Boolean - On Windows Electron will try to figure out which one of + the `buttons` are common buttons (like "Cancel" or "Yes"), and show the + others as command links in the dialog. This can make the dialog appear in + the style of modern Windows apps. If you don't like this behavior, you can + set `noLink` to `true`. * `callback` Function -Shows a message box, it will block until the message box is closed. It returns -the index of the clicked button. +Shows a message box, it will block the process until the message box is closed. +It returns the index of the clicked button. -If a `callback` is passed, the API call would be asynchronous and the result -would be passed via `callback(response)` +If a `callback` is passed, the API call will be asynchronous and the result +will be passed via `callback(response)`. -## dialog.showErrorBox(title, content) +### `dialog.showErrorBox(title, content)` -Runs a modal dialog that shows an error message. +Displays a modal dialog that shows an error message. -This API can be called safely before the `ready` event of `app` module emits, it -is usually used to report errors in early stage of startup. +This API can be called safely before the `ready` event the `app` module emits, +it is usually used to report errors in early stage of startup. diff --git a/docs/api/file-object.md b/docs/api/file-object.md index 36154cdd6a1f..01d49c39155f 100644 --- a/docs/api/file-object.md +++ b/docs/api/file-object.md @@ -1,11 +1,11 @@ # `File` object -The DOM's File interface provides abstraction around native files, in order to -let users work on native files directly with HTML5 file API, Electron has -added a `path` attribute to `File` interface which exposes the file's real path -on filesystem. +The DOM's File interface provides abstraction around native files in order to +let users work on native files directly with the HTML5 file API. Electron has +added a `path` attribute to the `File` interface which exposes the file's real +path on filesystem. -Example on getting real path of a dragged file: +Example on getting a real path from a dragged-onto-the-app file: ```html
diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index fe3b33388d9b..8d64a6fcd7c8 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -1,10 +1,10 @@ -# Frameless window +# Frameless Window -A frameless window is a window that has no chrome. +A frameless window is a window that has no [chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome), the parts of the window, like toolbars, that are not a part of the webp page. These are options on the [`BrowserWindow`](browser-window.md) class. ## Create a frameless window -To create a frameless window, you only need to specify `frame` to `false` in +To create a frameless window, you need to set `frame` to `false` in [BrowserWindow](browser-window.md)'s `options`: @@ -13,6 +13,20 @@ var BrowserWindow = require('browser-window'); var win = new BrowserWindow({ width: 800, height: 600, frame: false }); ``` +### Alternatives on Mac + +On Mac OS X 10.10 Yosemite and newer, there's an alternative way to specify +a chromeless window. Instead of setting `frame` to `false` which disables +both the titlebar and window controls, you may want to have the title bar +hidden and your content extend to the full window size, yet still preserve +the window controls ("traffic lights") for standard window actions. +You can do so by specifying the new `title-bar-style` option: + +```javascript +var BrowserWindow = require('browser-window'); +var win = new BrowserWindow({ width: 800, height: 600, 'title-bar-style': 'hidden' }); +``` + ## Transparent window By setting the `transparent` option to `true`, you can also make the frameless @@ -24,19 +38,22 @@ var win = new BrowserWindow({ transparent: true, frame: false }); ### Limitations -* You can not click through the transparent area, we are going to introduce an +* You can not click through the transparent area. We are going to introduce an API to set window shape to solve this, but currently blocked at an [upstream bug](https://code.google.com/p/chromium/issues/detail?id=387234). -* Transparent window is not resizable, setting `resizable` to `true` may make - transprent window stop working on some platforms. +* Transparent windows are not resizable. Setting `resizable` to `true` may make + a transparent window stop working on some platforms. * The `blur` filter only applies to the web page, so there is no way to apply - blur effect to the content below the window. -* On Windows transparent window will not work when DWM is disabled. + blur effect to the content below the window (i.e. other applications open on + the user's system). +* On Windows operation systems, transparent windows will not work when DWM is + disabled. * On Linux users have to put `--enable-transparent-visuals --disable-gpu` in - command line to disable GPU and allow ARGB to make transparent window, this is - caused by an upstream bug that [alpha channel doesn't work on some NVidia - drivers](https://code.google.com/p/chromium/issues/detail?id=369209) on Linux. -* On Mac the native window shadow will not show for transparent window. + the command line to disable GPU and allow ARGB to make transparent window, + this is caused by an upstream bug that [alpha channel doesn't work on some + NVidia drivers](https://code.google.com/p/chromium/issues/detail?id=369209) on + Linux. +* On Mac the native window shadow will not be shown on a transparent window. ## Draggable region @@ -44,7 +61,7 @@ By default, the frameless window is non-draggable. Apps need to specify `-webkit-app-region: drag` in CSS to tell Electron which regions are draggable (like the OS's standard titlebar), and apps can also use `-webkit-app-region: no-drag` to exclude the non-draggable area from the - draggable region. Note that only rectangular shape is currently supported. + draggable region. Note that only rectangular shapes are currently supported. To make the whole window draggable, you can add `-webkit-app-region: drag` as `body`'s style: @@ -64,15 +81,15 @@ button { } ``` -If you're only using a custom titlebar, you also need to make buttons in -titlebar non-draggable. +If you're setting just a custom titlebar as draggable, you also need to make all +buttons in titlebar non-draggable. ## Text selection -One thing on frameless window is that the dragging behaviour may conflict with -selecting text, for example, when you drag the titlebar, you may accidentally -select the text on titlebar. To prevent this, you need to disable text -selection on dragging area like this: +In a frameless window the dragging behaviour may conflict with selecting text. +For example, when you drag the titlebar you may accidentally select the text on +the titlebar. To prevent this, you need to disable text selection within a +draggable area like this: ```css .titlebar { @@ -83,7 +100,7 @@ selection on dragging area like this: ## Context menu -On some platforms, the draggable area would be treated as non-client frame, so -when you right click on it a system menu would be popuped. To make context menu -behave correctly on all platforms, you should never custom context menu on +On some platforms, the draggable area will be treated as a non-client frame, so +when you right click on it a system menu will pop up. To make the context menu +behave correctly on all platforms you should never use a custom context menu on draggable areas. diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index 285c26995afb..adba06e1adcf 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -1,47 +1,65 @@ # global-shortcut The `global-shortcut` module can register/unregister a global keyboard shortcut -in operating system, so that you can custom the operations for various shortcuts. -Note that it is global, even the app does not get focused, it still works. +with the operating system so that you can customize the operations for various +shortcuts. + +**Note:** The shortcut is global; it will work even if the app does +not have the keyboard focus. You should not use this module until the `ready` +event of the app module is emitted. ```javascript +var app = require('app'); var globalShortcut = require('global-shortcut'); -// Register a 'ctrl+x' shortcut listener. -var ret = globalShortcut.register('ctrl+x', function() { console.log('ctrl+x is pressed'); }) -if (!ret) - console.log('registerion fails'); +app.on('ready', function() { + // Register a 'ctrl+x' shortcut listener. + var ret = globalShortcut.register('ctrl+x', function() { + console.log('ctrl+x is pressed'); + }) -// Check whether a shortcut is registered. -console.log(globalShortcut.isRegistered('ctrl+x')); + if (!ret) { + console.log('registration failed'); + } -// Unregister a shortcut. -globalShortcut.unregister('ctrl+x'); + // Check whether a shortcut is registered. + console.log(globalShortcut.isRegistered('ctrl+x')); +}); -// Unregister all shortcuts. -globalShortcut.unregisterAll(); +app.on('will-quit', function() { + // Unregister a shortcut. + globalShortcut.unregister('ctrl+x'); + + // Unregister all shortcuts. + globalShortcut.unregisterAll(); +}); ``` -## globalShortcut.register(accelerator, callback) +## Methods + +The `global-shortcut` module has the following methods: + +### `globalShortcut.register(accelerator, callback)` * `accelerator` [Accelerator](accelerator.md) * `callback` Function -Registers a global shortcut of `accelerator`, the `callback` would be called when -the registered shortcut is pressed by user. +Registers a global shortcut of `accelerator`. The `callback` is called when +the registered shortcut is pressed by the user. -## globalShortcut.isRegistered(accelerator) +### `globalShortcut.isRegistered(accelerator)` * `accelerator` [Accelerator](accelerator.md) -Returns whether shortcut of `accelerator` is registered. +Returns `true` or `false` depending on whether the shortcut `accelerator` is +registered. -## globalShortcut.unregister(accelerator) +### `globalShortcut.unregister(accelerator)` * `accelerator` [Accelerator](accelerator.md) -Unregisters the global shortcut of `keycode`. +Unregisters the global shortcut of `accelerator`. -## globalShortcut.unregisterAll() +### `globalShortcut.unregisterAll()` Unregisters all the global shortcuts. diff --git a/docs/api/ipc-main-process.md b/docs/api/ipc-main-process.md index d558f7fb24d6..98d9c3c22d43 100644 --- a/docs/api/ipc-main-process.md +++ b/docs/api/ipc-main-process.md @@ -1,17 +1,22 @@ # ipc (main process) -Handles asynchronous and synchronous message sent from a renderer process (web -page). +The `ipc` module, when used in the main process, handles asynchronous and +synchronous messages sent from a renderer process (web page). Messages sent from +a renderer will be emitted to this module. -The messages sent from a renderer would be emitted to this module, the event name -is the `channel` when sending message. To reply a synchronous message, you need -to set `event.returnValue`, to send an asynchronous back to the sender, you can -use `event.sender.send(...)`. +## Sending Messages -It's also possible to send messages from main process to the renderer process, -see [WebContents.send](browser-window.md#webcontentssendchannel-args) for more. +It is also possible to send messages from the main process to the renderer +process, see [WebContents.send](web-contents.md#webcontentssendchannel-args) +for more information. -An example of sending and handling messages: +- When sending a message, the event name is the `channel`. +- To reply a synchronous message, you need to set `event.returnValue`. +- To send an asynchronous back to the sender, you can use + `event.sender.send(...)`. + +An example of sending and handling messages between the render and main +processes: ```javascript // In main process. @@ -38,12 +43,34 @@ ipc.on('asynchronous-reply', function(arg) { ipc.send('asynchronous-message', 'ping'); ``` -## Class: Event +## Listening for Messages -### Event.returnValue +The `ipc` module has the following method to listen for events: -Assign to this to return an value to synchronous messages. +### `ipc.on(channel, callback)` -### Event.sender +* `channel` String - The event name. +* `callback` Function -The `WebContents` that sent the message. +When the event occurs the `callback` is called with an `event` object and a +message, `arg`. + +## IPC Events + +The `event` object passed to the `callback` has the following methods: + +### `Event.returnValue` + +Set this to the value to be returned in a synchronous message. + +### `Event.sender` + +Returns the `WebContents` that sent the message. + +### `Event.sender.send(channel[, arg1][, arg2][, ...])` + +* `channel` String - The event name. +* `arg` (optional) + +This sends an asynchronous message back to the render process. Optionally, there +can be one or a series of arguments, `arg`, which can have any type. diff --git a/docs/api/ipc-renderer.md b/docs/api/ipc-renderer.md index 3aee11d781c9..752af2ebe293 100644 --- a/docs/api/ipc-renderer.md +++ b/docs/api/ipc-renderer.md @@ -1,29 +1,52 @@ # ipc (renderer) The `ipc` module provides a few methods so you can send synchronous and -asynchronous messages to the main process, and also receive messages sent from -main process. If you want to make use of modules of main process from renderer +asynchronous messages from the render process (web page) to the main process. +You can also receive replies from the main process. + +**Note:** If you want to make use of modules in the main process from the renderer process, you might consider using the [remote](remote.md) module. -See [ipc (main process)](ipc-main-process.md) for examples. +See [ipc (main process)](ipc-main-process.md) for code examples. -## ipc.send(channel[, args...]) +## Methods -Send `args..` to the renderer via `channel` in asynchronous message, the main -process can handle it by listening to the `channel` event of `ipc` module. +The `ipc` module has the following methods for sending messages: -## ipc.sendSync(channel[, args...]) +**Note:** When using these methods to send a `message` you must also listen +for it in the main process with [`ipc (main process)`](ipc-main-process.md). -Send `args..` to the renderer via `channel` in synchronous message, and returns -the result sent from main process. The main process can handle it by listening to -the `channel` event of `ipc` module, and returns by setting `event.returnValue`. +### `ipc.send(channel[, arg1][, arg2][, ...])` -**Note:** Usually developers should never use this API, since sending -synchronous message would block the whole renderer process. +* `channel` String - The event name. +* `arg` (optional) -## ipc.sendToHost(channel[, args...]) +Send an event to the main process asynchronously via a `channel`. Optionally, +there can be a message: one or a series of arguments, `arg`, which can have any +type. The main process handles it by listening for the `channel` event with +`ipc`. -Like `ipc.send` but the message will be sent to the host page instead of the -main process. +### `ipc.sendSync(channel[, arg1][, arg2][, ...])` -This is mainly used by the page in `` to communicate with host page. +* `channel` String - The event name. +* `arg` (optional) + +Send an event to the main process synchronously via a `channel`. Optionally, +there can be a message: one or a series of arguments, `arg`, which can have any +type. The main process handles it by listening for the `channel` event with +`ipc`. + +The main process handles it by listening for the `channel` event with `ipc` and +replies by setting the `event.returnValue`. + +**Note:** Sending a synchronous message will block the whole renderer process so +using this method is not recommended. + +### `ipc.sendToHost(channel[, arg1][, arg2][, ...])` + +* `channel` String - The event name. +* `arg` (optional) + +Like `ipc.send` but the event will be sent to the host page in a `` +instead of the main process. Optionally, there can be a message: one or a series +of arguments, `arg`, which can have any type. diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 360a48ab98e0..89524d9f2352 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -1,13 +1,21 @@ -# menu-item +# MenuItem + +The `menu-item` module allows you to add items to an application or content +[`menu`](menu.md). + +See [`menu`](menu.md) for examples. ## Class: MenuItem +Create a new `MenuItem` with the following method: + ### new MenuItem(options) * `options` Object - * `click` Function - Callback when the menu item is clicked - * `selector` String - Call the selector of first responder when clicked (OS - X only) + * `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 + `click` property will be ignored * `type` String - Can be `normal`, `separator`, `submenu`, `checkbox` or `radio` * `label` String @@ -23,3 +31,29 @@ 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. + +The `role` property can have following values: + +* `undo` +* `redo` +* `cut` +* `copy` +* `paste` +* `selectall` +* `minimize` - Minimize current window +* `close` - Close current window + +On OS X `role` can also have following additional values: + +* `about` - Map to the `orderFrontStandardAboutPanel` action +* `hide` - Map to the `hide` action +* `hideothers` - Map to the `hideOtherApplications` action +* `unhide` - Map to the `unhideAllApplications` action +* `front` - Map to the `arrangeInFront` action +* `window` - The submenu is a "Window" menu +* `help` - The submenu is a "Help" menu +* `services` - The submenu is a "Services" menu diff --git a/docs/api/menu.md b/docs/api/menu.md index 60e37884e352..f48b07e7e40a 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -1,12 +1,17 @@ -# menu +# Menu -The `Menu` class is used to create native menus that can be used as -application menus and context menus. Each menu consists of multiple menu -items, and each menu item can have a submenu. +The `menu` class is used to create native menus that can be used as +application menus and +[context menus](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/PopupGuide/ContextMenus). +This module is a main process module which can be used in a render process via +the `remote` module. -Below is an example of creating a menu dynamically in a web page by using -the [remote](remote.md) module, and showing it when the user right clicks -the page: +Each menu consists of multiple [menu items](menu-item.md) and each menu item can +have a submenu. + +Below is an example of creating a menu dynamically in a web page +(render process) by using the [remote](remote.md) module, and showing it when +the user right clicks the page: ```html @@ -27,41 +32,149 @@ window.addEventListener('contextmenu', function (e) { ``` -Another example of creating the application menu with the simple template API: +An example of creating the application menu in the render process with the +simple template API: ```javascript -// main.js var template = [ { - label: 'Electron', + label: 'Edit', submenu: [ { - label: 'About Electron', - selector: 'orderFrontStandardAboutPanel:' + 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('shell').openExternal('http://electron.atom.io') } + }, + ] + }, +]; + +if (process.platform == 'darwin') { + var name = require('app').getName(); + template.unshift({ + label: name, + submenu: [ + { + label: 'About ' + name, + role: 'about' }, { type: 'separator' }, { label: 'Services', + role: 'services', submenu: [] }, { type: 'separator' }, { - label: 'Hide Electron', + label: 'Hide ' + name, accelerator: 'Command+H', - selector: 'hide:' + role: 'hide' }, { label: 'Hide Others', accelerator: 'Command+Shift+H', - selector: 'hideOtherApplications:' + role: 'hideothers' }, { label: 'Show All', - selector: 'unhideAllApplications:' + role: 'unhide' }, { type: 'separator' @@ -72,191 +185,120 @@ var template = [ click: function() { app.quit(); } }, ] - }, - { - label: 'Edit', - submenu: [ - { - label: 'Undo', - accelerator: 'Command+Z', - selector: 'undo:' - }, - { - label: 'Redo', - accelerator: 'Shift+Command+Z', - selector: 'redo:' - }, - { - type: 'separator' - }, - { - label: 'Cut', - accelerator: 'Command+X', - selector: 'cut:' - }, - { - label: 'Copy', - accelerator: 'Command+C', - selector: 'copy:' - }, - { - label: 'Paste', - accelerator: 'Command+V', - selector: 'paste:' - }, - { - label: 'Select All', - accelerator: 'Command+A', - selector: 'selectAll:' - }, - ] - }, - { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'Command+R', - click: function() { BrowserWindow.getFocusedWindow().reloadIgnoringCache(); } - }, - { - label: 'Toggle DevTools', - accelerator: 'Alt+Command+I', - click: function() { BrowserWindow.getFocusedWindow().toggleDevTools(); } - }, - ] - }, - { - label: 'Window', - submenu: [ - { - label: 'Minimize', - accelerator: 'Command+M', - selector: 'performMiniaturize:' - }, - { - label: 'Close', - accelerator: 'Command+W', - selector: 'performClose:' - }, - { - type: 'separator' - }, - { - label: 'Bring All to Front', - selector: 'arrangeInFront:' - }, - ] - }, - { - label: 'Help', - submenu: [] - }, -]; + }); + // Window menu. + template[3].submenu.push( + { + type: 'separator' + }, + { + label: 'Bring All to Front', + role: 'front' + } + ); +} menu = Menu.buildFromTemplate(template); - -Menu.setApplicationMenu(menu); // Must be called within app.on('ready', function(){ ... }); +Menu.setApplicationMenu(menu); ``` ## Class: Menu -### new Menu() +### `new Menu()` Creates a new menu. -### Class Method: Menu.setApplicationMenu(menu) +## Methods + +The `menu` class has the following methods: + +### `Menu.setApplicationMenu(menu)` * `menu` Menu Sets `menu` as the application menu on OS X. On Windows and Linux, the `menu` will be set as each window's top menu. -### Class Method: Menu.sendActionToFirstResponder(action) +### `Menu.sendActionToFirstResponder(action)` _OS X_ * `action` String -Sends the `action` to the first responder of application, this is used for +Sends the `action` to the first responder of application. This is used for emulating default Cocoa menu behaviors, usually you would just use the `selector` property of `MenuItem`. -**Note:** This method is OS X only. - -### Class Method: Menu.buildFromTemplate(template) +### `Menu.buildFromTemplate(template)` * `template` Array -Generally, the `template` is just an array of `options` for constructing -[MenuItem](menu-item.md), the usage can be referenced above. +Generally, the `template` is just an array of `options` for constructing a +[MenuItem](menu-item.md). The usage can be referenced above. -You can also attach other fields to element of the `template`, and they will -become properties of the constructed menu items. +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]) +### `Menu.popup(browserWindow[, x, y])` * `browserWindow` BrowserWindow -* `x` Number -* `y` Number +* `x` Number (optional) +* `y` Number (**required** if `x` is used) -Popups 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. +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) +### `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 +### `Menu.items()` -Get the array containing the menu's items. +Get an array containing the menu's items. -## Notes on OS X application menu +## Notes on OS X Application Menu OS X has a completely different style of application menu from Windows and -Linux, and here are some notes on making your app's menu more native-like. +Linux, here are some notes on making your app's menu more native-like. -### Standard menus +### Standard Menus On OS X there are many system defined standard menus, like the `Services` and -`Windows` menus. To make your menu a standard menu, you can just set your menu's -label to one of followings, and Electron will recognize them and make them +`Windows` menus. To make your menu a standard menu, you should set your menu's +`role` to one of following and Electron will recognize them and make them become standard menus: -* `Window` -* `Help` -* `Services` +* `window` +* `help` +* `services` -### Standard menu item actions +### Standard Menu Item Actions -OS X has provided standard actions for some menu items (which are called -`selector`s), like `About xxx`, `Hide xxx`, and `Hide Others`. To set the action -of a menu item to a standard action, you can set the `selector` attribute of the -menu item. +OS X has provided standard actions for some menu items, like `About xxx`, +`Hide xxx`, and `Hide Others`. To set the action of a menu item to a standard +action, you should set the `role` attribute of the menu item. -### Main menu's name +### Main Menu's Name On OS X the label of application menu's first item is always your app's name, 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](https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html) -for more. +by modifying your app bundle's `Info.plist` file. See [About Information +Property List Files][AboutInformationPropertyListFiles] for more information. +## Menu Item Position -## Menu item position - -You can make use of `position` and `id` to control how the item would be placed +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 +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 an existing item in the menu: @@ -266,12 +308,12 @@ an existing item in the menu: * `after` - Inserts this item after id referenced item. If the referenced item doesn't exist the item will be inserted at the end of the menu. * `endof` - Inserts this item at the end of the logical group containing - the id referenced item. (Groups are created by separator items). If - the referenced item doesn't exist a new separator group is created with + the id referenced item (groups are created by separator items). If + the referenced item doesn't exist, a new separator group is created with the given id and this item is inserted after that separator. -When an item is positioned following unpositioned items are inserted after -it, until a new item is positioned. So if you want to position a group of +When an item is positioned, all un-positioned items are inserted after +it until a new item is positioned. So if you want to position a group of menu items in the same location you only need to specify a position for the first item. @@ -281,10 +323,10 @@ Template: ```javascript [ - {label: '4', id: '4'} - {label: '5', id: '5'} - {label: '1', id: '1', position: 'before=4'} - {label: '2', id: '2'} + {label: '4', id: '4'}, + {label: '5', id: '5'}, + {label: '1', id: '1', position: 'before=4'}, + {label: '2', id: '2'}, {label: '3', id: '3'} ] ``` @@ -303,11 +345,11 @@ Template: ```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: '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'} ] ``` @@ -324,3 +366,5 @@ Menu: - 2 - 3 ``` + +[AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html diff --git a/docs/api/native-image.md b/docs/api/native-image.md index 61298cae7e52..df2bb96ff9da 100644 --- a/docs/api/native-image.md +++ b/docs/api/native-image.md @@ -1,17 +1,17 @@ # NativeImage -In Electron for the APIs that take images, you can pass either file paths or -`NativeImage` instances. When passing `null`, an empty image will be used. +In Electron, for the APIs that take images, you can pass either file paths or +`NativeImage` instances. An empty image will be used when `null` is passed. -For example when creating tray or setting window's icon, you can pass image's -file path as `String` to represent an image: +For example, when creating a tray or setting a window's icon, you can pass an +image file path as a `String`: ```javascript var appIcon = new Tray('/Users/somebody/images/icon.png'); var window = new BrowserWindow({icon: '/Users/somebody/images/window.png'}); ``` -Or read the image from clipboard: +Or read the image from the clipboard which returns a `NativeImage`: ```javascript var clipboard = require('clipboard'); @@ -19,23 +19,25 @@ var image = clipboard.readImage(); var appIcon = new Tray(image); ``` -## Supported formats +## Supported Formats -Currently `PNG` and `JPEG` are supported, and it is recommended to use `PNG` -because it supports alpha channel and image is usually not compressed. +Currently `PNG` and `JPEG` image formats are supported. `PNG` is recommended +because of its support for transparency and lossless compression. -## High resolution image +On Windows, you can also load an `ICO` icon from a file path. + +## High Resolution Image On platforms that have high-DPI support, you can append `@2x` after image's -file name's base name to mark it as a high resolution image. +base filename to mark it as a high resolution image. -For example if `icon.png` is a normal image that has standard resolution, the -`icon@2x.png` would be treated as a high resolution image that has double DPI -dense. +For example if `icon.png` is a normal image that has standard resolution, then +`icon@2x.png` will be treated as a high resolution image that has double DPI +density. -If you want to support displays with different DPI denses at the same time, you -can put images with different sizes in the same folder, and use the filename -without DPI suffixes, like this: +If you want to support displays with different DPI densities at the same time, +you can put images with different sizes in the same folder and use the filename +without DPI suffixes. For example: ```text images/ @@ -49,7 +51,7 @@ images/ var appIcon = new Tray('/Users/somebody/images/icon.png'); ``` -Following suffixes as DPI denses are also supported: +Following suffixes for DPI are also supported: * `@1x` * `@1.25x` @@ -63,77 +65,91 @@ Following suffixes as DPI denses are also supported: * `@4x` * `@5x` -## Template image +## Template Image Template images consist of black and clear colors (and an alpha channel). Template images are not intended to be used as standalone images and are usually mixed with other content to create the desired final appearance. -The most common case is to use template image for menu bar icon so it can adapt -to both light and dark menu bars. +The most common case is to use template images for a menu bar icon so it can +adapt to both light and dark menu bars. -Template image is only supported on Mac. +**Note:** Template image is only supported on OS X. -To mark an image as template image, its filename should end with the word -`Template`, examples are: +To mark an image as a template image, its filename should end with the word +`Template`. For example: * `xxxTemplate.png` * `xxxTemplate@2x.png` -## nativeImage.createEmpty() +## Methods + +The `NativeImage` class has the following methods: + +### `NativeImage.createEmpty()` Creates an empty `NativeImage` instance. -## nativeImage.createFromPath(path) +### `NativeImage.createFromPath(path)` * `path` String -Creates a new `NativeImage` instance from file located at `path`. +Creates a new `NativeImage` instance from a file located at `path`. -## nativeImage.createFromBuffer(buffer[, scaleFactor]) +### `NativeImage.createFromBuffer(buffer[, scaleFactor])` * `buffer` [Buffer][buffer] -* `scaleFactor` Double +* `scaleFactor` Double (optional) -Creates a new `NativeImage` instance from `buffer`. The `scaleFactor` is 1.0 by -default. +Creates a new `NativeImage` instance from `buffer`. The default `scaleFactor` is +1.0. -## nativeImage.createFromDataUrl(dataUrl) +### `NativeImage.createFromDataUrl(dataUrl)` * `dataUrl` String Creates a new `NativeImage` instance from `dataUrl`. -## Class: NativeImage +## Instance Methods -This class is used to represent an image. +The following methods are available on instances of `nativeImage`: -### NativeImage.toPng() +```javascript +var NativeImage = require('native-image'); -Returns a [Buffer][buffer] that contains image's `PNG` encoded data. +var image = NativeImage.createFromPath('/Users/somebody/images/icon.png'); +``` -### NativeImage.toJpeg(quality) +### `image.toPng()` -* `quality` Integer +Returns a [Buffer][buffer] that contains the image's `PNG` encoded data. -Returns a [Buffer][buffer] that contains image's `JPEG` encoded data. +### `image.toJpeg(quality)` -### NativeImage.toDataUrl() +* `quality` Integer between 0 - 100 (**required**) -Returns the data URL of image. +Returns a [Buffer][buffer] that contains the image's `JPEG` encoded data. -### NativeImage.isEmpty() +### `image.toDataUrl()` -Returns whether the image is empty. +Returns the data URL of the image. -### NativeImage.getSize() +### `image.isEmpty()` + +Returns a boolean whether the image is empty. + +### `image.getSize()` Returns the size of the image. [buffer]: https://iojs.org/api/buffer.html#buffer_class_buffer -### NativeImage.setTemplateImage(option) +### `image.setTemplateImage(option)` * `option` Boolean Marks the image as template image. + +### `image.isTemplateImage()` + +Returns a boolean whether the image is a template image. diff --git a/docs/api/power-monitor.md b/docs/api/power-monitor.md index 4643110f35bb..6ffb910e164d 100644 --- a/docs/api/power-monitor.md +++ b/docs/api/power-monitor.md @@ -1,10 +1,10 @@ # power-monitor -The `power-monitor` module is used to monitor the power state change. You can -only use it on the main process. You should not use this module until the `ready` -event of `app` module gets emitted. +The `power-monitor` module is used to monitor power state changes. You can +only use it in the main process. You should not use this module until the `ready` +event of the `app` module is emitted. -An example is: +For example: ```javascript var app = require('app'); @@ -16,18 +16,22 @@ app.on('ready', function() { }); ``` -## Event: suspend +## Events + +The `power-monitor` module emits the following events: + +### Event: 'suspend' Emitted when the system is suspending. -## Event: resume +### Event: 'resume' Emitted when system is resuming. -## Event: on-ac +### Event: 'on-ac' Emitted when the system changes to AC power. -## Event: on-battery +### Event: 'on-battery' Emitted when system changes to battery power. diff --git a/docs/api/power-save-blocker.md b/docs/api/power-save-blocker.md new file mode 100644 index 000000000000..26f0abc84808 --- /dev/null +++ b/docs/api/power-save-blocker.md @@ -0,0 +1,54 @@ +# powerSaveBlocker + +The `power-save-blocker` module is used to block the system from entering +low-power (sleep) mode and thus allowing the app to keep the system and screen +active. + +For example: + +```javascript +var powerSaveBlocker = require('power-save-blocker'); + +var id = powerSaveBlocker.start('prevent-display-sleep'); +console.log(powerSaveBlocker.isStarted(id)); + +powerSaveBlocker.stop(id); +``` + +## Methods + +The `powerSaveBlocker` module has the following methods: + +### `powerSaveBlocker.start(type)` + +* `type` String - Power save blocker type. + * `prevent-app-suspension` - Prevent the application from being suspended. + Keeps system active but allows screen to be turned off. Example use cases: + downloading a file or playing audio. + * `prevent-display-sleep`- Prevent the display from going to sleep. Keeps + system and screen active. Example use case: playing video. + +Starts preventing the system from entering lower-power mode. Returns an integer +identifying the power save blocker. + +**Note:** `prevent-display-sleep` has higher has precedence over +`prevent-app-suspension`. Only the highest precedence type takes effect. In +other words, `prevent-display-sleep` always takes precedence over +`prevent-app-suspension`. + +For example, an API calling A requests for `prevent-app-suspension`, and +another calling B requests for `prevent-display-sleep`. `prevent-display-sleep` +will be used until B stops its request. After that, `prevent-app-suspension` +is used. + +### `powerSaveBlocker.stop(id)` + +* `id` Integer - The power save blocker id returned by `powerSaveBlocker.start`. + +Stops the specified power save blocker. + +### `powerSaveBlocker.isStarted(id)` + +* `id` Integer - The power save blocker id returned by `powerSaveBlocker.start`. + +Returns a boolean whether the corresponding `powerSaveBlocker` has started. diff --git a/docs/api/process.md b/docs/api/process.md index 160ffc78bc8a..abd3c4fe574c 100644 --- a/docs/api/process.md +++ b/docs/api/process.md @@ -1,9 +1,25 @@ -# Process object +# process -The `process` object in Electron has following differences between the one in +The `process` object in Electron has the following differences from the one in upstream node: -* `process.type` String - Process's type, can be `browser` (i.e. main process) or `renderer`. +* `process.type` String - Process's type, can be `browser` (i.e. main process) + or `renderer`. * `process.versions['electron']` String - Version of Electron. * `process.versions['chrome']` String - Version of Chromium. * `process.resourcesPath` String - Path to JavaScript source code. + +# Methods + +The `process` object has the following method: + +### `process.hang` + +Causes the main thread of the current process hang. + +## process.setFdLimit(maxDescriptors) _OS X_ _Linux_ + +* `maxDescriptors` Integer + +Sets the file descriptor soft limit to `maxDescriptors` or the OS hard +limit, whichever is lower for the current process. diff --git a/docs/api/protocol.md b/docs/api/protocol.md index 65554e052105..795e4340c7ee 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -1,106 +1,166 @@ # protocol -The `protocol` module can register a new protocol or intercept an existing -protocol, so you can customize the response to the requests for various protocols. +The `protocol` module can register a custom protocol or intercept an existing +protocol. -An example of implementing a protocol that has the same effect with the +An example of implementing a protocol that has the same effect as the `file://` protocol: ```javascript -var app = require('app'), - path = require('path'); +var app = require('app'); +var path = require('path'); app.on('ready', function() { var protocol = require('protocol'); - protocol.registerProtocol('atom', function(request) { - var url = request.url.substr(7) - return new protocol.RequestFileJob(path.normalize(__dirname + '/' + url)); + 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:** This module can only be used after the `ready` event -was emitted. +**Note:** This module can only be used after the `ready` event in the `app` +module is emitted. -## protocol.registerProtocol(scheme, handler) +## Methods + +The `protocol` module has the following methods: + +### `protocol.registerStandardSchemes(schemes)` + +* `schemes` Array - Custom schemes to be registered as standard schemes. + +A standard `scheme` adheres to what RFC 3986 calls +[generic URI syntax](https://tools.ietf.org/html/rfc3986#section-3). This +includes `file:` and `filesystem:`. + +### `protocol.registerFileProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function +* `completion` Function (optional) -Registers a custom protocol of `scheme`, the `handler` would be called with -`handler(request)` when the a request with registered `scheme` is made. +Registers a protocol of `scheme` that will send the file as a response. The +`handler` will be called with `handler(request, callback)` when a `request` is +going to be created with `scheme`. `completion` will be called with +`completion(null)` when `scheme` is successfully registered or +`completion(error)` when failed. -You need to return a request job in the `handler` to specify which type of -response you would like to send. +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})`. -## protocol.unregisterProtocol(scheme) +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). + +By default the `scheme` is treated like `http:`, which is parsed differently +than protocols that follow the "generic URI syntax" like `file:`, so you +probably want to call `protocol.registerStandardSchemes` to have your scheme +treated as a standard scheme. + +### `protocol.registerBufferProtocol(scheme, handler[, completion])` * `scheme` String +* `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. + +Example: + +```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) + +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. + +### `protocol.registerHttpProtocol(scheme, handler[, completion])` + +* `scheme` String +* `handler` Function +* `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`, +`referer`, and `session` properties. + +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`. + +### `protocol.unregisterProtocol(scheme[, completion])` + +* `scheme` String +* `completion` Function (optional) Unregisters the custom protocol of `scheme`. -## protocol.isHandledProtocol(scheme) +### `protocol.isProtocolHandled(scheme, callback)` * `scheme` String +* `callback` Function -Returns whether the `scheme` can be handled already. +The `callback` will be called with a boolean that indicates whether there is +already a handler for `scheme`. -## protocol.interceptProtocol(scheme, handler) +### `protocol.interceptFileProtocol(scheme, handler[, completion])` * `scheme` String * `handler` Function +* `completion` Function (optional) -Intercepts an existing protocol with `scheme`, returning `null` or `undefined` -in `handler` would use the original protocol handler to handle the request. +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a file as a response. -## protocol.uninterceptProtocol(scheme) +### `protocol.interceptStringProtocol(scheme, handler[, completion])` * `scheme` String +* `handler` Function +* `completion` Function (optional) -Unintercepts a protocol. +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a `String` as a response. -## Class: protocol.RequestFileJob(path) +## `protocol.interceptBufferProtocol(scheme, handler[, completion])` -* `path` String +* `scheme` String +* `handler` Function +* `completion` Function (optional) -Create a request job which would query a file of `path` and set corresponding -mime types. +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a `Buffer` as a response. -## Class: protocol.RequestStringJob(options) +## `protocol.interceptHttpProtocol(scheme, handler[, completion])` -* `options` Object - * `mimeType` String - Default is `text/plain` - * `charset` String - Default is `UTF-8` - * `data` String +* `scheme` String +* `handler` Function +* `completion` Function (optional) -Create a request job which sends a string as response. +Intercepts `scheme` protocol and uses `handler` as the protocol's new handler +which sends a new HTTP request as a response. -## Class: protocol.RequestBufferJob(options) +## `protocol.uninterceptProtocol(scheme[, completion])` -* `options` Object - * `mimeType` String - Default is `application/octet-stream` - * `encoding` String - Default is `UTF-8` - * `data` Buffer +* `scheme` String +* `completion` Function -Create a request job which sends a buffer as response. - -## Class: protocol.RequestErrorJob(code) - -* `code` Integer - -Create a request job which sets appropriate network error message to console. -Default message is `net::ERR_NOT_IMPLEMENTED`. Code should be in the following -range. - -* Ranges: - * 0- 99 System related errors - * 100-199 Connection related errors - * 200-299 Certificate errors - * 300-399 HTTP errors - * 400-499 Cache errors - * 500-599 ? - * 600-699 FTP errors - * 700-799 Certificate manager errors - * 800-899 DNS resolver errors - -Check the [network error list](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h) for code and message relations. +Remove the interceptor installed for `scheme` and restore its original handler. diff --git a/docs/api/remote.md b/docs/api/remote.md index 89bbc1f0606f..55893c65f1c4 100644 --- a/docs/api/remote.md +++ b/docs/api/remote.md @@ -1,26 +1,29 @@ # remote The `remote` module provides a simple way to do inter-process communication -between the renderer process and the main process. +(IPC) between the renderer process (web page) and the main process. -In Electron, only GUI-related modules are available in the renderer process. -Without the `remote` module, users who wanted to call a main process API in -the renderer process would have to explicitly send inter-process messages -to the main process. With the `remote` module, users can invoke methods of -main process object without explicitly sending inter-process messages, -similar to Java's -[RMI](http://en.wikipedia.org/wiki/Java_remote_method_invocation). +In Electron, only GUI-unrelated modules are available in the renderer process. +Without the `remote` module, users who want to call a main process API in +the renderer process will have to explicitly send inter-process messages +to the main process. With the `remote` module, you can invoke methods of the +main process object without explicitly sending inter-process messages, similar +to Java's [RMI](http://en.wikipedia.org/wiki/Java_remote_method_invocation). -An example of creating a browser window in renderer process: +An example of creating a browser window from a renderer process: ```javascript var remote = require('remote'); var BrowserWindow = remote.require('browser-window'); + var win = new BrowserWindow({ width: 800, height: 600 }); win.loadUrl('https://github.com'); ``` -## Remote objects +**Note:** for the reverse (access the renderer process from the main process), +you can use [webContents.executeJavascript](web-contents.md#webcontentsexecutejavascriptcode-usergesture). + +## Remote Objects Each object (including functions) returned by the `remote` module represents an object in the main process (we call it a remote object or remote function). @@ -29,36 +32,69 @@ a new object with the remote constructor (function), you are actually sending synchronous inter-process messages. In the example above, both `BrowserWindow` and `win` were remote objects and -`new BrowserWindow` didn't create a `BrowserWindow` object in the renderer process. -Instead, it created a `BrowserWindow` object in the main process and returned the -corresponding remote object in the renderer process, namely the `win` object. +`new BrowserWindow` didn't create a `BrowserWindow` object in the renderer +process. Instead, it created a `BrowserWindow` object in the main process and +returned the corresponding remote object in the renderer process, namely the +`win` object. -## Lifetime of remote objects +## Lifetime of Remote Objects Electron makes sure that as long as the remote object in the renderer process lives (in other words, has not been garbage collected), the corresponding object -in the main process would never be released. When the remote object has been -garbage collected, the corresponding object in the main process would be +in the main process will not be released. When the remote object has been +garbage collected, the corresponding object in the main process will be dereferenced. -If the remote object is leaked in renderer process (e.g. stored in a map but never -freed), the corresponding object in the main process would also be leaked, +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. Primary value types like strings and numbers, however, are sent by copy. ## Passing callbacks to the main process -Some APIs in the main process accept callbacks, and it would be tempting to -pass callbacks when calling a remote function. The `remote` module does support -doing this, but you should also be extremely careful with this. +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, so you should not expect the main process to +are called asynchronously. You should not expect the main process to get the return value of the passed callbacks. -Second, the callbacks passed to the main process will not get released -automatically after they are called. Instead, they will persistent until the +For instance you can't use a function from the renderer process in an +`Array.map` called in the main process: + +```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] +``` + +As you can see, the renderer callback's synchronous return value was not as +expected, and didn't match the return value of an identical callback that lives +in the main process. + +Second, the callbacks passed to the main process will persist until the main process garbage-collects them. For example, the following code seems innocent at first glance. It installs a @@ -66,90 +102,52 @@ callback for the `close` event on a remote object: ```javascript var remote = require('remote'); + remote.getCurrentWindow().on('close', function() { // blabla... }); ``` -The problem is that the callback would be stored in the main process until you -explicitly uninstall it! So each time you reload your window, the callback would -be installed again and previous callbacks would just leak. To make things -worse, since the context of previously installed callbacks have been released, -when the `close` event was emitted, exceptions would be raised in the main process. +But remember the callback is referenced by the main process until you +explicitly uninstall it. If you do not, each time you reload your window the +callback will be installed again, leaking one callback for each restart. -Generally, unless you are clear what you are doing, you should always avoid -passing callbacks to the main process. +To make things worse, since the context of previously installed callbacks has +been released, exceptions will be raised in the main process when the `close` +event is emitted. -## Remote buffer +To avoid this problem, ensure you clean up any references to renderer callbacks +passed to the main process. This involves cleaning up event handlers, or +ensuring the main process is explicitly told to deference callbacks that came +from a renderer process that is exiting. -An instance of node's `Buffer` is an object, so when you get a `Buffer` from -the main process, what you get is indeed a remote object (let's call it remote -buffer), and everything would just follow the rules of remote objects. +## Methods -However you should remember that although a remote buffer behaves like the real -`Buffer`, it's not a `Buffer` at all. If you pass a remote buffer to node APIs -that accept a `Buffer`, you should assume the remote buffer would be treated -like a normal object, instead of a `Buffer`. +The `remote` module has the following methods: -For example, you can call `BrowserWindow.capturePage` in the renderer process, which -returns a `Buffer` by calling the passed callback: - -```javascript -var remote = require('remote'); -var fs = require('fs'); -remote.getCurrentWindow().capturePage(function(buf) { - fs.writeFile('/tmp/screenshot.png', buf, function(err) { - console.log(err); - }); -}); -``` - -But you may be surprised to find that the file written was corrupted. This is -because when you called `fs.writeFile`, thinking that `buf` was a `Buffer` when -in fact it was a remote buffer, and it was converted to string before it was -written to the file. Since `buf` contained binary data and could not be represented -by a UTF-8 encoded string, the written file was corrupted. - -The work-around is to write the `buf` in the main process, where it is a real -`Buffer`: - -```javascript -var remote = require('remote'); -remote.getCurrentWindow().capturePage(function(buf) { - remote.require('fs').writeFile('/tmp/screenshot.png', buf, function(err) { - console.log(err); - }); -}); -``` - -The same thing could happen for all native types, but usually it would just -throw a type error. The `Buffer` deserves your special attention because it -might be converted to string, and APIs accepting `Buffer` usually accept string -too, and data corruption could happen when it contains binary data. - -## remote.require(module) +### `remote.require(module)` * `module` String Returns the object returned by `require(module)` in the main process. -## remote.getCurrentWindow() +### `remote.getCurrentWindow()` -Returns the [BrowserWindow](browser-window.md) object which this web page -belongs to. +Returns the [`BrowserWindow`](browser-window.md) object to which this web page +belongs. -## remote.getCurrentWebContent() +### `remote.getCurrentWebContents()` -Returns the WebContents object of this web page. +Returns the [`WebContents`](web-contents.md) object of this web page. -## remote.getGlobal(name) +### `remote.getGlobal(name)` * `name` String Returns the global variable of `name` (e.g. `global[name]`) in the main process. -## remote.process +### `remote.process` Returns the `process` object in the main process. This is the same as -`remote.getGlobal('process')`, but gets cached. +`remote.getGlobal('process')` but is cached. diff --git a/docs/api/screen.md b/docs/api/screen.md index 8b3de4abfb46..934e3eaf5a70 100644 --- a/docs/api/screen.md +++ b/docs/api/screen.md @@ -1,11 +1,14 @@ # screen -Gets various info about screen size, displays, cursor position, etc. You should -not use this module until the `ready` event of `app` module gets emitted. +The `screen` module retrieves information about screen size, displays, cursor +position, etc. You should not use this module until the `ready` event of the +`app` module is emitted. `screen` is an [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). -Make sure to note that in the renderer / DevTools, `window.screen` is a reserved DOM property, so writing `screen = require('screen')` won't work. In our examples below, we use `atomScreen` as the variable name instead. +**Note:** In the renderer / DevTools, `window.screen` is a reserved +DOM property, so writing `var screen = require('screen')` will not work. In our +examples below, we use `atomScreen` as the variable name instead. An example of creating a window that fills the whole screen: @@ -50,43 +53,57 @@ app.on('ready', function() { }); ``` -## Event: display-added +## Events + +The `screen` module emits the following events: + +### Event: 'display-added' + +Returns: * `event` Event * `newDisplay` Object Emitted when `newDisplay` has been added. -## Event: display-removed +### Event: 'display-removed' + +Returns: * `event` Event * `oldDisplay` Object Emitted when `oldDisplay` has been removed. -## Event: display-metrics-changed +### Event: 'display-metrics-changed' + +Returns: * `event` Event * `display` Object -* `changedMetricts` Array +* `changedMetrics` Array -Emitted when a `display` has one or more metrics changed, `changedMetricts` is +Emitted when one or more metrics change in a `display`. The `changedMetrics` is an array of strings that describe the changes. Possible changes are `bounds`, `workArea`, `scaleFactor` and `rotation`. -## screen.getCursorScreenPoint() +## Methods + +The `screen` module has the following methods: + +### `screen.getCursorScreenPoint()` Returns the current absolute position of the mouse pointer. -## screen.getPrimaryDisplay() +### `screen.getPrimaryDisplay()` Returns the primary display. -## screen.getAllDisplays() +### `screen.getAllDisplays()` Returns an array of displays that are currently available. -## screen.getDisplayNearestPoint(point) +### `screen.getDisplayNearestPoint(point)` * `point` Object * `x` Integer @@ -94,7 +111,7 @@ Returns an array of displays that are currently available. Returns the display nearest the specified point. -## screen.getDisplayMatching(rect) +### `screen.getDisplayMatching(rect)` * `rect` Object * `x` Integer diff --git a/docs/api/session.md b/docs/api/session.md new file mode 100644 index 000000000000..439cf8514e93 --- /dev/null +++ b/docs/api/session.md @@ -0,0 +1,197 @@ +# session + +The `session` object is a property of [`webContents`](web-contents.md) which is +a property of [`BrowserWindow`](browser-window.md). You can access it through an +instance of `BrowserWindow`. For example: + +```javascript +var BrowserWindow = require('browser-window'); + +var win = new BrowserWindow({ width: 800, height: 600 }); +win.loadUrl("http://github.com"); + +var session = win.webContents.session +``` + +## Events + +### Event: 'will-download' + +* `event` Event +* `item` Object + * `url` String + * `filename` String + * `mimeType` String + * `hasUserGesture` Boolean +* `webContents` [WebContents](web-contents.md) + +Fired when Electron is about to download `item` in `webContents`. + +Calling `event.preventDefault()` will cancel the download. + +```javascript +session.on('will-download', function(event, item, webContents) { + event.preventDefault(); + require('request')(item.url, function(data) { + require('fs').writeFileSync('/somewhere', data); + }); +}); +``` + +## Methods + +The `session` object has the following methods: + +### `session.cookies` + +The `cookies` gives you ability to query and modify cookies. For example: + +```javascript +var BrowserWindow = require('browser-window'); + +var win = new BrowserWindow({ width: 800, height: 600 }); + +win.loadUrl('https://github.com'); + +win.webContents.on('did-finish-load', function() { + // Query all cookies. + win.webContents.session.cookies.get({}, function(error, cookies) { + if (error) throw error; + console.log(cookies); + }); + + // Query all cookies associated with a specific url. + win.webContents.session.cookies.get({ url : "http://www.github.com" }, + function(error, cookies) { + if (error) throw error; + console.log(cookies); + }); + + // Set a cookie with the given cookie data; + // may overwrite equivalent cookies if they exist. + win.webContents.session.cookies.set( + { url : "http://www.github.com", name : "dummy_name", value : "dummy"}, + function(error, cookies) { + if (error) throw error; + console.log(cookies); + }); +}); +``` + +### `session.cookies.get(details, callback)` + +`details` Object, properties: + +* `url` String - Retrieves cookies which are associated with `url`. + Empty implies retrieving cookies of all urls. +* `name` String - Filters cookies by name +* `domain` String - Retrieves cookies whose domains match or are subdomains of + `domains` +* `path` String - Retrieves cookies whose path matches `path` +* `secure` Boolean - Filters cookies by their Secure property +* `session` Boolean - Filters out session or persistent cookies. +* `callback` Function - function(error, cookies) +* `error` Error +* `cookies` Array - array of `cookie` objects, properties: + * `name` String - The name of the cookie. + * `value` String - The value of the cookie. + * `domain` String - The domain of the cookie. + * `host_only` String - Whether the cookie is a host-only cookie. + * `path` String - The path of the cookie. + * `secure` Boolean - Whether the cookie is marked as Secure (typically HTTPS). + * `http_only` Boolean - Whether the cookie is marked as HttpOnly. + * `session` Boolean - Whether the cookie is a session cookie or a persistent + cookie with an expiration date. + * `expirationDate` Double - (Option) The expiration date of the cookie as + the number of seconds since the UNIX epoch. Not provided for session + cookies. + +### `session.cookies.set(details, callback)` + +`details` Object, properties: + +* `url` String - Retrieves cookies which are associated with `url` +* `name` String - The name of the cookie. Empty by default if omitted. +* `value` String - The value of the cookie. Empty by default if omitted. +* `domain` String - The domain of the cookie. Empty by default if omitted. +* `path` String - The path of the cookie. Empty by default if omitted. +* `secure` Boolean - Whether the cookie should be marked as Secure. Defaults to + false. +* `session` Boolean - Whether the cookie should be marked as HttpOnly. Defaults + to false. +* `expirationDate` Double - The expiration date of the cookie as the number of + seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie. + +* `callback` Function - function(error) + * `error` Error + +### `session.cookies.remove(details, callback)` + +* `details` Object, proprties: + * `url` String - The URL associated with the cookie + * `name` String - The name of cookie to remove +* `callback` Function - function(error) + * `error` Error + +### `session.clearCache(callback)` + +* `callback` Function - Called when operation is done + +Clears the session’s HTTP cache. + +### `session.clearStorageData([options, ]callback)` + +* `options` Object (optional), proprties: + * `origin` String - Should follow `window.location.origin`’s representation + `scheme://host:port`. + * `storages` Array - The types of storages to clear, can contain: + `appcache`, `cookies`, `filesystem`, `indexdb`, `local storage`, + `shadercache`, `websql`, `serviceworkers` + * `quotas` Array - The types of quotas to clear, can contain: + `temporary`, `persistent`, `syncable`. +* `callback` Function - Called when operation is done. + +Clears the data of web storages. + +### `session.setProxy(config, callback)` + +* `config` String +* `callback` Function - Called when operation is done. + +Parses the `config` indicating which proxies to use for the session. + +``` +config = scheme-proxies[";"] +scheme-proxies = ["="] +url-scheme = "http" | "https" | "ftp" | "socks" +proxy-uri-list = [","] +proxy-uri = ["://"][":"] + + 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. + "foopy:80,bar,direct://" -- use HTTP proxy "foopy:80" for all URLs, + failing over to "bar" if "foopy:80" is + unavailable, and after that using no + proxy. + "socks4://foopy" -- use SOCKS v4 proxy "foopy:1080" for all + URLs. + "http=foopy,socks5://bar.com -- use HTTP proxy "foopy" for http URLs, + and fail over to the SOCKS5 proxy + "bar.com" if "foopy" is unavailable. + "http=foopy,direct:// -- use HTTP proxy "foopy" for http URLs, + and use no proxy if "foopy" is + unavailable. + "http=foopy;socks=foopy2 -- use HTTP proxy "foopy" for http URLs, + and use socks4://foopy2 for all other + URLs. +``` + +### `session.setDownloadPath(path)` + +* `path` String - The download location + +Sets download saving directory. By default, the download directory will be the +`Downloads` under the respective app folder. diff --git a/docs/api/shell.md b/docs/api/shell.md index f0848875a975..662fa588f3f0 100644 --- a/docs/api/shell.md +++ b/docs/api/shell.md @@ -2,38 +2,43 @@ The `shell` module provides functions related to desktop integration. -An example of opening a URL in default browser: +An example of opening a URL in the user's default browser: ```javascript var shell = require('shell'); + shell.openExternal('https://github.com'); ``` -## shell.showItemInFolder(fullPath) +## Methods + +The `shell` module has the following methods: + +### `shell.showItemInFolder(fullPath)` * `fullPath` String Show the given file in a file manager. If possible, select the file. -## shell.openItem(fullPath) +### `shell.openItem(fullPath)` * `fullPath` String Open the given file in the desktop's default manner. -## shell.openExternal(url) +### `shell.openExternal(url)` * `url` String Open the given external protocol URL in the desktop's default manner. (For -example, mailto: URLs in the default mail user agent.) +example, mailto: URLs in the user's default mail agent.) -## shell.moveItemToTrash(fullPath) +### `shell.moveItemToTrash(fullPath)` * `fullPath` String -Move the given file to trash and returns boolean status for the operation. +Move the given file to trash and returns a boolean status for the operation. -## shell.beep() +### `shell.beep()` Play the beep sound. diff --git a/docs/api/synopsis.md b/docs/api/synopsis.md index 7dcd53363a29..85d9e3f8dfa6 100644 --- a/docs/api/synopsis.md +++ b/docs/api/synopsis.md @@ -1,18 +1,22 @@ # Synopsis -All [node.js's built-in modules](http://nodejs.org/api/) are available in -Electron, and third-party node modules are fully supported too (including the -[native modules](../tutorial/using-native-node-modules.md)). +All of [Node.js's built-in modules](http://nodejs.org/api/) are available in +Electron and third-party node modules also fully supported as well (including +the [native modules](../tutorial/using-native-node-modules.md)). Electron also provides some extra built-in modules for developing native -desktop applications. Some modules are only available on the main process, some -are only available on the renderer process, and some can be used on both processes. -The basic rule is: if a module is GUI or low-level system related, then it should -be only available on the main process. You need to be familiar with the concept of +desktop applications. Some modules are only available in the main process, some +are only available in the renderer process (web page), and some can be used in +both processes. + +The basic rule is: if a module is +[GUI](https://en.wikipedia.org/wiki/Graphical_user_interface) or low-level +system related, then it should be only available in the main process. You need +to be familiar with the concept of [main process vs. renderer process](../tutorial/quick-start.md#the-main-process) scripts to be able to use those modules. -The main process script is just like a normal `node.js` script: +The main process script is just like a normal Node.js script: ```javascript var app = require('app'); @@ -26,7 +30,7 @@ app.on('ready', function() { }); ``` -The web page is no different than a normal web page, except for the extra +The renderer process is no different than a normal web page, except for the extra ability to use node modules: ```html diff --git a/docs/api/tray.md b/docs/api/tray.md index 0562a6180e46..528705acb325 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -1,6 +1,6 @@ -# tray +# Tray -A `Tray` represents an icon in operating system's notification area, it is +A `Tray` represents an icon in an operating system's notification area, it is usually attached with a context menu. ```javascript @@ -15,7 +15,7 @@ app.on('ready', function(){ { label: 'Item1', type: 'radio' }, { label: 'Item2', type: 'radio' }, { label: 'Item3', type: 'radio', checked: true }, - { label: 'Item4', type: 'radio' }, + { label: 'Item4', type: 'radio' } ]); appIcon.setToolTip('This is my application.'); appIcon.setContextMenu(contextMenu); @@ -25,29 +25,41 @@ app.on('ready', function(){ __Platform limitations:__ -* On OS X `clicked` event will be ignored if the tray icon has context menu. -* On Linux app indicator will be used if it is supported, otherwise +* On Linux the app indicator will be used if it is supported, otherwise `GtkStatusIcon` will be used instead. -* App indicator will only be showed when it has context menu. -* When app indicator is used on Linux, `clicked` event is ignored. +* On Linux distributions that only have app indicator support, you have to + 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 `clicked` event is ignored. -So if you want to keep exact same behaviors on all platforms, you should not -rely on `clicked` event and always attach a context menu to the tray icon. +If you want to keep exact same behaviors on all platforms, you should not +rely on the `clicked` event and always attach a context menu to the tray icon. ## Class: Tray `Tray` is an [EventEmitter][event-emitter]. -### new Tray(image) +### `new Tray(image)` * `image` [NativeImage](native-image.md) Creates a new tray icon associated with the `image`. +## Events + +The `Tray` module emits the following events: + +**Note:** Some events are only available on specific operating systems and are +labeled as such. + ### Event: 'clicked' -* `event` -* `bounds` Object - the bounds of tray icon +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - the bounds of tray icon. * `x` Integer * `y` Integer * `width` Integer @@ -55,72 +67,100 @@ Creates a new tray icon associated with the `image`. Emitted when the tray icon is clicked. -__NOte:__ The `bounds` payload is only implemented on OS X. +__Note:__ The `bounds` payload is only implemented on OS X and Windows. -### Event: 'double-clicked' +### Event: 'right-clicked' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - the bounds of tray icon. + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +Emitted when the tray icon is right clicked. + +### Event: 'double-clicked' _OS X_ _Windows_ + +* `event` Event + * `altKey` Boolean + * `shiftKey` Boolean + * `ctrlKey` Boolean + * `metaKey` Boolean +* `bounds` Object - the bounds of tray icon + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer Emitted when the tray icon is double clicked. -__Note:__ This is only implemented on OS X. - -### Event: 'balloon-show' +### Event: 'balloon-show' _Windows_ Emitted when the tray balloon shows. -__Note:__ This is only implemented on Windows. - -### Event: 'balloon-clicked' +### Event: 'balloon-clicked' _Windows_ Emitted when the tray balloon is clicked. -__Note:__ This is only implemented on Windows. - -### Event: 'balloon-closed' +### Event: 'balloon-closed' _Windows_ Emitted when the tray balloon is closed because of timeout or user manually closes it. -__Note:__ This is only implemented on Windows. +### Event: 'drop-files' _OS X_ -### Tray.destroy() +* `event` +* `files` Array - the file path of dropped files. + +Emitted when dragged files are dropped in the tray icon. + +## Methods + +The `Tray` module has the following methods: + +**Note:** Some methods are only available on specific operating systems and are +labeled as such. + +### `Tray.destroy()` Destroys the tray icon immediately. -### Tray.setImage(image) +### `Tray.setImage(image)` * `image` [NativeImage](native-image.md) Sets the `image` associated with this tray icon. -### Tray.setPressedImage(image) +### `Tray.setPressedImage(image)` _OS X_ * `image` [NativeImage](native-image.md) -Sets the `image` associated with this tray icon when pressed. +Sets the `image` associated with this tray icon when pressed on OS X. -### Tray.setToolTip(toolTip) +### `Tray.setToolTip(toolTip)` * `toolTip` String Sets the hover text for this tray icon. -### Tray.setTitle(title) +### `Tray.setTitle(title)` _OS X_ * `title` String Sets the title displayed aside of the tray icon in the status bar. -__Note:__ This is only implemented on OS X. - -### Tray.setHighlightMode(highlight) +### `Tray.setHighlightMode(highlight)` _OS X_ * `highlight` Boolean Sets whether the tray icon is highlighted when it is clicked. -__Note:__ This is only implemented on OS X. - -### Tray.displayBalloon(options) +### `Tray.displayBalloon(options)` _Windows_ * `options` Object * `icon` [NativeImage](native-image.md) @@ -129,9 +169,15 @@ __Note:__ This is only implemented on OS X. Displays a tray balloon. -__Note:__ This is only implemented on Windows. +### `Tray.popUpContextMenu([position])` _OS X_ _Windows_ -### Tray.setContextMenu(menu) +* `position` Object (optional)- The pop up position. + * `x` Integer + * `y` Integer + +The `position` is only available on Windows, and it is (0, 0) by default. + +### `Tray.setContextMenu(menu)` * `menu` Menu diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md new file mode 100644 index 000000000000..e8f72cd24bf2 --- /dev/null +++ b/docs/api/web-contents.md @@ -0,0 +1,567 @@ +# webContents + +`webContents` is an +[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter). + +It is responsible for rendering and controlling a web page and is a property of +the [`BrowserWindow`](browser-window.md) object. An example of accessing the +`webContents` object: + +```javascript +var BrowserWindow = require('browser-window'); + +var win = new BrowserWindow({width: 800, height: 1500}); +win.loadUrl("http://github.com"); + +var webContents = win.webContents; +``` + +## Events + +The `webContents` object emits the following events: + +### Event: 'did-finish-load' + +Emitted when the navigation is done, i.e. the spinner of the tab has stopped +spinning, and the `onload` event was dispatched. + +### Event: 'did-fail-load' + +Returns: + +* `event` Event +* `errorCode` Integer +* `errorDescription` String +* `validatedUrl` String + +This event is like `did-finish-load` but emitted when the load failed or was +cancelled, e.g. `window.stop()` is invoked. + +### Event: 'did-frame-finish-load' + +Returns: + +* `event` Event +* `isMainFrame` Boolean + +Emitted when a frame has done navigation. + +### Event: 'did-start-loading' + +Corresponds to the points in time when the spinner of the tab started spinning. + +### Event: 'did-stop-loading' + +Corresponds to the points in time when the spinner of the tab stopped spinning. + +### Event: 'did-get-response-details' + +Returns: + +* `event` Event +* `status` Boolean +* `newUrl` String +* `originalUrl` String +* `httpResponseCode` Integer +* `requestMethod` String +* `referrer` String +* `headers` Object + +Emitted when details regarding a requested resource are available. +`status` indicates the socket connection to download the resource. + +### Event: 'did-get-redirect-request' + +Returns: + +* `event` Event +* `oldUrl` String +* `newUrl` String +* `isMainFrame` Boolean + +Emitted when a redirect is received while requesting a resource. + +### Event: 'dom-ready' + +Returns: + +* `event` Event + +Emitted when the document in the given frame is loaded. + +### Event: 'page-favicon-updated' + +Returns: + +* `event` Event +* `favicons` Array - Array of Urls + +Emitted when page receives favicon urls. + +### Event: 'new-window' + +Returns: + +* `event` Event +* `url` String +* `frameName` String +* `disposition` String - Can be `default`, `foreground-tab`, `background-tab`, + `new-window` and `other`. + +Emitted when the page requests to open a new window for a `url`. It could be +requested by `window.open` or an external link like `
`. + +By default a new `BrowserWindow` will be created for the `url`. + +Calling `event.preventDefault()` will prevent creating new windows. + +### Event: 'will-navigate' + +Returns: + +* `event` Event +* `url` String + +Emitted when a user or the page wants to start navigation. It can happen when the +`window.location` object is changed or a user clicks a link in the page. + +This event will not emit when the navigation is started programmatically with +APIs like `webContents.loadUrl` and `webContents.back`. + +Calling `event.preventDefault()` will prevent the navigation. + +### Event: 'crashed' + +Emitted when the renderer process has crashed. + +### Event: 'plugin-crashed' + +Returns: + +* `event` Event +* `name` String +* `version` String + +Emitted when a plugin process has crashed. + +### Event: 'destroyed' + +Emitted when `webContents` is destroyed. + +## Instance Methods + +The `webContents` object has the following instance methods: + +### `webContents.session` + +Returns the `session` object used by this webContents. + +See [session documentation](session.md) for this object's methods. + +### `webContents.loadUrl(url[, options])` + +* `url` URL +* `options` Object (optional), properties: + * `httpReferrer` String - A HTTP Referrer url. + * `userAgent` String - A user agent originating the request. + +Loads the `url` in the window, the `url` must contain the protocol prefix, +e.g. the `http://` or `file://`. + +### `webContents.getUrl()` + +```javascript +var BrowserWindow = require('browser-window'); + +var win = new BrowserWindow({width: 800, height: 600}); +win.loadUrl("http://github.com"); + +var currentUrl = win.webContents.getUrl(); +``` + +Returns URL of the current web page. + +### `webContents.getTitle()` + +Returns the title of the current web page. + +### `webContents.isLoading()` + +Returns whether web page is still loading resources. + +### `webContents.isWaitingForResponse()` + +Returns whether the web page is waiting for a first-response from the main +resource of the page. + +### `webContents.stop()` + +Stops any pending navigation. + +### `webContents.reload()` + +Reloads the current web page. + +### `webContents.reloadIgnoringCache()` + +Reloads current page and ignores cache. + +### `webContents.canGoBack()` + +Returns whether the browser can go back to previous web page. + +### `webContents.canGoForward()` + +Returns whether the browser can go forward to next web page. + +### `webContents.canGoToOffset(offset)` + +* `offset` Integer + +Returns whether the web page can go to `offset`. + +### `webContents.clearHistory()` + +Clears the navigation history. + +### `webContents.goBack()` + +Makes the browser go back a web page. + +### `webContents.goForward()` + +Makes the browser go forward a web page. + +### `webContents.goToIndex(index)` + +* `index` Integer + +Navigates browser to the specified absolute web page index. + +### `webContents.goToOffset(offset)` + +* `offset` Integer + +Navigates to the specified offset from the "current entry". + +### `webContents.isCrashed()` + +Whether the renderer process has crashed. + +### `webContents.setUserAgent(userAgent)` + +* `userAgent` String + +Overrides the user agent for this web page. + +### `webContents.getUserAgent()` + +Returns a `String` representing the user agent for this web page. + +### `webContents.insertCSS(css)` + +* `css` String + +Injects CSS into the current web page. + +### `webContents.executeJavaScript(code[, userGesture])` + +* `code` String +* `userGesture` Boolean (optional) + +Evaluates `code` in page. + +In the browser window some HTML APIs like `requestFullScreen` can only be +invoked by a gesture from the user. Setting `userGesture` to `true` will remove +this limitation. + +### `webContents.setAudioMuted(muted)` + ++ `muted` Boolean + +Mute the audio on the current web page. + +### `webContents.isAudioMuted()` + +Returns whether this page has been muted. + +### `webContents.undo()` + +Executes the editing command `undo` in web page. + +### `webContents.redo()` + +Executes the editing command `redo` in web page. + +### `webContents.cut()` + +Executes the editing command `cut` in web page. + +### `webContents.copy()` + +Executes the editing command `copy` in web page. + +### `webContents.paste()` + +Executes the editing command `paste` in web page. + +### `webContents.pasteAndMatchStyle()` + +Executes the editing command `pasteAndMatchStyle` in web page. + +### `webContents.delete()` + +Executes the editing command `delete` in web page. + +### `webContents.selectAll()` + +Executes the editing command `selectAll` in web page. + +### `webContents.unselect()` + +Executes the editing command `unselect` in web page. + +### `webContents.replace(text)` + +* `text` String + +Executes the editing command `replace` in web page. + +### `webContents.replaceMisspelling(text)` + +* `text` String + +Executes the editing command `replaceMisspelling` in web page. + +### `webContents.hasServiceWorker(callback)` + +* `callback` Function + +Checks if any ServiceWorker is registered and returns a boolean as +response to `callback`. + +### `webContents.unregisterServiceWorker(callback)` + +* `callback` Function + +Unregisters any ServiceWorker if present and returns a boolean as +response to `callback` when the JS promise is fulfilled or false +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`. + +Prints window's web page. When `silent` is set to `false`, Electron will pick +up system's default printer and default settings for printing. + +Calling `window.print()` in web page is equivalent to calling +`webContents.print({silent: false, printBackground: false})`. + +**Note:** On Windows, the print API relies on `pdf.dll`. If your application +doesn't need the print feature, you can safely remove `pdf.dll` to reduce binary +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. + * `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. + +Prints window's web page as PDF with Chromium's preview printing custom +settings. + +By default, an empty `options` will be regarded as: + +```javascript +{ + marginsType: 0, + printBackground: false, + printSelectionOnly: false, + landscape: false +} +``` + +```javascript +var BrowserWindow = require('browser-window'); +var 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 (err) + throw error; + console.log("Write PDF successfully."); + }) + }) +}); +``` + +### `webContents.addWorkSpace(path)` + +* `path` String + +Adds the specified path to DevTools workspace. + +### `webContents.removeWorkSpace(path)` + +* `path` String + +Removes the specified path from DevTools workspace. + +### `webContents.send(channel[, args...])` + +* `channel` String +* `args...` (optional) + +Send `args...` to the web page via `channel` in an asynchronous message, the web +page can handle it by listening to the `channel` event of the `ipc` module. + +An example of sending messages from the main process to the renderer process: + +```javascript +// In the main process. +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 + + + + + + +``` + +**Note:** + +1. The IPC message handler in web pages does not have an `event` parameter, + which is different from the handlers in the main process. +2. There is no way to send synchronous messages from the main process to a + renderer process, because it would be very easy to cause dead locks. + +### `webContents.enableDeviceEmulation(parameters)` + +`parameters` Object, properties: + +* `screenPosition` String - Specify the screen type to emulate + (default: `desktop`) + * `desktop` + * `mobile` +* `screenSize` Object - Set the emulated screen size (screenPosition == mobile) + * `width` Integer - Set the emulated screen width + * `height` Integer - Set the emulated screen height +* `viewPosition` Object - Position the view on the screen + (screenPosition == mobile) (default: `{x: 0, y: 0}`) + * `x` Integer - Set the x axis offset from top left corner + * `y` Integer - Set the y axis offset from top left corner +* `deviceScaleFactor` Integer - Set the device scale factor (if zero defaults to + original device scale factor) (default: `0`) +* `viewSize` Object - Set the emulated view size (empty means no override) + * `width` Integer - Set the emulated view width + * `height` Integer - Set the emulated view height +* `fitToView` Boolean - Whether emulated view should be scaled down if + necessary to fit into available space (default: `false`) +* `offset` Object - Offset of the emulated view inside available space (not in + fit to view mode) (default: `{x: 0, y: 0}`) + * `x` Float - Set the x axis offset from top left corner + * `y` Float - Set the y axis offset from top left corner +* `scale` Float - Scale of emulated view inside available space (not in fit to + view mode) (default: `1`) + +Enable device emulation with the given parameters. + +### `webContents.disableDeviceEmulation()` + +Disable device emulation enabled by `webContents.enableDeviceEmulation`. + +### `webContents.sendInputEvent(event)` + +* `event` Object + * `type` String (**required**) - The type of the event, can be `mouseDown`, + `mouseUp`, `mouseEnter`, `mouseLeave`, `contextMenu`, `mouseWheel`, + `keyDown`, `keyUp`, `char`. + * `modifiers` Array - An array of modifiers of the event, can + include `shift`, `control`, `alt`, `meta`, `isKeypad`, `isAutoRepeat`, + `leftButtonDown`, `middleButtonDown`, `rightButtonDown`, `capsLock`, + `numLock`, `left`, `right`. + +Sends an input `event` to the page. + +For keyboard events, the `event` object also have following properties: + +* `keyCode` String (**required**) - A single character that will be sent as + keyboard event. Can be any ASCII character on the keyboard, like `a`, `1` + and `=`. + +For mouse events, the `event` object also have following properties: + +* `x` Integer (**required**) +* `y` Integer (**required**) +* `globalX` Integer +* `globalY` Integer +* `movementX` Integer +* `movementY` Integer +* `clickCount` Integer + +For the `mouseWheel` event, the `event` object also have following properties: + +* `deltaX` Integer +* `deltaY` Integer +* `wheelTicksX` Integer +* `wheelTicksY` Integer +* `accelerationRatioX` Integer +* `accelerationRatioY` Integer +* `hasPreciseScrollingDeltas` Boolean +* `canScroll` Boolean + +### `webContents.beginFrameSubscription(callback)` + +* `callback` Function + +Begin subscribing for presentation events and captured frames, the `callback` +will be called with `callback(frameBuffer)` when there is a presentation event. + +The `frameBuffer` is a `Buffer` that contains raw pixel data, in the format of +32bit ARGB. + +### `webContents.endFrameSubscription()` + +End subscribing for frame presentation events. diff --git a/docs/api/web-frame.md b/docs/api/web-frame.md index 73b9ee50577f..bdf42da3003a 100644 --- a/docs/api/web-frame.md +++ b/docs/api/web-frame.md @@ -1,38 +1,51 @@ -# web-frame +# webFrame -The `web-frame` module can custom the rendering of current web page. +The `web-frame` module allows you to customize the rendering of the current +web page. An example of zooming current page to 200%. ```javascript var webFrame = require('web-frame'); + webFrame.setZoomFactor(2); ``` -## webFrame.setZoomFactor(factor) +## Methods -* `factor` Number - Zoom factor +The `web-frame` module has the following methods: -Changes the zoom factor to the specified factor, zoom factor is -zoom percent / 100, so 300% = 3.0. +### `webFrame.setZoomFactor(factor)` -## webFrame.getZoomFactor() +* `factor` Number - Zoom factor. + +Changes the zoom factor to the specified factor. Zoom factor is +zoom percent divided by 100, so 300% = 3.0. + +### `webFrame.getZoomFactor()` Returns the current zoom factor. -## webFrame.setZoomLevel(level) +### `webFrame.setZoomLevel(level)` * `level` Number - Zoom level -Changes the zoom level to the specified level, 0 is "original size", and each +Changes the zoom level to the specified level. The original size is 0 and each increment above or below represents zooming 20% larger or smaller to default limits of 300% and 50% of original size, respectively. -## webFrame.getZoomLevel() +### `webFrame.getZoomLevel()` Returns the current zoom level. -## webFrame.setSpellCheckProvider(language, autoCorrectWord, provider) +### `webFrame.setZoomLevelLimits(minimumLevel, maximumLevel)` + +* `minimumLevel` Number +* `maximumLevel` Number + +Sets the maximum and minimum zoom level. + +### `webFrame.setSpellCheckProvider(language, autoCorrectWord, provider)` * `language` String * `autoCorrectWord` Boolean @@ -53,14 +66,21 @@ require('web-frame').setSpellCheckProvider("en-US", true, { }); ``` -## webFrame.registerUrlSchemeAsSecure(scheme) +### `webFrame.registerUrlSchemeAsSecure(scheme)` * `scheme` String -Sets the `scheme` as secure scheme. +Registers the `scheme` as secure scheme. Secure schemes do not trigger mixed content warnings. For example, `https` and `data` are secure schemes because they cannot be corrupted by active network attackers. +### `webFrame.registerUrlSchemeAsBypassingCsp(scheme)` + +* `scheme` String + +Resources will be loaded from this `scheme` regardless of the current page's +Content Security Policy. + [spellchecker]: https://github.com/atom/node-spellchecker diff --git a/docs/api/web-view-tag.md b/docs/api/web-view-tag.md index 89ae678b3c46..8eb3857ac970 100644 --- a/docs/api/web-view-tag.md +++ b/docs/api/web-view-tag.md @@ -1,12 +1,12 @@ -# `` tag +# The `` tag Use the `webview` tag to embed 'guest' content (such as web pages) in your -Electron app. The guest content is contained within the `webview` container; -an embedder page within your app controls how the guest content is laid out and +Electron app. The guest content is contained within the `webview` container. +An embedded page within your app controls how the guest content is laid out and rendered. -Different from the `iframe`, the `webview` runs in a separate process than your -app; it doesn't have the same permissions as your web page and all interactions +Unlike an `iframe`, the `webview` runs in a separate process than your +app. It doesn't have the same permissions as your web page and all interactions between your app and embedded content will be asynchronous. This keeps your app safe from the embedded content. @@ -18,7 +18,7 @@ form, the `webview` tag includes the `src` of the web page and css styles that control the appearance of the `webview` container: ```html - + ``` If you want to control the guest content in any way, you can write JavaScript @@ -45,9 +45,11 @@ and displays a "loading..." message during the load time: ``` -## Tag attributes +## Tag Attributes -### src +The `webview` tag has the following attributes: + +### `src` ```html @@ -61,7 +63,7 @@ Assigning `src` its own value will reload the current page. The `src` attribute can also accept data URLs, such as `data:text/plain,Hello, world!`. -### autosize +### `autosize` ```html @@ -69,11 +71,11 @@ The `src` attribute can also accept data URLs, such as If "on", the `webview` container will automatically resize within the bounds specified by the attributes `minwidth`, `minheight`, `maxwidth`, and -`maxheight`. These contraints do not impact the `webview` UNLESS `autosize` is +`maxheight`. These constraints do not impact the `webview` unless `autosize` is enabled. When `autosize` is enabled, the `webview` container size cannot be less than the minimum values or greater than the maximum. -### nodeintegration +### `nodeintegration` ```html @@ -82,7 +84,7 @@ 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. -### plugins +### `plugins` ```html @@ -90,7 +92,7 @@ APIs like `require` and `process` to access low level system resources. If "on", the guest page in `webview` will be able to use browser plugins. -### preload +### `preload` ```html @@ -102,9 +104,9 @@ will be loaded by `require` in guest page under the hood. When the guest page doesn't have node integration this script will still have access to all Node APIs, but global objects injected by Node will be deleted -after this script has done execution. +after this script has finished executing. -### httpreferrer +### `httpreferrer` ```html @@ -112,7 +114,16 @@ after this script has done execution. Sets the referrer URL for the guest page. -### disablewebsecurity +### `useragent` + +```html + +``` + +Sets the user agent for the guest page before the page is navigated to. Once the +page is loaded, use the `setUserAgent` method to change the user agent. + +### `disablewebsecurity` ```html @@ -120,196 +131,292 @@ Sets the referrer URL for the guest page. If "on", the guest page will have web security disabled. +### partition + +```html + + +``` + +Sets the session used by the page. If `partition` starts with `persist:`, the +page will use a persistent session available to all pages in the app with the +same `partition`. if there is no `persist:` prefix, the page will use an +in-memory session. By assigning the same `partition`, multiple pages can share +the same session. If the `partition` is unset then default session of the app +will be used. + +This value can only be modified before the first navigation, since the session +of an active renderer process cannot change. Subsequent attempts to modify the +value will fail with a DOM exception. + ## Methods -### ``.getUrl() +The `webview` tag has the following methods: + +**Note:** The webview element must be loaded before using the methods. + +**Example** +```javascript +webview.addEventListener("dom-ready", function() { + webview.openDevTools(); +}); +``` + +### `.getUrl()` Returns URL of guest page. -### ``.getTitle() +### `.getTitle()` Returns the title of guest page. -### ``.isLoading() +### `.isLoading()` -Returns whether guest page is still loading resources. +Returns a boolean whether guest page is still loading resources. -### ``.isWaitingForResponse() +### `.isWaitingForResponse()` -Returns whether guest page is waiting for a first-response for the main resource -of the page. +Returns a boolean whether the guest page is waiting for a first-response for the +main resource of the page. -### ``.stop() +### `.stop()` Stops any pending navigation. -### ``.reload() +### `.reload()` -Reloads guest page. +Reloads the guest page. -### ``.reloadIgnoringCache() +### `.reloadIgnoringCache()` -Reloads guest page and ignores cache. +Reloads the guest page and ignores cache. -### ``.canGoBack() +### `.canGoBack()` -Returns whether guest page can go back. +Returns a boolean whether the guest page can go back. -### ``.canGoForward() +### `.canGoForward()` -Returns whether guest page can go forward. +Returns a boolean whether the guest page can go forward. -### ``.canGoToOffset(offset) +### `.canGoToOffset(offset)` * `offset` Integer -Returns whether guest page can go to `offset`. +Returns a boolean whether the guest page can go to `offset`. -### ``.goBack() +### `.clearHistory()` -Makes guest page go back. +Clears the navigation history. -### ``.goForward() +### `.goBack()` -Makes guest page go forward. +Makes the guest page go back. -### ``.goToIndex(index) +### `.goForward()` + +Makes the guest page go forward. + +### `.goToIndex(index)` * `index` Integer Navigates to the specified absolute index. -### ``.goToOffset(offset) +### `.goToOffset(offset)` * `offset` Integer Navigates to the specified offset from the "current entry". -### ``.isCrashed() +### `.isCrashed()` Whether the renderer process has crashed. -### ``.setUserAgent(userAgent) +### `.setUserAgent(userAgent)` * `userAgent` String -Overrides the user agent for guest page. +Overrides the user agent for the guest page. -### ``.insertCSS(css) +### `.getUserAgent()` + +Returns a `String` representing the user agent for guest page. + +### `.insertCSS(css)` * `css` String -Injects CSS into guest page. +Injects CSS into the guest page. -### ``.executeJavaScript(code) +### `.executeJavaScript(code, userGesture)` * `code` String +* `userGesture` Boolean - Default `false`. -Evaluates `code` in guest page. +Evaluates `code` in page. If `userGesture` is set, it will the create user +gesture context in the page. HTML APIs like `requestFullScreen`, which require +user action, can take advantage of this option for automation. -### ``.openDevTools() +### `.openDevTools()` -Opens a devtools window for guest page. +Opens a DevTools window for guest page. -### ``.closeDevTools() +### `.closeDevTools()` -Closes the devtools window of guest page. +Closes the DevTools window of guest page. -### ``.isDevToolsOpened() +### `.isDevToolsOpened()` -Returns whether guest page has a devtools window attached. +Returns a boolean whether guest page has a DevTools window attached. -### ``.inspectElement(x, y) +### `.inspectElement(x, y)` * `x` Integer * `y` Integer Starts inspecting element at position (`x`, `y`) of guest page. -### ``.undo() +### `.inspectServiceWorker()` + +Opens the DevTools for the service worker context present in the guest page. + +### `.setAudioMuted(muted)` + +* `muted` Boolean + +Set guest page muted. + +### `.isAudioMuted()` + +Returns whether guest page has been muted. + +### `.undo()` Executes editing command `undo` in page. -### ``.redo() +### `.redo()` Executes editing command `redo` in page. -### ``.cut() +### `.cut()` Executes editing command `cut` in page. -### ``.copy() +### `.copy()` Executes editing command `copy` in page. -### ``.paste() +### `.paste()` Executes editing command `paste` in page. -### ``.delete() +### `.pasteAndMatchStyle()` + +Executes editing command `pasteAndMatchStyle` in page. + +### `.delete()` Executes editing command `delete` in page. -### ``.selectAll() +### `.selectAll()` Executes editing command `selectAll` in page. -### ``.unselect() +### `.unselect()` Executes editing command `unselect` in page. -### ``.replace(text) +### ``.replaceMisspelling(text) +### `.replaceMisspelling(text)` * `text` String Executes editing command `replaceMisspelling` in page. -### ``.send(channel[, args...]) +### `.print([options])` + +Prints `webview`'s web page. Same with `webContents.print([options])`. + +### `.printToPDF(options, callback)` + +Prints webview's web page as PDF, Same with `webContents.printToPDF(options, callback)` + +### `.send(channel[, args...])` * `channel` String +* `arg` (optional) Send `args..` to guest page via `channel` in asynchronous message, the guest page can handle it by listening to the `channel` event of `ipc` module. -See [WebContents.send](browser-window.md#webcontentssendchannel-args) for +See [WebContents.send](web-contents.md#webcontentssendchannel-args) for examples. +### `.sendInputEvent(event)` + +* `event` Object + +Sends an input `event` to the page. + +See [WebContents.sendInputEvent](web-contents.md##webcontentssendinputeventevent) +for detailed description of `event` object. + ## DOM events -### did-finish-load +The following DOM events are available to the `webview` tag: + +### Event: 'load-commit' + +Returns: + +* `url` String +* `isMainFrame` Boolean + +Fired when a load has committed. This includes navigation within the current +document as well as subframe document-level loads, but does not include +asynchronous resource loads. + +### Event: 'did-finish-load' Fired when the navigation is done, i.e. the spinner of the tab will stop -spinning, and the `onload` event was dispatched. +spinning, and the `onload` event is dispatched. -### did-fail-load +### Event: 'did-fail-load' + +Returns: * `errorCode` Integer * `errorDescription` String +* `validatedUrl` String This event is like `did-finish-load`, but fired when the load failed or was cancelled, e.g. `window.stop()` is invoked. -### did-frame-finish-load +### Event: 'did-frame-finish-load' + +Returns: * `isMainFrame` Boolean Fired when a frame has done navigation. -### did-start-loading +### Event: 'did-start-loading' Corresponds to the points in time when the spinner of the tab starts spinning. -### did-stop-loading +### Event: 'did-stop-loading' Corresponds to the points in time when the spinner of the tab stops spinning. -### did-get-response-details +### Event: 'did-get-response-details' + +Returns: * `status` Boolean * `newUrl` String @@ -317,11 +424,14 @@ Corresponds to the points in time when the spinner of the tab stops spinning. * `httpResponseCode` Integer * `requestMethod` String * `referrer` String +* `headers` Object Fired when details regarding a requested resource is available. `status` indicates socket connection to download the resource. -### did-get-redirect-request +### Event: 'did-get-redirect-request' + +Returns: * `oldUrl` String * `newUrl` String @@ -329,11 +439,13 @@ Fired when details regarding a requested resource is available. Fired when a redirect was received while requesting a resource. -### dom-ready +### Event: 'dom-ready' Fired when document in the given frame is loaded. -### page-title-set +### Event: 'page-title-set' + +Returns: * `title` String * `explicitSet` Boolean @@ -341,13 +453,25 @@ Fired when document in the given frame is loaded. Fired when page title is set during navigation. `explicitSet` is false when title is synthesised from file url. -### page-favicon-updated +### Event: 'page-favicon-updated' -* `favicons` Array - Array of Urls +Returns: + +* `favicons` Array - Array of Urls. Fired when page receives favicon urls. -### console-message +### Event: 'enter-html-full-screen' + +Fired when page enters fullscreen triggered by HTML API. + +### Event: 'leave-html-full-screen' + +Fired when page leaves fullscreen triggered by HTML API. + +### Event: 'console-message' + +Returns: * `level` Integer * `message` String @@ -361,11 +485,13 @@ without regard for log level or other properties. ```javascript webview.addEventListener('console-message', function(e) { - console.log('Guest page logged a message: ', e.message); + console.log('Guest page logged a message:', e.message); }); ``` -### new-window +### Event: 'new-window' + +Returns: * `url` String * `frameName` String @@ -382,7 +508,7 @@ webview.addEventListener('new-window', function(e) { }); ``` -### close +### Event: 'close' Fired when the guest page attempts to close itself. @@ -395,7 +521,9 @@ webview.addEventListener('close', function() { }); ``` -### ipc-message +### Event: 'ipc-message' + +Returns: * `channel` String * `args` Array @@ -419,13 +547,26 @@ webview.send('ping'); var ipc = require('ipc'); ipc.on('ping', function() { ipc.sendToHost('pong'); -}) +}); ``` -### crashed +### Event: 'crashed' Fired when the renderer process is crashed. -### destroyed +### Event: 'gpu-crashed' + +Fired when the gpu process is crashed. + +### Event: 'plugin-crashed' + +Returns: + +* `name` String +* `version` String + +Fired when a plugin process is crashed. + +### Event: 'destroyed' Fired when the WebContents is destroyed. diff --git a/docs/api/window-open.md b/docs/api/window-open.md index 23bcf2b95f33..c574d5d036ee 100644 --- a/docs/api/window-open.md +++ b/docs/api/window-open.md @@ -1,22 +1,22 @@ -# `window.open` function +# The `window.open` function -When `window.open` is called to create a new window in web page, a new instance -of `BrowserWindow` will be created for the `url`, and a proxy will be returned -to `window.open` to let the page to have limited control over it. +When `window.open` is called to create a new window in a web page, a new instance +of `BrowserWindow` will be created for the `url` and a proxy will be returned +to `window.open` to let the page have limited control over it. -The proxy only has some limited standard functionality implemented to be -compatible with traditional web pages, for full control of the created window +The proxy has limited standard functionality implemented to be +compatible with traditional web pages. For full control of the new window you should create a `BrowserWindow` directly. -## window.open(url, [frameName[, features]]) +### `window.open(url[, frameName][, features])` * `url` String -* `frameName` String -* `features` String +* `frameName` String (optional) +* `features` String (optional) Creates a new window and returns an instance of `BrowserWindowProxy` class. -## window.opener.postMessage(message, targetOrigin) +### `window.opener.postMessage(message, targetOrigin)` * `message` String * `targetOrigin` String @@ -26,29 +26,32 @@ origin preference. ## Class: BrowserWindowProxy -### BrowserWindowProxy.blur() +The `BrowserWindowProxy` object is returned from `window.open` and provides +limited functionality with the child window. + +### `BrowserWindowProxy.blur()` Removes focus from the child window. -### BrowserWindowProxy.close() +### `BrowserWindowProxy.close()` Forcefully closes the child window without calling its unload event. -### BrowserWindowProxy.closed +### `BrowserWindowProxy.closed` Set to true after the child window gets closed. -### BrowserWindowProxy.eval(code) +### `BrowserWindowProxy.eval(code)` * `code` String Evaluates the code in the child window. -### BrowserWindowProxy.focus() +### `BrowserWindowProxy.focus()` Focuses the child window (brings the window to front). -### BrowserWindowProxy.postMessage(message, targetOrigin) +### `BrowserWindowProxy.postMessage(message, targetOrigin)` * `message` String * `targetOrigin` String @@ -57,4 +60,4 @@ 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: +with no properties and a single method. diff --git a/docs/development/atom-shell-vs-node-webkit.md b/docs/development/atom-shell-vs-node-webkit.md index ab8f6ab6dbdc..c1fffa304ab5 100644 --- a/docs/development/atom-shell-vs-node-webkit.md +++ b/docs/development/atom-shell-vs-node-webkit.md @@ -1,18 +1,18 @@ -# Technical differences to NW.js (formerly node-webkit) +# Technical Differences Between Electron and NW.js (formerly node-webkit) __Note: Electron was previously named Atom Shell.__ Like NW.js, Electron provides a platform to write desktop applications -with JavaScript and HTML and has Node integration to grant access to low level -system in web pages. +with JavaScript and HTML and has Node integration to grant access to the low +level system from web pages. But there are also fundamental differences between the two projects that make Electron a completely separate product from NW.js: -__1. Entry of application__ +__1. Entry of Application__ -In NW.js, the main entry of an application is a web page. You specify a -main page in the `package.json` and it is opened in a browser window as +In NW.js the main entry point of an application is a web page. You specify a +main page URL in the `package.json` and it is opened in a browser window as the application's main window. In Electron, the entry point is a JavaScript script. Instead of @@ -23,19 +23,18 @@ to decide when to quit the application. Electron works more like the Node.js runtime. Electron's APIs are lower level so you can use it for browser testing in place of [PhantomJS](http://phantomjs.org/). -__2. Build system__ +__2. Build System__ -In order to avoid the complexity of building the whole Chromium, Electron uses -[libchromiumcontent](https://github.com/brightray/libchromiumcontent) to access -Chromium's Content API. libchromiumcontent is a single, shared library that -includes the Chromium Content module and all its dependencies. Users don't +In order to avoid the complexity of building all of Chromium, Electron uses [`libchromiumcontent`](https://github.com/brightray/libchromiumcontent) to access +Chromium's Content API. `libchromiumcontent` is a single shared library that +includes the Chromium Content module and all of its dependencies. Users don't need a powerful machine to build Electron. -__3. Node integration__ +__3. Node Integration__ In NW.js, the Node integration in web pages requires patching Chromium to -work, while in Electron we chose a different way to integrate libuv loop with -each platform's message loop to avoid hacking Chromium. See the +work, while in Electron we chose a different way to integrate the libuv loop +with each platform's message loop to avoid hacking Chromium. See the [`node_bindings`](../../atom/common/) code for how that was done. __4. Multi-context__ diff --git a/docs/development/build-instructions-linux.md b/docs/development/build-instructions-linux.md index edaf2f79d2e2..9ddeed9809b0 100644 --- a/docs/development/build-instructions-linux.md +++ b/docs/development/build-instructions-linux.md @@ -1,33 +1,44 @@ -# Build instructions (Linux) +# Build Instructions (Linux) + +Follow the guidelines below for building Electron on Linux. ## Prerequisites -* [Node.js](http://nodejs.org) -* Clang 3.4 or later -* Development headers of GTK+ and libnotify +* Python 2.7.x. Some distributions like CentOS still use Python 2.6.x + so you may need to check your Python version with `python -V`. +* Node.js v0.12.x. There are various ways to install Node. You can download + source code from [Node.js](http://nodejs.org) and compile from source. + Doing so permits installing Node on your own home directory as a standard user. + Or try repositories such as [NodeSource](https://nodesource.com/blog/nodejs-v012-iojs-and-the-nodesource-linux-repositories). +* Clang 3.4 or later. +* Development headers of GTK+ and libnotify. -On Ubuntu you could install the libraries via: +On Ubuntu, install the following libraries: ```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 \ - gcc-multilib g++-multilib + libxss1 libnss3-dev gcc-multilib g++-multilib ``` -Latest Node.js could be installed via ppa: +On Fedora, install the following libraries: ```bash -$ sudo apt-get install python-software-properties software-properties-common -$ sudo add-apt-repository ppa:chris-lea/node.js -$ sudo apt-get update -$ sudo apt-get install nodejs - -# Update to latest npm -$ sudo npm install npm -g +$ 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 ``` -## Getting the code +Other distributions may offer similar packages for installation via package +managers such as pacman. Or one can compile from source code. + +## If You Use Virtual Machines For Building + +If you plan to build Electron on a virtual machine you will need a fixed-size +device container of at least 25 gigabytes in size. + +## Getting the Code ```bash $ git clone https://github.com/atom/electron.git @@ -36,89 +47,92 @@ $ git clone https://github.com/atom/electron.git ## Bootstrapping The bootstrap script will download all necessary build dependencies and create -build project files. Notice that we're using `ninja` to build Electron so -there is no `Makefile` generated. +the build project files. You must have Python 2.7.x for the script to succeed. +Downloading certain files can take a long time. Notice that we are using +`ninja` to build Electron so there is no `Makefile` generated. ```bash $ cd electron $ ./script/bootstrap.py -v ``` +### Cross compilation + +If you want to build for an `arm` target you should also install the following +dependencies: + +```bash +$ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \ + g++-arm-linux-gnueabihf +``` + +And to cross compile for `arm` or `ia32` targets, you should pass the +`--target_arch` parameter to the `bootstrap.py` script: + +```bash +$ ./script/bootstrap.py -v --target_arch=arm +``` + ## Building -Build both `Release` and `Debug` targets: +If you would like to build both `Release` and `Debug` targets: ```bash $ ./script/build.py ``` -You can also only build the `Debug` target: +This script will cause a very large Electron executable to be placed in +the directory `out/R`. The file size is in excess of 1.3 gigabytes. This +happens because the Release target binary contains debugging symbols. +To reduce the file size, run the `create-dist.py` script: + +```bash +$ ./script/create-dist.py +``` + +This will put a working distribution with much smaller file sizes in +the `dist` directory. After running the create-dist.py script, you +may want to remove the 1.3+ gigabyte binary which is still in `out/R`. + +You can also build the `Debug` target only: ```bash $ ./script/build.py -c D ``` -After building is done, you can find `atom` under `out/D`. +After building is done, you can find the `electron` debug binary under `out/D`. + +## Cleaning + +To clean the build files: + +```bash +$ ./script/clean.py +``` ## Troubleshooting -### fatal error: bits/predefs.h: No such file or directory +Make sure you have installed all of the build dependencies. -If you got an error like this: +### Error While Loading Shared Libraries: libtinfo.so.5 -```` -In file included from /usr/include/stdio.h:28:0, - from ../../../svnsrc/libgcc/../gcc/tsystem.h:88, - from ../../../svnsrc/libgcc/libgcc2.c:29: -/usr/include/features.h:324:26: fatal error: bits/predefs.h: No such file or directory - #include -```` - -Then you need to install `gcc-multilib` and `g++-multilib`, on Ubuntu you can do -this: +Prebulit `clang` will try to link to `libtinfo.so.5`. Depending on the host +architecture, symlink to appropriate `libncurses`: ```bash -$ sudo apt-get install gcc-multilib g++-multilib +$ sudo ln -s /usr/lib/libncurses.so.5 /usr/lib/libtinfo.so.5 ``` -### error adding symbols: DSO missing from command line - -If you got an error like this: - -```` -/usr/bin/ld: vendor/download/libchromiumcontent/Release/libchromiumcontent.so: undefined reference to symbol 'gconf_client_get' -//usr/lib/x86_64-linux-gnu/libgconf-2.so.4: error adding symbols: DSO missing from command line -```` - -libchromiumcontent.so is build with clang 3.0 which is incompatible with newer -versions of clang. Try using clang 3.0, default version in Ubuntu 12.04. - -### libudev.so.0 missing - -If you get an error like: - -```` -/usr/bin/ld: warning: libudev.so.0, needed by .../vendor/brightray/vendor/download/libchromiumcontent/Release/libchromiumcontent.so, not found (try using -rpath or -rpath-link) -```` - -and you are on Ubuntu 13.04+, 64 bit system, try doing - -```bash -sudo ln -s /lib/x86_64-linux-gnu/libudev.so.1.3.5 /usr/lib/libudev.so.0 -``` - -for ubuntu 13.04+ 32 bit systems, try doing - -```bash -sudo ln -s /lib/i386-linux-gnu/libudev.so.1.3.5 /usr/lib/libudev.so.0 -``` - -also see - -https://github.com/nwjs/nw.js/wiki/The-solution-of-lacking-libudev.so.0 - ## Tests +Test your changes conform to the project coding style using: + +```bash +$ ./script/cpplint.py +``` + +Test functionality using: + ```bash $ ./script/test.py ``` diff --git a/docs/development/build-instructions-mac.md b/docs/development/build-instructions-osx.md similarity index 58% rename from docs/development/build-instructions-mac.md rename to docs/development/build-instructions-osx.md index b0980a231250..ac51bf0dc224 100644 --- a/docs/development/build-instructions-mac.md +++ b/docs/development/build-instructions-osx.md @@ -1,17 +1,19 @@ -# Build instructions (Mac) +# Build Instructions (OS X) + +Follow the guidelines below for building Electron on OS X. ## Prerequisites * OS X >= 10.8 * [Xcode](https://developer.apple.com/technologies/tools/) >= 5.1 -* [node.js](http://nodejs.org) (external). +* [node.js](http://nodejs.org) (external) -If you are using the python downloaded by Homebrew, you also need to install +If you are using the Python downloaded by Homebrew, you also need to install following python modules: * pyobjc -## Getting the code +## Getting the Code ```bash $ git clone https://github.com/atom/electron.git @@ -20,7 +22,7 @@ $ git clone https://github.com/atom/electron.git ## Bootstrapping The bootstrap script will download all necessary build dependencies and create -build project files. Notice that we're using `ninja` to build Electron so +the build project files. Notice that we're using `ninja` to build Electron so there is no Xcode project generated. ```bash @@ -44,13 +46,21 @@ $ ./script/build.py -c D After building is done, you can find `Electron.app` under `out/D`. -## 32bit support +## 32bit Support -Electron can only be built for 64bit target on OS X, and there is no plan to +Electron can only be built for a 64bit target on OS X and there is no plan to support 32bit OS X in future. ## Tests +Test your changes conform to the project coding style using: + +```bash +$ ./script/cpplint.py +``` + +Test functionality using: + ```bash $ ./script/test.py ``` diff --git a/docs/development/build-instructions-windows.md b/docs/development/build-instructions-windows.md index 27403b7688da..733966f1da69 100644 --- a/docs/development/build-instructions-windows.md +++ b/docs/development/build-instructions-windows.md @@ -1,41 +1,44 @@ -# Build instructions (Windows) +# Build Instructions (Windows) + +Follow the guidelines below for building Electron on Windows. ## Prerequisites * Windows 7 / Server 2008 R2 or higher * Visual Studio 2013 - [download VS 2013 Community Edition for - free](http://www.visualstudio.com/products/visual-studio-community-vs) + free](https://www.visualstudio.com/downloads/download-visual-studio-vs). * [Python 2.7](http://www.python.org/download/releases/2.7/) * [Node.js](http://nodejs.org/download/) -* [git](http://git-scm.com) +* [Git](http://git-scm.com) -If you don't have a Windows installation at the moment, -[modern.ie](https://www.modern.ie/en-us/virtualization-tools#downloads) has -timebombed versions of Windows that you can use to build Electron. +If you don't currently have a Windows installation, [modern.ie](https://www.modern.ie/en-us/virtualization-tools#downloads) +has timebombed versions of Windows that you can use to build Electron. -The building of Electron is done entirely with command-line scripts, so you -can use any editor you like to develop Electron, but it also means you can -not use Visual Studio for the development. Support of building with Visual -Studio will come in the future. +Building Electron is done entirely with command-line scripts and cannot be done +with Visual Studio. You can develop Electron with any editor but support for +building with Visual Studio will come in the future. **Note:** Even though Visual Studio is not used for building, it's still -**required** because we need the build toolchains it provided. +**required** because we need the build toolchains it provides. -## Getting the code +**Note:** Visual Studio 2015 will not work. Please make sure to get MSVS +**2013**. + +## Getting the Code ```powershell -git clone https://github.com/atom/electron.git +$ git clone https://github.com/atom/electron.git ``` ## Bootstrapping The bootstrap script will download all necessary build dependencies and create -build project files. Notice that we're using `ninja` to build Electron so +the build project files. Notice that we're using `ninja` to build Electron so there is no Visual Studio project generated. ```powershell -cd electron -python script\bootstrap.py -v +$ cd electron +$ python script\bootstrap.py -v ``` ## Building @@ -43,32 +46,51 @@ python script\bootstrap.py -v Build both Release and Debug targets: ```powershell -python script\build.py +$ python script\build.py ``` You can also only build the Debug target: ```powershell -python script\build.py -c D +$ python script\build.py -c D ``` -After building is done, you can find `atom.exe` under `out\D`. +After building is done, you can find `electron.exe` under `out\D` (debug +target) or under `out\R` (release target). -## 64bit build +## 64bit Build To build for the 64bit target, you need to pass `--target_arch=x64` when running the bootstrap script: ```powershell -python script\bootstrap.py -v --target_arch=x64 +$ python script\bootstrap.py -v --target_arch=x64 ``` The other building steps are exactly the same. ## Tests +Test your changes conform to the project coding style using: + ```powershell -python script\test.py +$ python script\cpplint.py +``` + +Test functionality using: + +```powershell +$ python script\test.py +``` + +Tests that include native modules (e.g. `runas`) can't be executed with the +debug build (see [#2558](https://github.com/atom/electron/issues/2558) for +details), but they will work with the release build. + +To run the tests with the release build use: + +```powershell +$ python script\test.py -R ``` ## Troubleshooting @@ -102,24 +124,24 @@ Traceback (most recent call last): subprocess.CalledProcessError: Command '['npm.cmd', 'install']' returned non-zero exit status 3 ``` -This is caused by a bug when using Cygwin python and Win32 node together. The -solution is to use the Win32 python to execute the bootstrap script (supposing -you have installed python under `C:\Python27`): +This is caused by a bug when using Cygwin Python and Win32 Node together. The +solution is to use the Win32 Python to execute the bootstrap script (assuming +you have installed Python under `C:\Python27`): -```bash -/cygdrive/c/Python27/python.exe script/bootstrap.py +```powershell +$ /cygdrive/c/Python27/python.exe script/bootstrap.py ``` ### LNK1181: cannot open input file 'kernel32.lib' -Try reinstalling 32bit node.js. +Try reinstalling 32bit Node.js. ### Error: ENOENT, stat 'C:\Users\USERNAME\AppData\Roaming\npm' Simply making that directory [should fix the problem](http://stackoverflow.com/a/25095327/102704): ```powershell -mkdir ~\AppData\Roaming\npm +$ mkdir ~\AppData\Roaming\npm ``` ### node-gyp is not recognized as an internal or external command diff --git a/docs/development/build-system-overview.md b/docs/development/build-system-overview.md index 5aec9d74cb03..856c53273ff2 100644 --- a/docs/development/build-system-overview.md +++ b/docs/development/build-system-overview.md @@ -1,64 +1,72 @@ -# Build system overview +# Build System Overview -Electron uses `gyp` for project generation, and `ninja` for building, project -configurations can be found in `.gyp` and `.gypi` files. +Electron uses `gyp` for project generation and `ninja` for building. Project +configurations can be found in the `.gyp` and `.gypi` files. -## Gyp files +## Gyp Files -Following `gyp` files contain the main rules of building Electron: +Following `gyp` files contain the main rules for building Electron: * `atom.gyp` defines how Electron itself is built. * `common.gypi` adjusts the build configurations of Node to make it build together with Chromium. -* `vendor/brightray/brightray.gyp` defines how `brightray` is built, and - includes the default configurations of linking with Chromium. +* `vendor/brightray/brightray.gyp` defines how `brightray` is built and + includes the default configurations for linking with Chromium. * `vendor/brightray/brightray.gypi` includes general build configurations about building. -## Component build +## Component Build -Since Chromium is quite a large project, the final linking stage would take -quite a few minutes, making it hard for development. In order to solve this, -Chromium introduced the "component build", which builds each component as a -separate shared library, making linking very quick but sacrificing file size +Since Chromium is quite a large project, the final linking stage can take +quite a few minutes, which makes it hard for development. In order to solve +this, Chromium introduced the "component build", which builds each component as +a separate shared library, making linking very quick but sacrificing file size and performance. In Electron we took a very similar approach: for `Debug` builds, the binary -will be linked to shared library version of Chromium's components to achieve +will be linked to a shared library version of Chromium's components to achieve fast linking time; for `Release` builds, the binary will be linked to the static library versions, so we can have the best possible binary size and performance. -## Minimal bootstrapping +## Minimal Bootstrapping -All of Chromium's prebuilt binaries are downloaded when running the bootstrap -script. By default both static libraries and shared libraries will be -downloaded and the final size should be between 800MB and 2GB according to the -platform. +All of Chromium's prebuilt binaries (`libchromiumcontent`) are downloaded when +running the bootstrap script. By default both static libraries and shared +libraries will be downloaded and the final size should be between 800MB and 2GB +depending on the platform. + +By default, `libchromiumcontent` is downloaded from Amazon Web Services. +If the `LIBCHROMIUMCONTENT_MIRROR` environment variable is set, the bootstrap +script will download from it. +[`libchromiumcontent-qiniu-mirror`](https://github.com/hokein/libchromiumcontent-qiniu-mirror) +is a mirror for `libchromiumcontent`. If you have trouble in accessing AWS, you +can switch the download address to it via +`export LIBCHROMIUMCONTENT_MIRROR=http://7xk3d2.dl1.z0.glb.clouddn.com/` If you only want to build Electron quickly for testing or development, you -can only download the shared library versions by passing the `--dev` parameter: +can download just the shared library versions by passing the `--dev` parameter: ```bash $ ./script/bootstrap.py --dev $ ./script/build.py -c D ``` -## Two-phrase project generation +## Two-Phrase Project Generation Electron links with different sets of libraries in `Release` and `Debug` -builds, however `gyp` doesn't support configuring different link settings for +builds. `gyp`, however, doesn't support configuring different link settings for different configurations. To work around this Electron uses a `gyp` variable -`libchromiumcontent_component` to control which link settings to use, and only +`libchromiumcontent_component` to control which link settings to use and only generates one target when running `gyp`. -## Target names +## Target Names Unlike most projects that use `Release` and `Debug` as target names, Electron uses `R` and `D` instead. This is because `gyp` randomly crashes if there is -only one `Release` or `Debug` build configuration is defined, and Electron has -to only generate one target for one time as stated above. +only one `Release` or `Debug` build configuration defined, and Electron only has +to generate one target at a time as stated above. -This only affects developers, if you are only building Electron for rebranding +This only affects developers, if you are just building Electron for rebranding you are not affected. diff --git a/docs/development/coding-style.md b/docs/development/coding-style.md index df7f42e4a74e..a86ee32417db 100644 --- a/docs/development/coding-style.md +++ b/docs/development/coding-style.md @@ -1,28 +1,37 @@ -# Coding style +# Coding Style + +These are the style guidelines for coding in Electron. ## C++ and Python -For C++ and Python, we just follow Chromium's [Coding -Style](http://www.chromium.org/developers/coding-style), there is also a -script `script/cpplint.py` to check whether all files confirm. +For C++ and Python, we follow Chromium's [Coding +Style](http://www.chromium.org/developers/coding-style). There is also a +script `script/cpplint.py` to check whether all files conform. -The python's version we are using now is Python 2.7. +The Python version we are using now is Python 2.7. + +The C++ code uses a lot of Chromium's abstractions and types, so it's +recommended to get acquainted with them. A good place to start is +Chromium's [Important Abstractions and Data Structures](https://www.chromium.org/developers/coding-style/important-abstractions-and-data-structures) +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 also following rules: +Guide](https://github.com/styleguide/javascript) and the following rules: * 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 [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 `.coffee` files. ## API Names When creating a new API, we should prefer getters and setters instead of -jQuery's one-function style, for example, `.getText()` and `.setText(text)` +jQuery's one-function style. For example, `.getText()` and `.setText(text)` are preferred to `.text([text])`. There is a -[discussion](https://github.com/atom/electron/issues/46) of this. +[discussion](https://github.com/atom/electron/issues/46) on this. diff --git a/docs/development/setting-up-symbol-server.md b/docs/development/setting-up-symbol-server.md index 3149755b98de..1dc31f7056d2 100644 --- a/docs/development/setting-up-symbol-server.md +++ b/docs/development/setting-up-symbol-server.md @@ -1,4 +1,4 @@ -# Setting up symbol server in debugger +# Setting Up Symbol Server in Debugger Debug symbols allow you to have better debugging sessions. They have information about the functions contained in executables and dynamic libraries and provide @@ -16,16 +16,16 @@ unoptimized local build. The official symbol server URL for Electron is http://54.249.141.255:8086/atom-shell/symbols. -You cannot visit this URL directly: you must add it to the symbol path of your +You cannot visit this URL directly, you must add it to the symbol path of your debugging tool. In the examples below, a local cache directory is used to avoid -repeatedly fetching the PDB from the server. Replace `c:\code\symbols` with an +repeatedly fetching the PDB from the server. Replace `c:\code\symbols` with an appropriate cache directory on your machine. -## Using the symbol server in Windbg +## Using the Symbol Server in Windbg 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 486f553ab604..cbca7108526f 100644 --- a/docs/development/source-code-directory-structure.md +++ b/docs/development/source-code-directory-structure.md @@ -1,62 +1,63 @@ -# Source code directory structure +# Source Code Directory Structure -## Overview - -The source code of Electron is separated into a few parts, and we are mostly +The source code of Electron is separated into a few parts, mostly following Chromium on the separation conventions. You may need to become familiar with [Chromium's multi-process architecture](http://dev.chromium.org/developers/design-documents/multi-process-architecture) to understand the source code better. -## Structure of source code +## Structure of Source Code -* **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. -* **chromium_src** - Source code that copied from Chromium. -* **docs** - Documentations. -* **spec** - Automatic tests. -* **atom.gyp** - Building rules of Electron. -* **common.gypi** - Compiler specific settings and building rules for other - components like `node` and `breakpad`. +``` +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. +├── chromium_src - Source code that copied from Chromium. +├── docs - Documentations. +├── spec - Automatic tests. +├── atom.gyp - Building rules of Electron. +└── common.gypi - Compiler specific settings and building rules for other + components like `node` and `breakpad`. +``` -## Structure of other directories +## Structure of Other Directories * **script** - Scripts used for development purpose like building, packaging, testing, etc. * **tools** - Helper scripts used by gyp files, unlike `script`, scripts put here should never be invoked by users directly. * **vendor** - Source code of third party dependencies, we didn't use - `third_party` as name because it would confuse with the same directory in + `third_party` as name because it would confuse it with the same directory in Chromium's source code tree. * **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. * **external_binaries** - Downloaded binaries of third-party frameworks which - do not support to be built via `gyp`. + do not support building with `gyp`. diff --git a/docs/styleguide.md b/docs/styleguide.md new file mode 100644 index 000000000000..77c90467ca93 --- /dev/null +++ b/docs/styleguide.md @@ -0,0 +1,94 @@ +# Electron Documentation Styleguide + +Find the appropriate section for your task: [reading Electron documentation](#reading-electron-documentation) +or [writing Electron documentation](#writing-electron-documentation). + +## Writing Electron Documentation + +These are the ways that we construct the Electron documentation. + +- Maximum one `h1` title per page. +- Use `bash` instead of `cmd` in code blocks (because of syntax highlighter). +- Doc `h1` titles should match object name (i.e. `browser-window` → + `BrowserWindow`). + - Hyphen separated filenames, however, are fine. +- No headers following headers, add at least a one-sentence description. +- Methods headers are wrapped in `code` ticks. +- Event headers are wrapped in single 'quotation' marks. +- No nesting lists more than 2 levels (unfortunately because of markdown + renderer). +- Add section titles: Events, Class Methods and Instance Methods. +- Use 'will' over 'would' when describing outcomes. +- Events and methods are `h3` headers. +- Optional arguments written as `function (required[, optional])`. +- Optional arguments are denoted when called out in list. +- Line length is 80-column wrapped. +- Platform specific methods are noted in italics following method header. + - ```### `method(foo, bar)` _OS X_``` +- Prefer 'in the ___ process' over 'on' + +### Documentation Translations + +Translations of the Electron docs are located within the `docs-translations` +directory. + +To add another set (or partial set): + +- Create a subdirectory named by language abbreviation. +- Within that subdirectory, duplicate the `docs` directory, keeping the + names of directories and files same. +- Translate the files. +- Update the `README.md` within your language directory to link to the files + you have translated. +- Add a link to your translation directory on the main Electron [README](https://github.com/atom/electron#documentation-translations). + +## Reading Electron Documentation + +Here are some tips for understanding Electron documentation syntax. + +### Methods + +An example of [method](https://developer.mozilla.org/en-US/docs/Glossary/Method) +documentation: + +--- + +`methodName(required[, optional]))` + +* `require` String, **required** +* `optional` Integer + +--- + +The method name is followed by the arguments it takes. Optional arguments are +notated by brackets surrounding the optional argument as well as the comma +required if this optional argument follows another argument. + +Below the method is more detailed information on each of the arguments. The type +of argument is notated by either the common types: [`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) +or a custom type like Electron's [`webContent`](api/web-content.md). + +### Events + +An example of [event](https://developer.mozilla.org/en-US/docs/Web/API/Event) +documentation: + +--- + +Event: 'wake-up' + +Returns: + +* `time` String + +--- + +The event is a string that is used after a `.on` listener method. If it returns +a value it and its type is noted below. If you were to listen and respond to +this event it might look something like this: + +```javascript +Alarm.on('wake-up', function(time) { + console.log(time) +}) +``` diff --git a/docs/tutorial/application-distribution.md b/docs/tutorial/application-distribution.md index bb969ef3f85b..4f1f5202a008 100644 --- a/docs/tutorial/application-distribution.md +++ b/docs/tutorial/application-distribution.md @@ -1,9 +1,9 @@ -# Application distribution +# Application Distribution -To distribute your app with Electron, you should name the folder of your app -as `app`, and put it under Electron's resources directory (on OS X it is -`Electron.app/Contents/Resources/`, and on Linux and Windows it is -`resources/`), like this: +To distribute your app with Electron, the folder containing your app should be +named `app` and placed under Electron's resources directory (on OS X it is +`Electron.app/Contents/Resources/` and on Linux and Windows it is `resources/`), +like this: On OS X: @@ -24,18 +24,18 @@ electron/resources/app ``` Then execute `Electron.app` (or `electron` on Linux, `electron.exe` on Windows), -and Electron will start as your app. The `electron` directory would then be -your distribution that should be delivered to final users. +and Electron will start as your app. The `electron` directory will then be +your distribution to deliver to final users. -## Packaging your app into a file +## Packaging Your App into a File -Apart from shipping your app by copying all its sources files, you can also +Apart from shipping your app by copying all of its source files, you can also package your app into an [asar](https://github.com/atom/asar) archive to avoid exposing your app's source code to users. To use an `asar` archive to replace the `app` folder, you need to rename the archive to `app.asar`, and put it under Electron's resources directory like -bellow, and Electron will then try read the archive and start from it. +below, and Electron will then try read the archive and start from it. On OS X: @@ -53,7 +53,7 @@ electron/resources/ More details can be found in [Application packaging](application-packaging.md). -## Rebranding with downloaded binaries +## Rebranding with Downloaded Binaries After bundling your app into Electron, you will want to rebrand Electron before distributing it to users. @@ -103,29 +103,11 @@ MyApp.app/Contents You can rename the `electron` executable to any name you like. -## Rebranding by rebuilding Electron from source +## Rebranding by Rebuilding Electron from Source It is also possible to rebrand Electron by changing the product name and -building it from source. To do this you need to override the `GYP_DEFINES` -environment variable and have a clean rebuild: - -__Windows__ - -```cmd -> set "GYP_DEFINES=project_name=myapp product_name=MyApp" -> python script\clean.py -> python script\bootstrap.py -> python script\build.py -c R -t myapp -``` - -__Bash__ - -```bash -$ export GYP_DEFINES="project_name=myapp product_name=MyApp" -$ script/clean.py -$ script/bootstrap.py -$ script/build.py -c Release -t myapp -``` +building it from source. To do this you need to modify the `atom.gyp` file and +have a clean rebuild. ### grunt-build-atom-shell diff --git a/docs/tutorial/application-packaging.md b/docs/tutorial/application-packaging.md index 0741b2572447..0cf3a6b596f4 100644 --- a/docs/tutorial/application-packaging.md +++ b/docs/tutorial/application-packaging.md @@ -1,18 +1,19 @@ -# Application packaging +# Application Packaging -To protect your app's resources and source code from the users, you can choose -to package your app into an [asar][asar] archive with little changes to your -source code. +To mitigate [issues](https://github.com/joyent/node/issues/6960) around long +path names on Windows, slightly speed up `require` and conceal your source code +from cursory inspection, you can choose to package your app into an [asar][asar] +archive with little changes to your source code. -## Generating `asar` archive +## Generating `asar` Archive An [asar][asar] archive is a simple tar-like format that concatenates files -into a single file, Electron can read arbitrary files from it without unpacking +into a single file. Electron can read arbitrary files from it without unpacking the whole file. -Following is the steps to package your app into an `asar` archive: +Steps to package your app into an `asar` archive: -### 1. Install the asar utility +### 1. Install the asar Utility ```bash $ npm install -g asar @@ -24,9 +25,9 @@ $ npm install -g asar $ asar pack your-app app.asar ``` -## Using `asar` archives +## Using `asar` Archives -In Electron there are two sets of APIs: Node APIs provided by Node.js, and Web +In Electron there are two sets of APIs: Node APIs provided by Node.js and Web APIs provided by Chromium. Both APIs support reading files from `asar` archives. ### Node API @@ -77,8 +78,8 @@ win.loadUrl('file:///path/to/example.asar/static/index.html'); ### Web API -In a web page, files in archive can be requested with the `file:` protocol. Like -the Node API, `asar` archives are treated as directories. +In a web page, files in an archive can be requested with the `file:` protocol. +Like the Node API, `asar` archives are treated as directories. For example, to get a file with `$.get`: @@ -91,7 +92,7 @@ $.get('file:///path/to/example.asar/file.txt', function(data) { ``` -### Treating `asar` archive as normal file +### Treating an `asar` Archive as a Normal File For some cases like verifying the `asar` archive's checksum, we need to read the content of `asar` archive as file. For this purpose you can use the built-in @@ -108,21 +109,21 @@ Even though we tried hard to make `asar` archives in the Node API work like directories as much as possible, there are still limitations due to the low-level nature of the Node API. -### Archives are read only +### Archives Are Read-only The archives can not be modified so all Node APIs that can modify files will not work with `asar` archives. -### Working directory can not be set to directories in archive +### Working Directory Can Not Be Set to Directories in Archive Though `asar` archives are treated as directories, there are no actual directories in the filesystem, so you can never set the working directory to -directories in `asar` archives, passing them to `cwd` option of some APIs will -also cause errors. +directories in `asar` archives. Passing them as the `cwd` option of some APIs +will also cause errors. -### Extra unpacking on some APIs +### Extra Unpacking on Some APIs -Most `fs` APIs can read file or get file's information from `asar` archives +Most `fs` APIs can read a file or get a file's information from `asar` archives without unpacking, but for some APIs that rely on passing the real file path to underlying system calls, Electron will extract the needed file into a temporary file and pass the path of the temporary file to the APIs to make them @@ -135,14 +136,14 @@ APIs that requires extra unpacking are: * `fs.openSync` * `process.dlopen` - Used by `require` on native modules -### Fake stat information of `fs.stat` +### Fake Stat Information of `fs.stat` The `Stats` object returned by `fs.stat` and its friends on files in `asar` archives is generated by guessing, because those files do not exist on the filesystem. So you should not trust the `Stats` object except for getting file size and checking file type. -## Adding unpacked files in `asar` archive +## Adding Unpacked Files in `asar` Archive As stated above, some Node APIs will unpack the file to filesystem when calling, apart from the performance issues, it could also lead to false alerts diff --git a/docs/tutorial/debugging-main-process.md b/docs/tutorial/debugging-main-process.md index 857f26faa4d7..38c6e61ffcde 100644 --- a/docs/tutorial/debugging-main-process.md +++ b/docs/tutorial/debugging-main-process.md @@ -1,25 +1,26 @@ -# Debugging the main process +# Debugging the Main Process -The devtools of browser window can only debug the renderer process scripts. -(I.e. the web pages.) In order to provide a way to debug the scripts of -the main process, Electron has provided the `--debug` and `--debug-brk` -switches. +The browser window DevTools can only debug the renderer process scripts (i.e. +the web pages). In order to provide a way to debug the scripts from the main +process, Electron has provided the `--debug` and `--debug-brk` switches. -## Command line switches +## Command Line Switches + +Use the following command line switches to debug Electron's main process: ### `--debug=[port]` -When this switch is used Electron would listen for V8 debugger protocol -messages on `port`, the `port` is `5858` by default. +When this switch is used Electron will listen for V8 debugger protocol +messages on `port`. The default `port` is `5858`. ### `--debug-brk=[port]` Like `--debug` but pauses the script on the first line. -## Use node-inspector for debugging +## Use node-inspector for Debugging __Note:__ Electron uses node v0.11.13, which currently doesn't work very well -with node-inspector, and the main process would crash if you inspect the +with node-inspector, and the main process will crash if you inspect the `process` object under node-inspector's console. ### 1. Start the [node-inspector][node-inspector] server @@ -44,6 +45,6 @@ $ electron --debug-brk=5858 your/app ### 3. Load the debugger UI -Open http://127.0.0.1:8080/debug?port=5858 in the Chrome browser. +Open http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in the Chrome browser. [node-inspector]: https://github.com/node-inspector/node-inspector diff --git a/docs/tutorial/desktop-environment-integration.md b/docs/tutorial/desktop-environment-integration.md index 3f65df234dfe..3132edffcc27 100644 --- a/docs/tutorial/desktop-environment-integration.md +++ b/docs/tutorial/desktop-environment-integration.md @@ -1,17 +1,17 @@ -# Desktop environment integration +# Desktop Environment Integration -Different operating systems provide different features on integrating desktop -applications into their desktop environments, for example, on Windows -applications can put shortcuts in the JumpList of task bar, and on Mac +Different operating systems provide different features for integrating desktop +applications into their desktop environments. For example, on Windows, +applications can put shortcuts in the JumpList of task bar, and on Mac, applications can put a custom menu in the dock menu. -This guide introduces how to integrate your application into those desktop +This guide explains how to integrate your application into those desktop environments with Electron APIs. ## Recent documents (Windows & OS X) -Windows and OS X have provided easy access to recent documents opened by the -application via JumpList and dock menu. +Windows and OS X provide easy access to a list of recent documents opened by +the application via JumpList or dock menu, respectively. __JumpList:__ @@ -21,7 +21,7 @@ __Application dock menu:__ -To add a file to recent documents, you can use +To add a file to recent documents, you can use the [app.addRecentDocument][addrecentdocument] API: ```javascript @@ -29,31 +29,30 @@ var app = require('app'); app.addRecentDocument('/Users/USERNAME/Desktop/work.type'); ``` -And you can use [app.clearRecentDocuments](clearrecentdocuments) API to empty +And you can use [app.clearRecentDocuments][clearrecentdocuments] API to empty the recent documents list: ```javascript app.clearRecentDocuments(); ``` -### Windows notes +### Windows Notes In order to be able to use this feature on Windows, your application has to be -registered as handler of the file type of the document, otherwise the file won't -appear in JumpList even after you have added it. You can find everything on -registering your application in [Application Registration][app-registration]. +registered as a handler of the file type of the document, otherwise the file +won't appear in JumpList even after you have added it. You can find everything +on registering your application in [Application Registration][app-registration]. -When a user clicks a file from JumpList, a new instance of your application will -be started with the path of file appended in command line. +When a user clicks a file from the JumpList, a new instance of your application will be started with the path of the file added as a command line argument. -### OS X notes +### OS X Notes When a file is requested from the recent documents menu, the `open-file` event -of `app` module would be emitted for it. +of `app` module will be emitted for it. -## Custom dock menu (OS X) +## Custom Dock Menu (OS X) -OS X enables developers to specify a custom menu for dock, which usually +OS X enables developers to specify a custom menu for the dock, which usually contains some shortcuts for commonly used features of your application: __Dock menu of Terminal.app:__ @@ -70,14 +69,14 @@ var dockMenu = Menu.buildFromTemplate([ { label: 'New Window', click: function() { console.log('New Window'); } }, { label: 'New Window with Settings', submenu: [ { label: 'Basic' }, - { label: 'Pro'}, + { label: 'Pro'} ]}, - { label: 'New Command...'}, + { label: 'New Command...'} ]); app.dock.setMenu(dockMenu); ``` -## User tasks (Windows) +## User Tasks (Windows) On Windows you can specify custom actions in the `Tasks` category of JumpList, as quoted from MSDN: @@ -104,7 +103,7 @@ __Tasks of Internet Explorer:__ ![IE](http://i.msdn.microsoft.com/dynimg/IC420539.png) Unlike the dock menu in OS X which is a real menu, user tasks in Windows work -like application shortcuts that when user clicks a task a program would be +like application shortcuts such that when user clicks a task, a program will be executed with specified arguments. To set user tasks for your application, you can use @@ -119,12 +118,12 @@ app.setUserTasks([ iconPath: process.execPath, iconIndex: 0, title: 'New Window', - description: 'Create a new window', + description: 'Create a new window' } ]); ``` -To clean your tasks list, just call `app.setUserTasks` with empty array: +To clean your tasks list, just call `app.setUserTasks` with an empty array: ```javascript app.setUserTasks([]); @@ -134,23 +133,76 @@ The user tasks will still show even after your application closes, so the icon and program path specified for a task should exist until your application is uninstalled. -## Unity launcher shortcuts (Linux) +## Thumbnail Toolbars -In Unity, you can add custom entries to its launcher via modifying `.desktop` -file, see [Adding shortcuts to a launcher][unity-launcher]. +On Windows you can add a thumbnail toolbar with specified buttons in a taskbar +layout of an application window. It provides users a way to access to a +particular window's command without restoring or activating the window. + +From MSDN, it's illustrated: + +> This toolbar is simply the familiar standard toolbar common control. It has a +> maximum of seven buttons. Each button's ID, image, tooltip, and state are defined +> in a structure, which is then passed to the taskbar. The application can show, +> enable, disable, or hide buttons from the thumbnail toolbar as required by its +> current state. +> +> For example, Windows Media Player might offer standard media transport controls +> such as play, pause, mute, and stop. + +__Thumbnail toolbar of Windows Media Player:__ + +![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png) + +You can use [BrowserWindow.setThumbarButtons][setthumbarbuttons] to set thumbnail +toolbar in your application: + +``` +var BrowserWindow = require('browser-window'); +var path = require('path'); +var win = new BrowserWindow({ + width: 800, + height: 600 +}); +win.setThumbarButtons([ + { + tooltip: "button1", + icon: path.join(__dirname, 'button1.png'), + click: function() { console.log("button2 clicked"); } + }, + { + tooltip: "button2", + icon: path.join(__dirname, 'button2.png'), + flags:['enabled', 'dismissonclick'], + click: function() { console.log("button2 clicked."); } + } +]); +``` + +To clean thumbnail toolbar buttons, just call `BrowserWindow.setThumbarButtons` +with an empty array: + +```javascript +win.setThumbarButtons([]); +``` + +## Unity Launcher Shortcuts (Linux) + +In Unity, you can add custom entries to its launcher via modifying the `.desktop` +file, see [Adding Shortcuts to a Launcher][unity-launcher]. __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 & 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 that user having to +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. -The Unity DE also has a simililar feature that allows you to specify progress -bar in the lancher. +The Unity DE also has a similar feature that allows you to specify the progress +bar in the launcher. __Progress bar in taskbar button:__ @@ -168,14 +220,14 @@ var window = new BrowserWindow({...}); window.setProgressBar(0.5); ``` -## Represented file of window (OS X) +## Represented File of Window (OS X) On OS X a window can set its represented file, so the file's icon can show in -title bar, and when users Command-Click or Control-Click on the tile a path +the title bar and when users Command-Click or Control-Click on the tile a path popup will show. -You can also set edited state of a window so the file icon can indicate whether -the document in this window has been modified. +You can also set the edited state of a window so that the file icon can indicate +whether the document in this window has been modified. __Represented file popup menu:__ @@ -199,3 +251,4 @@ window.setDocumentEdited(true); [setdocumentedited]: ../api/browser-window.md#browserwindowsetdocumenteditededited [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 diff --git a/docs/tutorial/devtools-extension.md b/docs/tutorial/devtools-extension.md index 1e0a64ac3901..e9466f14b648 100644 --- a/docs/tutorial/devtools-extension.md +++ b/docs/tutorial/devtools-extension.md @@ -1,56 +1,57 @@ -# DevTools extension +# DevTools Extension -To make debugging more easy, Electron has added basic support for +To make debugging easier, Electron has basic support for the [Chrome DevTools Extension][devtools-extension]. -For most devtools extensions, you can simply download their source codes and use -the `BrowserWindow.addDevToolsExtension` API to load them, the loaded extensions +For most DevTools extensions you can simply download the source code and use +the `BrowserWindow.addDevToolsExtension` API to load them. The loaded extensions will be remembered so you don't need to call the API every time when creating a window. -For example to use the React DevTools Extension, first you need to download its -source code: +For example, to use the [React DevTools Extension](https://github.com/facebook/react-devtools) +, first you need to download its source code: ```bash $ cd /some-directory $ git clone --recursive https://github.com/facebook/react-devtools.git ``` -Then you can load it in Electron by opening the devtools in arbitray window, -and run this code in the console of devtools: +Then you can load the extension in Electron by opening DevTools in any window, +and running the following code in the DevTools console: ```javascript -require('remote').require('browser-window').addDevToolsExtension('/some-directory/react-devtools') +require('remote').require('browser-window').addDevToolsExtension('/some-directory/react-devtools'); ``` -To unload the extension, you can call `BrowserWindow.removeDevToolsExtension` -API with its name and it will disappear the next time you open the devtools: +To unload the extension, you can call the `BrowserWindow.removeDevToolsExtension` +API with its name and it will not load the next time you open the DevTools: ```javascript -require('remote').require('browser-window').removeDevToolsExtension('React Developer Tools') +require('remote').require('browser-window').removeDevToolsExtension('React Developer Tools'); ``` -## Format of devtools extension +## Format of DevTools Extension -Ideally all devtools extension written for Chrome browser can be loaded by -Electron, but they have to be in a plain directory, for those packaged `crx` -extensions, there is no way in Electron to load them unless you find a way to -extract them into a directory. +Ideally all DevTools extensions written for the Chrome browser can be loaded by +Electron, but they have to be in a plain directory. For those packaged with +`crx` extensions, there is no way for Electron to load them unless you find a +way to extract them into a directory. -## Background pages +## Background Pages -Currently Electron doesn't support the background pages of chrome extensions, -so for some devtools extensions that rely on this feature, they may not work -well in Electron +Currently Electron doesn't support the background pages feature in Chrome +extensions, so some DevTools extensions that rely on this feature may +not work in Electron. ## `chrome.*` APIs -Some chrome extensions use `chrome.*` APIs for some features, there is some -effort to implement those APIs in Electron to make them work, but we have -only implemented few for now. +Some Chrome extensions may use `chrome.*` APIs for features and while there has +been some effort to implement those APIs in Electron, not all have been +implemented. -So if the devtools extension is using APIs other than `chrome.devtools.*`, it is -very likely to fail, but you can report those extensions in the issues tracker -so we can add support for those APIs. +Given that not all `chrome.*` APIs are implemented if the DevTools extension is +using APIs other than `chrome.devtools.*`, the extension is very likely not to +work. You can report failing extensions in the issue tracker so that we can add +support for those APIs. [devtools-extension]: https://developer.chrome.com/extensions/devtools diff --git a/docs/tutorial/online-offline-events.md b/docs/tutorial/online-offline-events.md index a8d669454069..88f9a32f2ec6 100644 --- a/docs/tutorial/online-offline-events.md +++ b/docs/tutorial/online-offline-events.md @@ -36,9 +36,9 @@ _online-status.html_ ``` -There may be instances where one wants to respond to these events in the -main process as well. The main process however does not have a -`navigator` object and thus cannot detect these events directly. Using +There may be instances where you want to respond to these events in the +main process as well. The main process however does not have a +`navigator` object and thus cannot detect these events directly. Using Electron's inter-process communication utilities, the events can be forwarded to the main process and handled as needed, as shown in the following example. diff --git a/docs/tutorial/quick-start.md b/docs/tutorial/quick-start.md index d9f6504eb1fa..93cd0ebc5bd9 100644 --- a/docs/tutorial/quick-start.md +++ b/docs/tutorial/quick-start.md @@ -1,50 +1,54 @@ -# Quick start +# Quick Start -## Introduction +Electron enables you to create desktop applications with pure JavaScript by +providing a runtime with rich native (operating system) APIs. You could see it +as a variant of the io.js runtime that is focused on desktop applications +instead of web servers. -Electron enables you to create desktop applications with pure JavaScript by providing a runtime with rich native APIs. You could see it as a variant of the io.js runtime which is focused on desktop applications instead of web servers. +This doesn't mean Electron is a JavaScript binding to graphical user interface +(GUI) libraries. Instead, Electron uses web pages as its GUI, so you could also +see it as a minimal Chromium browser, controlled by JavaScript. -It doesn't mean Electron is a JavaScript binding to GUI libraries. Instead, -Electron uses web pages as its GUI, so you could also see it as a minimal -Chromium browser, controlled by JavaScript. - -### Main process +### Main Process In Electron, the process that runs `package.json`'s `main` script is called -__the main process__. The script that runs in the main process, can display GUI by -creating web pages. +__the main process__. The script that runs in the main process can display a GUI +by creating web pages. -### Renderer process +### Renderer Process Since Electron uses Chromium for displaying web pages, Chromium's -multi-processes architecture is also used. Each web page in Electron runs in +multi-process architecture is also used. Each web page in Electron runs in its own process, which is called __the renderer process__. -In normal browsers web pages usually run in a sandboxed environment and are not -allowed access to native resources. Electron users however, have the power to use +In normal browsers, web pages usually run in a sandboxed environment and are not +allowed access to native resources. Electron users, however, have the power to use io.js APIs in web pages allowing lower level operating system interactions. -### Differences between main process and renderer process +### Differences Between Main Process and Renderer Process -The main process creates web pages by creating `BrowserWindow` instances. Each `BrowserWindow` instance runs the web page in its own renderer process. When a `BrowserWindow` instance is destroyed, the corresponding renderer process -would also be terminated. +The main process creates web pages by creating `BrowserWindow` instances. Each +`BrowserWindow` instance runs the web page in its own renderer process. When a +`BrowserWindow` instance is destroyed, the corresponding renderer process +is also terminated. The main process manages all web pages and their corresponding renderer -processes, each renderer process is isolated and only cares +processes. Each renderer process is isolated and only cares about the web page running in it. -In web pages, it is not allowed to call native GUI related APIs because managing -native GUI resources in web pages is very dangerous and easy to leak resources. -If you want to do GUI operations in web pages, you have to communicate with -the main process to do it there. +In web pages, calling native GUI related APIs is not allowed because managing +native GUI resources in web pages is very dangerous and it is easy to leak +resources. If you want to perform GUI operations in a web page, the renderer +process of the web page must communicate with the main process to request that +the main process perform those operations. In Electron, we have provided the [ipc](../api/ipc-renderer.md) module for -communication between main process and renderer process. And there is also a +communication between the main process and renderer process. There is also a [remote](../api/remote.md) module for RPC style communication. -## Write your first Electron app +## Write your First Electron App -Generally, an Electron app would be structured like this: +Generally, an Electron app is structured like this: ```text your-app/ @@ -55,7 +59,7 @@ your-app/ The format of `package.json` is exactly the same as that of Node's modules, and the script specified by the `main` field is the startup script of your app, -which will run on the main process. An example of your `package.json` might look +which will run the main process. An example of your `package.json` might look like this: ```json @@ -66,6 +70,9 @@ like this: } ``` +__Note__: If the `main` field is not present in `package.json`, Electron will +attempt to load an `index.js`. + The `main.js` should create windows and handle system events, a typical example being: @@ -77,17 +84,20 @@ var BrowserWindow = require('browser-window'); // Module to create native brows require('crash-reporter').start(); // Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the javascript object is GCed. +// be closed automatically when the JavaScript object is garbage collected. var mainWindow = null; // Quit when all windows are closed. app.on('window-all-closed', function() { - if (process.platform != 'darwin') + // On OS X it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform != 'darwin') { app.quit(); + } }); -// This method will be called when Electron has done everything -// initialization and ready for creating browser windows. +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. app.on('ready', function() { // Create the browser window. mainWindow = new BrowserWindow({width: 800, height: 600}); @@ -95,6 +105,9 @@ app.on('ready', function() { // and load the index.html of the app. mainWindow.loadUrl('file://' + __dirname + '/index.html'); + // Open the DevTools. + mainWindow.openDevTools(); + // Emitted when the window is closed. mainWindow.on('closed', function() { // Dereference the window object, usually you would store windows @@ -123,24 +136,43 @@ Finally the `index.html` is the web page you want to show: ## Run your app -After you're done writing your app, you can create a distribution by -following the [Application distribution](./application-distribution.md) guide -and then execute the packaged app. You can also just use the downloaded -Electron binary to execute your app directly. +Once you've created your initial `main.js`, `index.html`, and `package.json` files, +you'll probably want to try running your app locally to test it and make sure it's +working as expected. -On Windows: +### electron-prebuilt -```cmd +If you've installed `electron-prebuilt` globally with `npm`, then you need only +run the following in your app's source directory: + +```bash +electron . +``` + +If you've installed it locally, then run: + +```bash +./node_modules/.bin/electron . +``` + +### Manually Downloaded Electron Binary + +If you downloaded Electron manually, you can also just use the included +binary to execute your app directly. + +#### Windows + +```bash $ .\electron\electron.exe your-app\ ``` -On Linux: +#### Linux ```bash $ ./electron/electron your-app/ ``` -On OS X: +#### OS X ```bash $ ./Electron.app/Contents/MacOS/Electron your-app/ @@ -148,3 +180,9 @@ $ ./Electron.app/Contents/MacOS/Electron your-app/ `Electron.app` here is part of the Electron's release package, you can download it from [here](https://github.com/atom/electron/releases). + +### Run as a distribution + +After you're done writing your app, you can create a distribution by +following the [Application Distribution](./application-distribution.md) guide +and then executing the packaged app. diff --git a/docs/tutorial/using-native-node-modules.md b/docs/tutorial/using-native-node-modules.md index 643b4915b7ed..c338494cde2b 100644 --- a/docs/tutorial/using-native-node-modules.md +++ b/docs/tutorial/using-native-node-modules.md @@ -1,59 +1,62 @@ -# Using native Node modules +# Using Native Node Modules The native Node modules are supported by Electron, but since Electron is using a different V8 version from official Node, you have to manually specify the location of Electron's headers when building native modules. -## Native Node module compatibility +## Native Node Module Compatibility Since Node v0.11.x there were vital changes in the V8 API. So generally all -native modules written for Node v0.10.x wouldn't work for Node v0.11.x. And -because Electron internally uses Node v0.11.13, it carries with the same -problem. +native modules written for Node v0.10.x won't work for newer Node or io.js +versions. And because Electron internally uses __io.js v3.1.0__, it has the +same problem. -To solve this, you should use modules that support Node v0.11.x, +To solve this, you should use modules that support Node v0.11.x or later, [many modules](https://www.npmjs.org/browse/depended/nan) do support both now. For old modules that only support Node v0.10.x, you should use the -[nan](https://github.com/rvagg/nan) module to port it to v0.11.x. +[nan](https://github.com/rvagg/nan) module to port it to v0.11.x or later +versions of Node or io.js. -## How to install native modules +## How to Install Native Modules + +Three ways to install native modules: ### The Easy Way -The most straightforward way to rebuild native modules is via the -[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) package, +The most straightforward way to rebuild native modules is via the +[`electron-rebuild`](https://github.com/paulcbetts/electron-rebuild) package, which handles the manual steps of downloading headers and building native modules: ```sh npm install --save-dev electron-rebuild -# Every time you run npm install, run this too -./node_modules/.bin/electron-rebuild +# Every time you run npm install, run this +node ./node_modules/.bin/electron-rebuild ``` -### The node-gyp way +### The node-gyp Way To build Node modules with headers of Electron, you need to tell `node-gyp` where to download headers and which version to use: ```bash $ cd /path-to-module/ -$ HOME=~/.electron-gyp node-gyp rebuild --target=0.25.0 --arch=ia64 --dist-url=https://atom.io/download/atom-shell +$ HOME=~/.electron-gyp node-gyp rebuild --target=0.29.1 --arch=x64 --dist-url=https://atom.io/download/atom-shell ``` The `HOME=~/.electron-gyp` changes where to find development headers. The -`--target=0.25.0` is version of Electron. The `--dist-url=...` specifies -where to download the headers. The `--arch=ia64` says the module is built for +`--target=0.29.1` is version of Electron. The `--dist-url=...` specifies +where to download the headers. The `--arch=x64` says the module is built for 64bit system. -### The npm way +### The npm Way -You can also use `npm` to install modules, the steps are exactly the same with +You can also use `npm` to install modules. The steps are exactly the same with Node modules, except that you need to setup some environment variables: ```bash export npm_config_disturl=https://atom.io/download/atom-shell -export npm_config_target=0.25.0 +export npm_config_target=0.29.1 export npm_config_arch=x64 HOME=~/.electron-gyp npm install module-name ``` diff --git a/docs/tutorial/using-pepper-flash-plugin.md b/docs/tutorial/using-pepper-flash-plugin.md new file mode 100644 index 000000000000..5c8820c2fad4 --- /dev/null +++ b/docs/tutorial/using-pepper-flash-plugin.md @@ -0,0 +1,67 @@ +# Using Pepper Flash Plugin + +Electron now supports the Pepper Flash plugin. To use the Pepper Flash plugin in +Electron, you should manually specify the location of the Pepper Flash plugin +and then enable it in your application. + +## Prepare a Copy of Flash Plugin + +On OS X and Linux, the details of the Pepper Flash plugin can be found by +navigating to `chrome://plugins` in the Chrome browser. Its location and version +are useful for Electron's Pepper Flash support. You can also copy it to another +location. + +## Add Electron Switch + +You can directly add `--ppapi-flash-path` and `ppapi-flash-version` to the +Electron command line or by using the `app.commandLine.appendSwitch` method +before the app ready event. Also, add the `plugins` switch of `browser-window`. +For example: + +```javascript +var app = require('app'); +var BrowserWindow = require('browser-window'); + +// Report crashes to our server. +require('crash-reporter').start(); + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the javascript object is GCed. +var mainWindow = null; + +// Quit when all windows are closed. +app.on('window-all-closed', function() { + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// Specify flash path. +// On Windows, it might be /path/to/pepflashplayer.dll +// On Mac, /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 +}); +``` + +## Enable Flash Plugin in a `` Tag + +Add `plugins` attribute to `` tag. + +```html + +``` diff --git a/docs/tutorial/using-selenium-and-webdriver.md b/docs/tutorial/using-selenium-and-webdriver.md index 9a0f6b8116a9..ebc6d2fa7801 100644 --- a/docs/tutorial/using-selenium-and-webdriver.md +++ b/docs/tutorial/using-selenium-and-webdriver.md @@ -8,21 +8,15 @@ From [ChromeDriver - WebDriver for Chrome][chrome-driver]: > implements WebDriver's wire protocol for Chromium. It is being developed by > members of the Chromium and WebDriver teams. -In Electron's [releases](https://github.com/atom/electron/releases) page you -can find archives of `chromedriver`, there is no difference between Electron's -distribution of `chromedriver` and upstream ones, so in order to use -`chromedriver` together with Electron, you will need some special setup. - -Also notice that only minor version update releases (e.g. `vX.X.0` releases) -include `chromedriver` archives, because `chromedriver` doesn't change as -frequent as Electron itself. +In order to use `chromedriver` with Electron you have to tell it where to +find Electron and make it think Electron is the Chrome browser. ## Setting up with WebDriverJs -[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) provided +[WebDriverJs](https://code.google.com/p/selenium/wiki/WebDriverJs) provides a Node package for testing with web driver, we will use it as an example. -### 1. Start chrome driver +### 1. Start ChromeDriver First you need to download the `chromedriver` binary, and run it: @@ -40,7 +34,7 @@ Remember the port number `9515`, which will be used later $ npm install selenium-webdriver ``` -### 3. Connect to chrome driver +### 3. Connect to ChromeDriver The usage of `selenium-webdriver` with Electron is basically the same with upstream, except that you have to manually specify how to connect chrome driver @@ -49,14 +43,14 @@ and where to find Electron's binary: ```javascript var 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(); +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'); @@ -70,4 +64,59 @@ driver.wait(function() { driver.quit(); ``` +## Setting up with WebdriverIO + +[WebdriverIO](http://webdriver.io/) provides a Node package for testing with web +driver. + +### 1. Start ChromeDriver + +First you need to download the `chromedriver` binary, and run it: + +```bash +$ chromedriver --url-base=/wd/hub --port=9515 +Starting ChromeDriver (v2.10.291558) on port 9515 +Only local connections are allowed. +``` + +Remember the port number `9515`, which will be used later + +### 2. Install WebdriverIO + +```bash +$ npm install webdriverio +``` + +### 3. Connect to chrome driver + +```javascript +var 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.app/Electron'} // Path to your Electron binary. + } +}; + +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(); +``` + +## Workflow + +To test your application without rebuilding Electron, simply +[place](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) +your app source into Electron's resource directory. + [chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/ diff --git a/filenames.gypi b/filenames.gypi index ebd1fb41aaf2..99d6bc6d50d3 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -20,6 +20,7 @@ 'atom/browser/api/lib/menu-item.coffee', 'atom/browser/api/lib/navigation-controller.coffee', 'atom/browser/api/lib/power-monitor.coffee', + 'atom/browser/api/lib/power-save-blocker.coffee', 'atom/browser/api/lib/protocol.coffee', 'atom/browser/api/lib/screen.coffee', 'atom/browser/api/lib/tray.coffee', @@ -33,9 +34,7 @@ 'atom/common/api/lib/callbacks-registry.coffee', 'atom/common/api/lib/clipboard.coffee', 'atom/common/api/lib/crash-reporter.coffee', - 'atom/common/api/lib/id-weak-map.coffee', 'atom/common/api/lib/native-image.coffee', - 'atom/common/api/lib/original-fs.coffee', 'atom/common/api/lib/shell.coffee', 'atom/common/lib/init.coffee', 'atom/renderer/lib/chrome-api.coffee', @@ -58,18 +57,20 @@ 'lib_sources': [ 'atom/app/atom_content_client.cc', 'atom/app/atom_content_client.h', - 'atom/app/atom_main_args.cc', - 'atom/app/atom_main_args.h', 'atom/app/atom_main_delegate.cc', 'atom/app/atom_main_delegate.h', 'atom/app/atom_main_delegate_mac.mm', 'atom/app/node_main.cc', 'atom/app/node_main.h', + 'atom/app/uv_task_runner.cc', + 'atom/app/uv_task_runner.h', 'atom/browser/api/atom_api_app.cc', 'atom/browser/api/atom_api_app.h', 'atom/browser/api/atom_api_auto_updater.cc', 'atom/browser/api/atom_api_auto_updater.h', 'atom/browser/api/atom_api_content_tracing.cc', + 'atom/browser/api/atom_api_cookies.cc', + 'atom/browser/api/atom_api_cookies.h', 'atom/browser/api/atom_api_dialog.cc', 'atom/browser/api/atom_api_global_shortcut.cc', 'atom/browser/api/atom_api_global_shortcut.h', @@ -81,10 +82,14 @@ 'atom/browser/api/atom_api_menu_mac.mm', 'atom/browser/api/atom_api_power_monitor.cc', 'atom/browser/api/atom_api_power_monitor.h', + 'atom/browser/api/atom_api_power_save_blocker.cc', + 'atom/browser/api/atom_api_power_save_blocker.h', 'atom/browser/api/atom_api_protocol.cc', 'atom/browser/api/atom_api_protocol.h', 'atom/browser/api/atom_api_screen.cc', 'atom/browser/api/atom_api_screen.h', + 'atom/browser/api/atom_api_session.cc', + 'atom/browser/api/atom_api_session.h', 'atom/browser/api/atom_api_tray.cc', 'atom/browser/api/atom_api_tray.h', 'atom/browser/api/atom_api_web_contents.cc', @@ -96,6 +101,10 @@ 'atom/browser/api/event.h', 'atom/browser/api/event_emitter.cc', 'atom/browser/api/event_emitter.h', + 'atom/browser/api/trackable_object.cc', + 'atom/browser/api/trackable_object.h', + 'atom/browser/api/frame_subscriber.cc', + 'atom/browser/api/frame_subscriber.h', 'atom/browser/auto_updater.cc', 'atom/browser/auto_updater.h', 'atom/browser/auto_updater_delegate.h', @@ -108,22 +117,28 @@ 'atom/browser/atom_browser_client.h', 'atom/browser/atom_browser_context.cc', 'atom/browser/atom_browser_context.h', + 'atom/browser/atom_download_manager_delegate.cc', + 'atom/browser/atom_download_manager_delegate.h', 'atom/browser/atom_browser_main_parts.cc', 'atom/browser/atom_browser_main_parts.h', 'atom/browser/atom_browser_main_parts_linux.cc', 'atom/browser/atom_browser_main_parts_mac.mm', 'atom/browser/atom_javascript_dialog_manager.cc', 'atom/browser/atom_javascript_dialog_manager.h', - 'atom/browser/atom_resource_dispatcher_host_delegate.cc', - 'atom/browser/atom_resource_dispatcher_host_delegate.h', + 'atom/browser/atom_quota_permission_context.cc', + 'atom/browser/atom_quota_permission_context.h', 'atom/browser/atom_speech_recognition_manager_delegate.cc', 'atom/browser/atom_speech_recognition_manager_delegate.h', + 'atom/browser/bridge_task_runner.cc', + 'atom/browser/bridge_task_runner.h', 'atom/browser/browser.cc', 'atom/browser/browser.h', 'atom/browser/browser_linux.cc', 'atom/browser/browser_mac.mm', 'atom/browser/browser_win.cc', 'atom/browser/browser_observer.h', + 'atom/browser/common_web_contents_delegate.cc', + 'atom/browser/common_web_contents_delegate.h', 'atom/browser/javascript_environment.cc', 'atom/browser/javascript_environment.h', 'atom/browser/mac/atom_application.h', @@ -137,22 +152,32 @@ 'atom/browser/native_window_mac.h', 'atom/browser/native_window_mac.mm', 'atom/browser/native_window_observer.h', - 'atom/browser/net/adapter_request_job.cc', - 'atom/browser/net/adapter_request_job.h', 'atom/browser/net/asar/asar_protocol_handler.cc', 'atom/browser/net/asar/asar_protocol_handler.h', 'atom/browser/net/asar/url_request_asar_job.cc', 'atom/browser/net/asar/url_request_asar_job.h', 'atom/browser/net/atom_url_request_job_factory.cc', 'atom/browser/net/atom_url_request_job_factory.h', + 'atom/browser/net/http_protocol_handler.cc', + 'atom/browser/net/http_protocol_handler.h', + 'atom/browser/net/js_asker.cc', + 'atom/browser/net/js_asker.h', + 'atom/browser/net/url_request_async_asar_job.cc', + 'atom/browser/net/url_request_async_asar_job.h', 'atom/browser/net/url_request_string_job.cc', 'atom/browser/net/url_request_string_job.h', 'atom/browser/net/url_request_buffer_job.cc', 'atom/browser/net/url_request_buffer_job.h', + 'atom/browser/net/url_request_fetch_job.cc', + 'atom/browser/net/url_request_fetch_job.h', + 'atom/browser/node_debugger.cc', + 'atom/browser/node_debugger.h', 'atom/browser/ui/accelerator_util.cc', 'atom/browser/ui/accelerator_util.h', 'atom/browser/ui/accelerator_util_mac.mm', 'atom/browser/ui/accelerator_util_views.cc', + 'atom/browser/ui/atom_menu_model.cc', + 'atom/browser/ui/atom_menu_model.h', 'atom/browser/ui/cocoa/atom_menu_controller.h', 'atom/browser/ui/cocoa/atom_menu_controller.mm', 'atom/browser/ui/cocoa/event_processing_window.h', @@ -162,8 +187,9 @@ 'atom/browser/ui/file_dialog_mac.mm', 'atom/browser/ui/file_dialog_win.cc', 'atom/browser/ui/message_box.h', + 'atom/browser/ui/message_box_gtk.cc', 'atom/browser/ui/message_box_mac.mm', - 'atom/browser/ui/message_box_views.cc', + 'atom/browser/ui/message_box_win.cc', 'atom/browser/ui/tray_icon.cc', 'atom/browser/ui/tray_icon.h', 'atom/browser/ui/tray_icon_gtk.cc', @@ -182,22 +208,34 @@ 'atom/browser/ui/views/menu_delegate.h', 'atom/browser/ui/views/menu_layout.cc', 'atom/browser/ui/views/menu_layout.h', + 'atom/browser/ui/views/native_frame_view.cc', + 'atom/browser/ui/views/native_frame_view.h', 'atom/browser/ui/views/submenu_button.cc', 'atom/browser/ui/views/submenu_button.h', 'atom/browser/ui/views/win_frame_view.cc', 'atom/browser/ui/views/win_frame_view.h', + 'atom/browser/ui/win/atom_desktop_window_tree_host_win.cc', + 'atom/browser/ui/win/atom_desktop_window_tree_host_win.h', + 'atom/browser/ui/win/message_handler_delegate.cc', + 'atom/browser/ui/win/message_handler_delegate.h', 'atom/browser/ui/win/notify_icon_host.cc', 'atom/browser/ui/win/notify_icon_host.h', 'atom/browser/ui/win/notify_icon.cc', 'atom/browser/ui/win/notify_icon.h', + 'atom/browser/ui/win/taskbar_host.cc', + 'atom/browser/ui/win/taskbar_host.h', 'atom/browser/ui/x/window_state_watcher.cc', '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_view_manager.cc', - 'atom/browser/web_view_manager.h', + 'atom/browser/web_contents_preferences.cc', + 'atom/browser/web_contents_preferences.h', 'atom/browser/web_dialog_helper.cc', 'atom/browser/web_dialog_helper.h', + 'atom/browser/web_view_guest_delegate.cc', + 'atom/browser/web_view_guest_delegate.h', + 'atom/browser/web_view_manager.cc', + 'atom/browser/web_view_manager.h', 'atom/browser/window_list.cc', 'atom/browser/window_list.h', 'atom/browser/window_list_observer.h', @@ -205,8 +243,6 @@ 'atom/common/api/atom_api_asar.cc', 'atom/common/api/atom_api_clipboard.cc', 'atom/common/api/atom_api_crash_reporter.cc', - 'atom/common/api/atom_api_id_weak_map.cc', - 'atom/common/api/atom_api_id_weak_map.h', 'atom/common/api/atom_api_native_image.cc', 'atom/common/api/atom_api_native_image.h', 'atom/common/api/atom_api_native_image_mac.mm', @@ -214,6 +250,10 @@ 'atom/common/api/atom_api_v8_util.cc', 'atom/common/api/atom_bindings.cc', 'atom/common/api/atom_bindings.h', + 'atom/common/api/event_emitter_caller.cc', + 'atom/common/api/event_emitter_caller.h', + 'atom/common/api/locker.cc', + 'atom/common/api/locker.h', 'atom/common/api/object_life_monitor.cc', 'atom/common/api/object_life_monitor.h', 'atom/common/asar/archive.cc', @@ -222,6 +262,8 @@ 'atom/common/asar/asar_util.h', 'atom/common/asar/scoped_temporary_file.cc', 'atom/common/asar/scoped_temporary_file.h', + 'atom/common/atom_command_line.cc', + 'atom/common/atom_command_line.h', 'atom/common/common_message_generator.cc', 'atom/common/common_message_generator.h', 'atom/common/crash_reporter/crash_reporter.cc', @@ -241,9 +283,16 @@ 'atom/common/draggable_region.cc', 'atom/common/draggable_region.h', '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/linux/application_info.cc', 'atom/common/native_mate_converters/accelerator_converter.cc', 'atom/common/native_mate_converters/accelerator_converter.h', + 'atom/common/native_mate_converters/blink_converter.cc', + 'atom/common/native_mate_converters/blink_converter.h', + 'atom/common/native_mate_converters/callback.h', 'atom/common/native_mate_converters/file_path_converter.h', 'atom/common/native_mate_converters/gfx_converter.cc', 'atom/common/native_mate_converters/gfx_converter.h', @@ -281,6 +330,8 @@ 'atom/renderer/atom_renderer_client.h', 'atom/renderer/guest_view_container.cc', 'atom/renderer/guest_view_container.h', + 'atom/renderer/node_array_buffer_bridge.cc', + 'atom/renderer/node_array_buffer_bridge.h', 'atom/utility/atom_content_utility_client.cc', 'atom/utility/atom_content_utility_client.h', 'chromium_src/chrome/browser/browser_process.cc', @@ -311,6 +362,8 @@ 'chromium_src/chrome/browser/printing/printer_query.h', 'chromium_src/chrome/browser/printing/printing_message_filter.cc', 'chromium_src/chrome/browser/printing/printing_message_filter.h', + 'chromium_src/chrome/browser/printing/print_preview_message_handler.cc', + 'chromium_src/chrome/browser/printing/print_preview_message_handler.h', 'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc', 'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h', 'chromium_src/chrome/browser/renderer_host/pepper/pepper_broker_message_filter.cc', @@ -338,6 +391,8 @@ 'chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc', 'chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h', 'chromium_src/chrome/common/chrome_utility_messages.h', + 'chromium_src/chrome/common/pref_names.cc', + 'chromium_src/chrome/common/pref_names.h', 'chromium_src/chrome/common/print_messages.cc', 'chromium_src/chrome/common/print_messages.h', 'chromium_src/chrome/common/tts_messages.h', @@ -369,6 +424,10 @@ 'chromium_src/chrome/utility/utility_message_handler.h', 'chromium_src/library_loaders/libspeechd_loader.cc', 'chromium_src/library_loaders/libspeechd.h', + 'chromium_src/net/test/embedded_test_server/stream_listen_socket.cc', + 'chromium_src/net/test/embedded_test_server/stream_listen_socket.h', + 'chromium_src/net/test/embedded_test_server/tcp_listen_socket.cc', + 'chromium_src/net/test/embedded_test_server/tcp_listen_socket.h', '<@(native_mate_files)', '<(SHARED_INTERMEDIATE_DIR)/atom_natives.h', ], @@ -378,8 +437,8 @@ 'chromium_src/chrome/browser/ui/views/color_chooser_win.cc', 'chromium_src/chrome/browser/printing/pdf_to_emf_converter.cc', 'chromium_src/chrome/browser/printing/pdf_to_emf_converter.h', - 'chromium_src/chrome/utility/printing_handler.cc', - 'chromium_src/chrome/utility/printing_handler.h', + 'chromium_src/chrome/utility/printing_handler_win.cc', + 'chromium_src/chrome/utility/printing_handler_win.h', ], 'framework_sources': [ 'atom/app/atom_library_main.h', @@ -393,7 +452,6 @@ 'sk', 'sl', 'sr', 'sv', 'sw', 'ta', 'te', 'th', 'tr', 'uk', 'vi', 'zh-CN', 'zh-TW', ], - 'atom_source_root': ' /dev/null; then + curl -L --fail "${CDS_FULL_URL}" -o "${CDS_OUTPUT}" || \ + rm -rf "${CDS_OUT_DIR}" +elif which wget > /dev/null; then + wget "${CDS_FULL_URL}" -O "${CDS_OUTPUT}" || rm -rf "${CDS_OUT_DIR}" +else + echo "Neither curl nor wget found. Please install one of these." + exit 1 +fi +if [ -f "${CDS_OUTPUT}" ]; then + rm -rf "${LLVM_BUILD_DIR}" + mkdir -p "${LLVM_BUILD_DIR}" + tar -xzf "${CDS_OUTPUT}" -C "${LLVM_BUILD_DIR}" + echo clang "${PACKAGE_VERSION}" unpacked + echo "${PACKAGE_VERSION}" > "${STAMP_FILE}" + rm -rf "${CDS_OUT_DIR}" + exit 0 +else + echo Did not find prebuilt clang "${PACKAGE_VERSION}", building +fi diff --git a/script/update.py b/script/update.py index aaa07526c05f..abb3756ca365 100755 --- a/script/update.py +++ b/script/update.py @@ -1,10 +1,12 @@ #!/usr/bin/env python import os +import platform import subprocess import sys -from lib.config import get_target_arch +from lib.config import get_target_arch, PLATFORM +from lib.util import get_host_arch SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) @@ -13,6 +15,10 @@ SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def main(): os.chdir(SOURCE_ROOT) + if PLATFORM != 'win32' and platform.architecture()[0] != '64bit': + print 'Electron is required to be built on a 64bit machine' + return 1 + update_external_binaries() return update_gyp() @@ -35,18 +41,29 @@ def update_gyp(): def run_gyp(target_arch, component): + env = os.environ.copy() + if PLATFORM == 'linux' and target_arch != get_host_arch(): + env['GYP_CROSSCOMPILE'] = '1' + elif PLATFORM == 'win32': + env['GYP_MSVS_VERSION'] = '2013' python = sys.executable if sys.platform == 'cygwin': # Force using win32 python on cygwin. python = os.path.join('vendor', 'python_26', 'python.exe') gyp = os.path.join('vendor', 'brightray', 'vendor', 'gyp', 'gyp_main.py') + gyp_pylib = os.path.join(os.path.dirname(gyp), 'pylib') + # Avoid using the old gyp lib in system. + env['PYTHONPATH'] = os.path.pathsep.join([gyp_pylib, + env.get('PYTHONPATH', '')]) defines = [ '-Dlibchromiumcontent_component={0}'.format(component), '-Dtarget_arch={0}'.format(target_arch), + '-Dhost_arch={0}'.format(get_host_arch()), '-Dlibrary=static_library', ] return subprocess.call([python, gyp, '-f', 'ninja', '--depth', '.', - 'atom.gyp', '-Icommon.gypi'] + defines) + 'atom.gyp', '-Icommon.gypi'] + defines, env=env) + if __name__ == '__main__': sys.exit(main()) diff --git a/script/upload-checksums.py b/script/upload-checksums.py index c71425126363..776a5debdb64 100755 --- a/script/upload-checksums.py +++ b/script/upload-checksums.py @@ -40,6 +40,7 @@ def get_files_list(version): return [ 'node-{0}.tar.gz'.format(version), 'iojs-{0}.tar.gz'.format(version), + 'iojs-{0}-headers.tar.gz'.format(version), 'node.lib', 'x64/node.lib', 'win-x86/iojs.lib', diff --git a/script/upload-index-json.py b/script/upload-index-json.py index dfcbc835915c..671408fa9219 100755 --- a/script/upload-index-json.py +++ b/script/upload-index-json.py @@ -8,7 +8,7 @@ from lib.util import atom_gyp, execute, s3put, scoped_cwd SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) -OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'R') +OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'D') PROJECT_NAME = atom_gyp()['project_name%'] PRODUCT_NAME = atom_gyp()['product_name%'] diff --git a/script/upload-node-headers.py b/script/upload-node-headers.py index 38cb2f7e16f5..230b08ed5ac7 100755 --- a/script/upload-node-headers.py +++ b/script/upload-node-headers.py @@ -40,11 +40,15 @@ def main(): args = parse_args() node_headers_dir = os.path.join(DIST_DIR, 'node-{0}'.format(args.version)) iojs_headers_dir = os.path.join(DIST_DIR, 'iojs-{0}'.format(args.version)) + iojs2_headers_dir = os.path.join(DIST_DIR, + 'iojs-{0}-headers'.format(args.version)) copy_headers(node_headers_dir) create_header_tarball(node_headers_dir) copy_headers(iojs_headers_dir) create_header_tarball(iojs_headers_dir) + copy_headers(iojs2_headers_dir) + create_header_tarball(iojs2_headers_dir) # Upload node's headers to S3. bucket, access_key, secret_key = s3_config() diff --git a/script/upload.py b/script/upload.py index 281300554ef3..6fc421e6b7a2 100755 --- a/script/upload.py +++ b/script/upload.py @@ -7,15 +7,14 @@ import subprocess import sys import tempfile -from lib.config import PLATFORM, get_target_arch +from lib.config import PLATFORM, get_target_arch, get_chromedriver_version from lib.util import atom_gyp, execute, get_atom_shell_version, parse_version, \ - get_chromedriver_version, scoped_cwd + scoped_cwd from lib.github import GitHub ATOM_SHELL_REPO = 'atom/electron' ATOM_SHELL_VERSION = get_atom_shell_version() -CHROMEDRIVER_VERSION = get_chromedriver_version() PROJECT_NAME = atom_gyp()['project_name%'] PRODUCT_NAME = atom_gyp()['product_name%'] @@ -31,9 +30,6 @@ SYMBOLS_NAME = '{0}-{1}-{2}-{3}-symbols.zip'.format(PROJECT_NAME, ATOM_SHELL_VERSION, PLATFORM, get_target_arch()) -CHROMEDRIVER_NAME = 'chromedriver-{0}-{1}-{2}.zip'.format(CHROMEDRIVER_VERSION, - PLATFORM, - get_target_arch()) MKSNAPSHOT_NAME = 'mksnapshot-{0}-{1}-{2}.zip'.format(ATOM_SHELL_VERSION, PLATFORM, get_target_arch()) @@ -42,20 +38,29 @@ MKSNAPSHOT_NAME = 'mksnapshot-{0}-{1}-{2}.zip'.format(ATOM_SHELL_VERSION, def main(): args = parse_args() - if not dist_newer_than_head(): - create_dist = os.path.join(SOURCE_ROOT, 'script', 'create-dist.py') - execute([sys.executable, create_dist]) + if not args.publish_release: + if not dist_newer_than_head(): + create_dist = os.path.join(SOURCE_ROOT, 'script', 'create-dist.py') + execute([sys.executable, create_dist]) - build_version = get_atom_shell_build_version() - if not ATOM_SHELL_VERSION.startswith(build_version): - error = 'Tag name ({0}) should match build version ({1})\n'.format( - ATOM_SHELL_VERSION, build_version) - sys.stderr.write(error) - sys.stderr.flush() - return 1 + build_version = get_atom_shell_build_version() + if not ATOM_SHELL_VERSION.startswith(build_version): + error = 'Tag name ({0}) should match build version ({1})\n'.format( + ATOM_SHELL_VERSION, build_version) + sys.stderr.write(error) + sys.stderr.flush() + return 1 github = GitHub(auth_token()) - release_id = create_or_get_release_draft(github, args.version) + releases = github.repos(ATOM_SHELL_REPO).releases.get() + tag_exists = False + for release in releases: + if not release['draft'] and release['tag_name'] == args.version: + tag_exists = True + break + + release = create_or_get_release_draft(github, releases, args.version, + tag_exists) if args.publish_release: # Upload the SHASUMS.txt. @@ -68,31 +73,27 @@ def main(): os.path.join(SOURCE_ROOT, 'script', 'upload-index-json.py')]) # Press the publish button. - publish_release(github, release_id) + publish_release(github, release['id']) # Do not upload other files when passed "-p". return # Upload atom-shell with GitHub Releases API. - upload_atom_shell(github, release_id, os.path.join(DIST_DIR, DIST_NAME)) - upload_atom_shell(github, release_id, os.path.join(DIST_DIR, SYMBOLS_NAME)) + upload_atom_shell(github, release, os.path.join(DIST_DIR, DIST_NAME)) + upload_atom_shell(github, release, os.path.join(DIST_DIR, SYMBOLS_NAME)) # Upload chromedriver and mksnapshot for minor version update. if parse_version(args.version)[2] == '0': - upload_atom_shell(github, release_id, - os.path.join(DIST_DIR, CHROMEDRIVER_NAME)) - upload_atom_shell(github, release_id, - os.path.join(DIST_DIR, MKSNAPSHOT_NAME)) - - if PLATFORM == 'win32': - # Upload PDBs to Windows symbol server. - execute([sys.executable, - os.path.join(SOURCE_ROOT, 'script', 'upload-windows-pdb.py')]) + chromedriver = 'chromedriver-{0}-{1}-{2}.zip'.format( + get_chromedriver_version(), PLATFORM, 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)) + if PLATFORM == 'win32' and not tag_exists: # Upload node headers. execute([sys.executable, os.path.join(SOURCE_ROOT, 'script', 'upload-node-headers.py'), - '-v', ATOM_SHELL_VERSION]) + '-v', args.version]) def parse_args(): @@ -106,6 +107,9 @@ def parse_args(): def get_atom_shell_build_version(): + if get_target_arch() == 'arm' or os.environ.has_key('CI'): + # In CI we just build as told. + return ATOM_SHELL_VERSION if PLATFORM == 'darwin': atom_shell = os.path.join(SOURCE_ROOT, 'out', 'R', '{0}.app'.format(PRODUCT_NAME), 'Contents', @@ -151,34 +155,49 @@ def get_text_with_editor(name): os.unlink(t.name) return text -def create_or_get_release_draft(github, tag): - name = '{0} {1}'.format(PROJECT_NAME, tag) - releases = github.repos(ATOM_SHELL_REPO).releases.get() +def create_or_get_release_draft(github, releases, tag, tag_exists): + # Search for existing draft. for release in releases: - # The untagged commit doesn't have a matching tag_name, so also check name. - if release['tag_name'] == tag or release['name'] == name: - return release['id'] + if release['draft']: + return release + if tag_exists: + tag = 'do-not-publish-me' return create_release_draft(github, tag) def create_release_draft(github, tag): - name = '{0} {1}'.format(PROJECT_NAME, tag) - body = get_text_with_editor(name) + if os.environ.has_key('CI'): + name = '{0} pending draft'.format(PROJECT_NAME) + body = '(placeholder)' + else: + name = '{0} {1}'.format(PROJECT_NAME, tag) + body = get_text_with_editor(name) if body == '': sys.stderr.write('Quit due to empty release note.\n') sys.exit(0) data = dict(tag_name=tag, name=name, body=body, draft=True) r = github.repos(ATOM_SHELL_REPO).releases.post(data=data) - return r['id'] + return r -def upload_atom_shell(github, release_id, file_path): +def upload_atom_shell(github, release, file_path): + # Delete the original file before uploading in CI. + if os.environ.has_key('CI'): + try: + for asset in release['assets']: + if asset['name'] == os.path.basename(file_path): + github.repos(ATOM_SHELL_REPO).releases.assets(asset['id']).delete() + break + except Exception: + pass + + # Upload the file. params = {'name': os.path.basename(file_path)} headers = {'Content-Type': 'application/zip'} with open(file_path, 'rb') as f: - github.repos(ATOM_SHELL_REPO).releases(release_id).assets.post( + github.repos(ATOM_SHELL_REPO).releases(release['id']).assets.post( params=params, headers=headers, data=f, verify=False) diff --git a/spec/api-app-spec.coffee b/spec/api-app-spec.coffee index 4e6bda2ac1fe..4b1a04e00827 100644 --- a/spec/api-app-spec.coffee +++ b/spec/api-app-spec.coffee @@ -1,5 +1,7 @@ assert = require 'assert' -app = require('remote').require 'app' +remote = require 'remote' +app = remote.require 'app' +BrowserWindow = remote.require 'browser-window' describe 'app module', -> describe 'app.getVersion()', -> @@ -23,3 +25,35 @@ describe 'app module', -> app.setName 'test-name' assert.equal app.getName(), 'test-name' app.setName 'Electron Test' + + describe 'app.getLocale()', -> + it 'should not be empty', -> + assert.notEqual app.getLocale(), '' + + describe 'BrowserWindow events', -> + w = null + afterEach -> + w.destroy() if w? + w = null + + it 'should emit browser-window-focus event when window is focused', (done) -> + app.once 'browser-window-focus', (e, window) -> + assert.equal w.id, window.id + done() + w = new BrowserWindow(show: false) + w.emit 'focus' + + it 'should emit browser-window-blur event when window is blured', (done) -> + app.once 'browser-window-blur', (e, window) -> + assert.equal w.id, window.id + done() + w = new BrowserWindow(show: false) + w.emit 'blur' + + it 'should emit browser-window-created event when window is created', (done) -> + app.once 'browser-window-created', (e, window) -> + setImmediate -> + assert.equal w.id, window.id + done() + w = new BrowserWindow(show: false) + w.emit 'blur' diff --git a/spec/api-browser-window-spec.coffee b/spec/api-browser-window-spec.coffee index 958bd7295803..bb4d782e7ffb 100644 --- a/spec/api-browser-window-spec.coffee +++ b/spec/api-browser-window-spec.coffee @@ -4,7 +4,7 @@ path = require 'path' remote = require 'remote' http = require 'http' url = require 'url' -auth = require 'basic-auth' +os = require 'os' BrowserWindow = remote.require 'browser-window' @@ -90,12 +90,24 @@ describe 'browser-window module', -> done() describe 'BrowserWindow.setSize(width, height)', -> - it 'sets the window size', -> - size = [400, 400] + it 'sets the window size', (done) -> + size = [300, 400] + w.once 'resize', -> + newSize = w.getSize() + assert.equal newSize[0], size[0] + assert.equal newSize[1], size[1] + done() w.setSize size[0], size[1] - after = w.getSize() - assert.equal after[0], size[0] - assert.equal after[1], size[1] + + describe 'BrowserWindow.setPosition(x, y)', -> + it 'sets the window position', (done) -> + pos = [10, 10] + w.once 'move', -> + 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)', -> it 'sets the content size', -> @@ -105,10 +117,27 @@ describe 'browser-window module', -> assert.equal after[0], size[0] assert.equal after[1], size[1] + it 'works for framless window', -> + 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] + assert.equal after[1], size[1] + describe 'BrowserWindow.fromId(id)', -> it 'returns the window with id', -> assert.equal w.id, BrowserWindow.fromId(w.id).id + describe 'BrowserWindow.setResizable(resizable)', -> + it 'does not change window size for frameless window', -> + w.destroy() + w = new BrowserWindow(show: true, frame: false) + s = w.getSize() + w.setResizable not w.isResizable() + assert.deepEqual s, w.getSize() + describe '"use-content-size" option', -> it 'make window created with content size when used', -> w.destroy() @@ -122,6 +151,32 @@ describe 'browser-window module', -> assert.equal size[0], 400 assert.equal size[1], 400 + it 'works for framless window', -> + w.destroy() + w = new BrowserWindow(show: false, frame: false, width: 400, height: 400, 'use-content-size': true) + contentSize = w.getContentSize() + assert.equal contentSize[0], 400 + assert.equal contentSize[1], 400 + size = w.getSize() + assert.equal size[0], 400 + assert.equal size[1], 400 + + describe '"title-bar-style" option', -> + return if process.platform isnt 'darwin' + return if parseInt(os.release().split('.')[0]) < 14 # only run these tests on Yosemite or newer + + it 'creates browser window with hidden title bar', -> + w.destroy() + w = new BrowserWindow(show: false, width: 400, height: 400, 'title-bar-style': 'hidden') + contentSize = w.getContentSize() + assert.equal contentSize[1], 400 + + it 'creates browser window with hidden inset title bar', -> + w.destroy() + w = new BrowserWindow(show: false, width: 400, height: 400, 'title-bar-style': 'hidden-inset') + contentSize = w.getContentSize() + assert.equal contentSize[1], 400 + describe '"enable-larger-than-screen" option', -> return if process.platform is 'linux' @@ -144,15 +199,36 @@ describe 'browser-window module', -> assert.equal after[0], size.width assert.equal after[1], size.height - describe '"preload" options', -> - it 'loads the script before other scripts in window', (done) -> - preload = path.join fixtures, 'module', 'set-global.js' - remote.require('ipc').once 'preload', (event, test) -> - assert.equal(test, 'preload') - done() - w.destroy() - w = new BrowserWindow(show: false, width: 400, height: 400, preload: preload) - w.loadUrl 'file://' + path.join(fixtures, 'api', 'preload.html') + describe '"web-preferences" option', -> + afterEach -> + remote.require('ipc').removeAllListeners('answer') + + describe '"preload" option', -> + it 'loads the script before other scripts in window', (done) -> + preload = path.join fixtures, 'module', 'set-global.js' + remote.require('ipc').once 'answer', (event, test) -> + assert.equal(test, 'preload') + done() + w.destroy() + w = new BrowserWindow + show: false + 'web-preferences': + preload: preload + w.loadUrl 'file://' + path.join(fixtures, 'api', 'preload.html') + + describe '"node-integration" option', -> + it 'disables node integration when specified to false', (done) -> + preload = path.join fixtures, 'module', 'send-later.js' + remote.require('ipc').once 'answer', (event, test) -> + assert.equal(test, 'undefined') + done() + w.destroy() + w = new BrowserWindow + show: false + 'web-preferences': + preload: preload + 'node-integration': false + w.loadUrl 'file://' + path.join(fixtures, 'api', 'blank.html') describe 'beforeunload handler', -> it 'returning true would not prevent close', (done) -> @@ -176,10 +252,11 @@ describe 'browser-window module', -> w.loadUrl 'file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html') describe 'new-window event', -> + return if isCI and process.platform is 'darwin' it 'emits when window.open is called', (done) -> w.webContents.once 'new-window', (e, url, frameName) -> e.preventDefault() - assert.equal url, 'http://host' + assert.equal url, 'http://host/' assert.equal frameName, 'host' done() w.loadUrl "file://#{fixtures}/pages/window-open.html" @@ -193,7 +270,7 @@ describe 'browser-window module', -> w.loadUrl "file://#{fixtures}/pages/target-name.html" describe 'maximize event', -> - return if isCI and process.platform is 'linux' + return if isCI it 'emits when window is maximized', (done) -> @timeout 10000 w.once 'maximize', -> done() @@ -201,7 +278,7 @@ describe 'browser-window module', -> w.maximize() describe 'unmaximize event', -> - return if isCI and process.platform is 'linux' + return if isCI it 'emits when window is unmaximized', (done) -> @timeout 10000 w.once 'unmaximize', -> done() @@ -210,7 +287,7 @@ describe 'browser-window module', -> w.unmaximize() describe 'minimize event', -> - return if isCI and process.platform is 'linux' + return if isCI it 'emits when window is minimized', (done) -> @timeout 10000 w.once 'minimize', -> done() @@ -218,54 +295,19 @@ describe 'browser-window module', -> w.minimize() describe 'will-navigate event', -> + @timeout 10000 it 'emits when user starts a navigation', (done) -> - @timeout 10000 - w.webContents.on 'will-navigate', (event, url) -> + url = "file://#{fixtures}/pages/will-navigate.html" + w.webContents.on 'will-navigate', (event, u) -> event.preventDefault() - assert.equal url, 'https://www.github.com/' + assert.equal u, url done() - w.loadUrl "file://#{fixtures}/pages/will-navigate.html" + w.loadUrl url - describe 'dom-ready event', -> - it 'emits when document is loaded', (done) -> - ipc = remote.require 'ipc' - server = http.createServer (req, res) -> - action = url.parse(req.url, true).pathname - if action == '/logo.png' - img = fs.readFileSync(path.join(fixtures, 'assets', 'logo.png')) - res.writeHead(200, {'Content-Type': 'image/png'}) - setTimeout -> - res.end(img, 'binary') - , 2000 - server.close() - server.listen 62542, '127.0.0.1' - ipc.on 'dom-ready', (e, state) -> - ipc.removeAllListeners 'dom-ready' - assert.equal state, 'interactive' + describe 'beginFrameSubscription method', -> + it 'subscribes frame updates', (done) -> + w.loadUrl "file://#{fixtures}/api/blank.html" + w.webContents.beginFrameSubscription (data) -> + assert.notEqual data.length, 0 + w.webContents.endFrameSubscription() done() - w.webContents.on 'did-finish-load', -> - w.close() - w.loadUrl "file://#{fixtures}/pages/f.html" - - describe 'basic auth', -> - it 'should authenticate with correct credentials', (done) -> - ipc = remote.require 'ipc' - server = http.createServer (req, res) -> - action = url.parse(req.url, true).pathname - if action == '/' - credentials = auth(req) - if credentials.name == 'test' and credentials.pass == 'test' - res.end('Authenticated') - server.close() - else if action == '/jquery.js' - js = fs.readFileSync(path.join(__dirname, 'static', 'jquery-2.0.3.min.js')) - res.writeHead(200, {'Content-Type': 'text/javascript'}) - res.end(js, 'utf-8') - server.listen 62342, '127.0.0.1' - ipc.on 'console-message', (e, message) -> - ipc.removeAllListeners 'console-message' - assert.equal message, 'Authenticated' - done() - w.webContents.on 'did-finish-load', -> - w.close() - w.loadUrl "file://#{fixtures}/pages/basic-auth.html" diff --git a/spec/api-clipboard-spec.coffee b/spec/api-clipboard-spec.coffee index feadd15b0a6f..ddb38825e35f 100644 --- a/spec/api-clipboard-spec.coffee +++ b/spec/api-clipboard-spec.coffee @@ -18,3 +18,35 @@ describe 'clipboard module', -> text = '千江有水千江月,万里无云万里天' clipboard.writeText text assert.equal clipboard.readText(), text + + describe 'clipboard.readHtml()', -> + it 'returns markup correctly', -> + text = 'Hi' + markup = + if process.platform is 'darwin' + 'Hi' + else if process.platform is 'linux' + 'Hi' + else + 'Hi' + clipboard.writeHtml text + assert.equal clipboard.readHtml(), markup + + describe 'clipboard.write()', -> + it 'returns data correctly', -> + text = 'test' + p = path.join fixtures, 'assets', 'logo.png' + i = nativeImage.createFromPath p + markup = + if process.platform is 'darwin' + 'Hi' + else if process.platform is 'linux' + 'Hi' + else + 'Hi' + clipboard.write {text: "test", html: 'Hi', image: p} + assert.equal clipboard.readText(), text + assert.equal clipboard.readHtml(), markup + assert.equal clipboard.readImage().toDataUrl(), i.toDataUrl() diff --git a/spec/api-crash-reporter-spec.coffee b/spec/api-crash-reporter-spec.coffee index d6a619c905c3..60b630bc2ffc 100644 --- a/spec/api-crash-reporter-spec.coffee +++ b/spec/api-crash-reporter-spec.coffee @@ -5,12 +5,10 @@ url = require 'url' remote = require 'remote' formidable = require 'formidable' +crashReporter = remote.require 'crash-reporter' BrowserWindow = remote.require 'browser-window' describe 'crash-reporter module', -> - # We have trouble makeing crash reporter work on Yosemite. - return if process.platform is 'darwin' - fixtures = path.resolve __dirname, 'fixtures' w = null @@ -20,9 +18,14 @@ describe 'crash-reporter module', -> # It is not working on 64bit Windows. return if process.platform is 'win32' and process.arch is 'x64' + # The crash-reporter test is not reliable on CI machine. + isCI = remote.process.argv[2] == '--ci' + return if isCI + it 'should send minidump when renderer crashes', (done) -> - @timeout 60000 + @timeout 120000 server = http.createServer (req, res) -> + server.close() form = new formidable.IncomingForm() process.throwDeprecation = false form.parse req, (error, fields, files) -> @@ -39,12 +42,16 @@ describe 'crash-reporter module', -> assert files['upload_file_minidump']['name']? res.end('abc-123-def') - server.close() done() - server.listen 0, '127.0.0.1', -> + # Server port is generated randomly for the first run, it will be reused + # when page is refreshed. + port = remote.process.port + server.listen port, '127.0.0.1', -> {port} = server.address() + remote.process.port = port url = url.format protocol: 'file' pathname: path.join fixtures, 'api', 'crash.html' search: "?port=#{port}" + crashReporter.start {'submitUrl': 'http://127.0.0.1:' + port} w.loadUrl url diff --git a/spec/api-ipc-spec.coffee b/spec/api-ipc-spec.coffee index d7f77b14ce2e..142f06c00ff3 100644 --- a/spec/api-ipc-spec.coffee +++ b/spec/api-ipc-spec.coffee @@ -5,6 +5,13 @@ remote = require 'remote' BrowserWindow = remote.require 'browser-window' +comparePaths = (path1, path2) -> + if process.platform is 'win32' + # Paths in Windows are case insensitive. + path1 = path1.toLowerCase() + path2 = path2.toLowerCase() + assert.equal path1, path2 + describe 'ipc module', -> fixtures = path.join __dirname, 'fixtures' @@ -19,8 +26,8 @@ describe 'ipc module', -> assert.equal a.id, 1127 it 'should search module from the user app', -> - assert.equal path.normalize(remote.process.mainModule.filename), path.resolve(__dirname, 'static', 'main.js') - assert.equal path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules') + 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', -> it 'should be called in browser synchronously', -> @@ -52,6 +59,14 @@ describe 'ipc module', -> print_name = remote.require path.join(fixtures, 'module', 'print_name.js') assert.equal print_name.print(buf), 'Buffer' + describe 'remote promise', -> + it 'can be used as promise in each side', (done) -> + promise = remote.require path.join(fixtures, 'module', 'promise.js') + promise.twicePromise(Promise.resolve(1234)) + .then (value) => + assert.equal value, 2468 + done() + describe 'ipc.sender.send', -> it 'should work when sending an object containing id property', (done) -> obj = id: 1, name: 'ly' diff --git a/spec/api-protocol-spec.coffee b/spec/api-protocol-spec.coffee index 69506156089f..02fd8d5e402a 100644 --- a/spec/api-protocol-spec.coffee +++ b/spec/api-protocol-spec.coffee @@ -1,207 +1,355 @@ assert = require 'assert' -ipc = require 'ipc' +http = require 'http' path = require 'path' remote = require 'remote' protocol = remote.require 'protocol' describe 'protocol module', -> - describe 'protocol.registerProtocol', -> - it 'throws error when scheme is already registered', (done) -> - register = -> protocol.registerProtocol('test1', ->) - protocol.once 'registered', (event, scheme) -> - assert.equal scheme, 'test1' - assert.throws register, /The scheme is already registered/ - protocol.unregisterProtocol 'test1' - done() - register() + protocolName = 'sp' + text = 'valar morghulis' - it 'calls the callback when scheme is visited', (done) -> - protocol.registerProtocol 'test2', (request) -> - assert.equal request.url, 'test2://test2' - protocol.unregisterProtocol 'test2' - done() - $.get 'test2://test2', -> + afterEach (done) -> + protocol.unregisterProtocol protocolName, -> + protocol.uninterceptProtocol 'http', -> done() + + describe 'protocol.register(Any)Protocol', -> + emptyHandler = (request, callback) -> callback() + it 'throws error when scheme is already registered', (done) -> + protocol.registerStringProtocol protocolName, emptyHandler, (error) -> + assert.equal error, null + protocol.registerBufferProtocol protocolName, emptyHandler, (error) -> + assert.notEqual error, null + done() + + it 'does not crash when handler is called twice', (done) -> + doubleHandler = (request, callback) -> + callback(text) + callback() + protocol.registerStringProtocol protocolName, doubleHandler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + assert.equal data, text + done() + error: (xhr, errorType, error) -> + done(error) + + it 'sends error when callback is called with nothing', (done) -> + protocol.registerBufferProtocol protocolName, emptyHandler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + done('request succeeded but it should not') + error: (xhr, errorType, error) -> + assert.equal errorType, 'error' + done() + + it 'does not crash when callback is called in next tick', (done) -> + handler = (request, callback) -> + setImmediate -> callback(text) + protocol.registerStringProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + assert.equal data, text + done() + error: (xhr, errorType, error) -> + done(error) describe 'protocol.unregisterProtocol', -> - it 'throws error when scheme does not exist', -> - unregister = -> protocol.unregisterProtocol 'test3' - assert.throws unregister, /The scheme has not been registered/ - - describe 'registered protocol callback', -> - it 'returns string should send the string as request content', (done) -> - handler = remote.createFunctionWithReturnValue 'valar morghulis' - protocol.registerProtocol 'atom-string', handler - - $.ajax - url: 'atom-string://fake-host' - success: (data) -> - assert.equal data, handler() - protocol.unregisterProtocol 'atom-string' - done() - error: (xhr, errorType, error) -> - assert false, 'Got error: ' + errorType + ' ' + error - protocol.unregisterProtocol 'atom-string' - - it 'returns RequestStringJob should send string', (done) -> - data = 'valar morghulis' - job = new protocol.RequestStringJob(mimeType: 'text/html', data: data) - handler = remote.createFunctionWithReturnValue job - protocol.registerProtocol 'atom-string-job', handler - - $.ajax - url: 'atom-string-job://fake-host' - success: (response) -> - assert.equal response, data - protocol.unregisterProtocol 'atom-string-job' - done() - error: (xhr, errorType, error) -> - assert false, 'Got error: ' + errorType + ' ' + error - protocol.unregisterProtocol 'atom-string-job' - - it 'returns RequestErrorJob should send error', (done) -> - data = 'valar morghulis' - job = new protocol.RequestErrorJob(-6) - handler = remote.createFunctionWithReturnValue job - protocol.registerProtocol 'atom-error-job', handler - - $.ajax - url: 'atom-error-job://fake-host' - success: (response) -> - assert false, 'should not reach here' - error: (xhr, errorType, error) -> - assert errorType, 'error' - protocol.unregisterProtocol 'atom-error-job' - done() - - it 'returns RequestBufferJob should send buffer', (done) -> - data = new Buffer("hello") - job = new protocol.RequestBufferJob(data: data) - handler = remote.createFunctionWithReturnValue job - protocol.registerProtocol 'atom-buffer-job', handler - - $.ajax - url: 'atom-buffer-job://fake-host' - success: (response) -> - assert.equal response.length, data.length - buf = new Buffer(response.length) - buf.write(response) - assert buf.equals(data) - protocol.unregisterProtocol 'atom-buffer-job' - done() - error: (xhr, errorType, error) -> - assert false, 'Got error: ' + errorType + ' ' + error - protocol.unregisterProtocol 'atom-buffer-job' - - it 'returns RequestFileJob should send file', (done) -> - job = new protocol.RequestFileJob(__filename) - handler = remote.createFunctionWithReturnValue job - protocol.registerProtocol 'atom-file-job', handler - - $.ajax - url: 'atom-file-job://' + __filename - success: (data) -> - content = require('fs').readFileSync __filename - assert.equal data, String(content) - protocol.unregisterProtocol 'atom-file-job' - done() - error: (xhr, errorType, error) -> - assert false, 'Got error: ' + errorType + ' ' + error - protocol.unregisterProtocol 'atom-file-job' - - it 'returns RequestFileJob should send file from asar archive', (done) -> - p = path.join __dirname, 'fixtures', 'asar', 'a.asar', 'file1' - job = new protocol.RequestFileJob(p) - handler = remote.createFunctionWithReturnValue job - protocol.registerProtocol 'atom-file-job', handler - - $.ajax - url: 'atom-file-job://' + p - success: (data) -> - content = require('fs').readFileSync(p) - assert.equal data, String(content) - protocol.unregisterProtocol 'atom-file-job' - done() - error: (xhr, errorType, error) -> - assert false, 'Got error: ' + errorType + ' ' + error - protocol.unregisterProtocol 'atom-file-job' - - it 'returns RequestFileJob should send file from asar archive with unpacked file', (done) -> - p = path.join __dirname, 'fixtures', 'asar', 'unpack.asar', 'a.txt' - job = new protocol.RequestFileJob(p) - handler = remote.createFunctionWithReturnValue job - protocol.registerProtocol 'atom-file-job', handler - - $.ajax - url: 'atom-file-job://' + p - success: (response) -> - data = require('fs').readFileSync(p) - assert.equal response.length, data.length - buf = new Buffer(response.length) - buf.write(response) - assert buf.equals(data) - protocol.unregisterProtocol 'atom-file-job' - done() - error: (xhr, errorType, error) -> - assert false, 'Got error: ' + errorType + ' ' + error - protocol.unregisterProtocol 'atom-file-job' - - describe 'protocol.isHandledProtocol', -> - it 'returns true if the scheme can be handled', -> - assert.equal protocol.isHandledProtocol('file'), true - assert.equal protocol.isHandledProtocol('http'), true - assert.equal protocol.isHandledProtocol('https'), true - assert.equal protocol.isHandledProtocol('atom'), false - - describe 'protocol.interceptProtocol', -> - it 'throws error when scheme is not a registered one', -> - register = -> protocol.interceptProtocol('test-intercept', ->) - assert.throws register, /Scheme does not exist/ - - it 'throws error when scheme is a custom protocol', (done) -> - protocol.once 'unregistered', (event, scheme) -> - assert.equal scheme, 'atom' + it 'returns error when scheme does not exist', (done) -> + protocol.unregisterProtocol 'not-exist', (error) -> + assert.notEqual error, null done() - protocol.once 'registered', (event, scheme) -> - assert.equal scheme, 'atom' - register = -> protocol.interceptProtocol('test-intercept', ->) - assert.throws register, /Scheme does not exist/ - protocol.unregisterProtocol scheme - protocol.registerProtocol('atom', ->) - it 'returns original job when callback returns nothing', (done) -> - targetScheme = 'file' - protocol.once 'intercepted', (event, scheme) -> - assert.equal scheme, targetScheme - free = -> protocol.uninterceptProtocol targetScheme + describe 'protocol.registerStringProtocol', -> + it 'sends string as response', (done) -> + handler = (request, callback) -> callback(text) + protocol.registerStringProtocol protocolName, handler, (error) -> $.ajax - url: "#{targetScheme}://#{__filename}", - success: -> - protocol.once 'unintercepted', (event, scheme) -> - assert.equal scheme, targetScheme - done() - free() - error: (xhr, errorType, error) -> - free() - assert false, 'Got error: ' + errorType + ' ' + error - protocol.interceptProtocol targetScheme, (request) -> - if process.platform is 'win32' - pathInUrl = path.normalize request.url.substr(8) - assert.equal pathInUrl.toLowerCase(), __filename.toLowerCase() - else - assert.equal request.url, "#{targetScheme}://#{__filename}" - - it 'can override original protocol handler', (done) -> - handler = remote.createFunctionWithReturnValue 'valar morghulis' - protocol.once 'intercepted', -> - free = -> protocol.uninterceptProtocol 'file' - $.ajax - url: 'file://fake-host' + url: "#{protocolName}://fake-host" success: (data) -> - protocol.once 'unintercepted', -> - assert.equal data, handler() - done() - free() + assert.equal data, text + done() error: (xhr, errorType, error) -> - assert false, 'Got error: ' + errorType + ' ' + error - free() - protocol.interceptProtocol 'file', handler + done(error) + + it 'sends object as response', (done) -> + handler = (request, callback) -> callback(data: text, mimeType: 'text/html') + protocol.registerStringProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data, statux, request) -> + assert.equal data, text + done() + error: (xhr, errorType, error) -> + done(error) + + it 'fails when sending object other than string', (done) -> + handler = (request, callback) -> callback(new Date) + protocol.registerBufferProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + done('request succeeded but it should not') + error: (xhr, errorType, error) -> + assert.equal errorType, 'error' + done() + + describe 'protocol.registerBufferProtocol', -> + buffer = new Buffer(text) + + it 'sends Buffer as response', (done) -> + handler = (request, callback) -> callback(buffer) + protocol.registerBufferProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + assert.equal data, text + done() + error: (xhr, errorType, error) -> + done(error) + + it 'sends object as response', (done) -> + handler = (request, callback) -> callback(data: buffer, mimeType: 'text/html') + protocol.registerBufferProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data, statux, request) -> + assert.equal data, text + done() + error: (xhr, errorType, error) -> + done(error) + + it 'fails when sending string', (done) -> + handler = (request, callback) -> callback(text) + protocol.registerBufferProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + done('request succeeded but it should not') + error: (xhr, errorType, error) -> + assert.equal errorType, 'error' + done() + + describe 'protocol.registerFileProtocol', -> + 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', (done) -> + handler = (request, callback) -> callback(filePath) + protocol.registerFileProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + assert.equal data, String(fileContent) + done() + error: (xhr, errorType, error) -> + done(error) + + it 'sends object as response', (done) -> + handler = (request, callback) -> callback(path: filePath) + protocol.registerFileProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data, statux, request) -> + assert.equal data, String(fileContent) + done() + error: (xhr, errorType, error) -> + done(error) + + it 'can send normal file', (done) -> + handler = (request, callback) -> callback(normalPath) + protocol.registerFileProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + assert.equal data, String(normalContent) + done() + error: (xhr, errorType, error) -> + done(error) + + it 'fails when sending unexist-file', (done) -> + fakeFilePath = path.join __dirname, 'fixtures', 'asar', 'a.asar', 'not-exist' + handler = (request, callback) -> callback(fakeFilePath) + protocol.registerBufferProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + done('request succeeded but it should not') + error: (xhr, errorType, error) -> + assert.equal errorType, 'error' + done() + + it 'fails when sending unsupported content', (done) -> + handler = (request, callback) -> callback(new Date) + protocol.registerBufferProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + done('request succeeded but it should not') + error: (xhr, errorType, error) -> + assert.equal errorType, 'error' + done() + + describe 'protocol.registerHttpProtocol', -> + it 'sends url as response', (done) -> + server = http.createServer (req, res) -> + assert.notEqual req.headers.accept, '' + res.end(text) + server.close() + server.listen 0, '127.0.0.1', -> + {port} = server.address() + url = "http://127.0.0.1:#{port}" + handler = (request, callback) -> callback({url}) + protocol.registerHttpProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + assert.equal data, text + done() + error: (xhr, errorType, error) -> + done(error) + + it 'fails when sending invalid url', (done) -> + handler = (request, callback) -> callback({url: 'url'}) + protocol.registerHttpProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + done('request succeeded but it should not') + error: (xhr, errorType, error) -> + assert.equal errorType, 'error' + done() + + it 'fails when sending unsupported content', (done) -> + handler = (request, callback) -> callback(new Date) + protocol.registerHttpProtocol protocolName, handler, (error) -> + $.ajax + url: "#{protocolName}://fake-host" + success: (data) -> + done('request succeeded but it should not') + error: (xhr, errorType, error) -> + assert.equal errorType, 'error' + done() + + describe 'protocol.isProtocolHandled', -> + it 'returns true for file:', (done) -> + protocol.isProtocolHandled 'file', (result) -> + assert.equal result, true + done() + + it 'returns true for http:', (done) -> + protocol.isProtocolHandled 'http', (result) -> + assert.equal result, true + done() + + it 'returns true for https:', (done) -> + protocol.isProtocolHandled 'https', (result) -> + assert.equal result, true + done() + + it 'returns false when scheme is not registred', (done) -> + protocol.isProtocolHandled 'no-exist', (result) -> + assert.equal result, false + done() + + it 'returns true for custom protocol', (done) -> + emptyHandler = (request, callback) -> callback() + protocol.registerStringProtocol protocolName, emptyHandler, (error) -> + assert.equal error, null + protocol.isProtocolHandled protocolName, (result) -> + assert.equal result, true + done() + + it 'returns true for intercepted protocol', (done) -> + emptyHandler = (request, callback) -> callback() + protocol.interceptStringProtocol 'http', emptyHandler, (error) -> + assert.equal error, null + protocol.isProtocolHandled 'http', (result) -> + assert.equal result, true + done() + + describe 'protocol.intercept(Any)Protocol', -> + emptyHandler = (request, callback) -> callback() + + it 'throws error when scheme is already intercepted', (done) -> + protocol.interceptStringProtocol 'http', emptyHandler, (error) -> + assert.equal error, null + protocol.interceptBufferProtocol 'http', emptyHandler, (error) -> + assert.notEqual error, null + done() + + it 'does not crash when handler is called twice', (done) -> + doubleHandler = (request, callback) -> + callback(text) + callback() + protocol.interceptStringProtocol 'http', doubleHandler, (error) -> + $.ajax + url: 'http://fake-host' + success: (data) -> + assert.equal data, text + done() + error: (xhr, errorType, error) -> + done(error) + + it 'sends error when callback is called with nothing', (done) -> + protocol.interceptBufferProtocol 'http', emptyHandler, (error) -> + $.ajax + url: 'http://fake-host' + success: (data) -> + done('request succeeded but it should not') + error: (xhr, errorType, error) -> + assert.equal errorType, 'error' + done() + + describe 'protocol.interceptStringProtocol', -> + it 'can intercept http protocol', (done) -> + handler = (request, callback) -> callback(text) + protocol.interceptStringProtocol 'http', handler, (error) -> + $.ajax + url: 'http://fake-host' + success: (data) -> + assert.equal data, text + done() + error: (xhr, errorType, error) -> + done(error) + + it 'can set content-type', (done) -> + handler = (request, callback) -> + callback({mimeType: 'application/json', data: '{"value": 1}'}) + protocol.interceptStringProtocol 'http', handler, (error) -> + $.ajax + url: 'http://fake-host' + success: (data) -> + assert.equal typeof(data), 'object' + assert.equal data.value, 1 + done() + error: (xhr, errorType, error) -> + done(error) + + describe 'protocol.interceptBufferProtocol', -> + it 'can intercept http protocol', (done) -> + handler = (request, callback) -> callback(new Buffer(text)) + protocol.interceptBufferProtocol 'http', handler, (error) -> + $.ajax + url: 'http://fake-host' + success: (data) -> + assert.equal data, text + done() + error: (xhr, errorType, error) -> + done(error) + + describe 'protocol.uninterceptProtocol', -> + it 'returns error when scheme does not exist', (done) -> + protocol.uninterceptProtocol 'not-exist', (error) -> + assert.notEqual error, null + done() + + it 'returns error when scheme is not intercepted', (done) -> + protocol.uninterceptProtocol 'http', (error) -> + assert.notEqual error, null + done() diff --git a/spec/api-session-spec.coffee b/spec/api-session-spec.coffee new file mode 100644 index 000000000000..34a08ee50f09 --- /dev/null +++ b/spec/api-session-spec.coffee @@ -0,0 +1,74 @@ +assert = require 'assert' +remote = require 'remote' +http = require 'http' +path = require 'path' +app = remote.require 'app' +BrowserWindow = remote.require 'browser-window' + +describe 'session module', -> + @timeout 10000 + fixtures = path.resolve __dirname, 'fixtures' + w = null + url = "http://127.0.0.1" + + beforeEach -> w = new BrowserWindow(show: false, width: 400, height: 400) + afterEach -> w.destroy() + + it 'should get cookies', (done) -> + server = http.createServer (req, res) -> + res.setHeader('Set-Cookie', ['0=0']) + res.end('finished') + server.close() + + server.listen 0, '127.0.0.1', -> + {port} = server.address() + w.loadUrl "#{url}:#{port}" + w.webContents.on 'did-finish-load', -> + w.webContents.session.cookies.get {url: url}, (error, list) -> + return done(error) if error + for cookie in list when cookie.name is '0' + if cookie.value is '0' + return done() + else + return done("cookie value is #{cookie.value} while expecting 0") + done('Can not find cookie') + + it 'should over-write the existent cookie', (done) -> + app.defaultSession.cookies.set {url: url, name: '1', value: '1'}, (error) -> + return done(error) if error + app.defaultSession.cookies.get {url: url}, (error, list) -> + return done(error) if error + for cookie in list when cookie.name is '1' + if cookie.value is '1' + return done() + else + return done("cookie value is #{cookie.value} while expecting 1") + done('Can not find cookie') + + it 'should remove cookies', (done) -> + app.defaultSession.cookies.set {url: url, name: '2', value: '2'}, (error) -> + return done(error) if error + app.defaultSession.cookies.remove {url: url, name: '2'}, (error) -> + return done(error) if error + app.defaultSession.cookies.get {url: url}, (error, list) -> + return done(error) if error + for cookie in list when cookie.name is '2' + return done('Cookie not deleted') + done() + + describe 'session.clearStorageData(options)', -> + fixtures = path.resolve __dirname, 'fixtures' + it 'clears localstorage data', (done) -> + ipc = remote.require('ipc') + ipc.on 'count', (event, count) -> + ipc.removeAllListeners 'count' + assert not count + done() + w.loadUrl 'file://' + path.join(fixtures, 'api', 'localstorage.html') + w.webContents.on 'did-finish-load', -> + options = + origin: "file://", + storages: ['localstorage'], + quotas: ['persistent'], + w.webContents.session.clearStorageData options, -> + w.webContents.send 'getcount' diff --git a/spec/asar-spec.coffee b/spec/asar-spec.coffee index d78ab4360618..4ef9337f3abe 100644 --- a/spec/asar-spec.coffee +++ b/spec/asar-spec.coffee @@ -1,6 +1,7 @@ -assert = require 'assert' -fs = require 'fs' -path = require 'path' +assert = require 'assert' +child_process = require 'child_process' +fs = require 'fs' +path = require 'path' describe 'asar package', -> fixtures = path.join __dirname, 'fixtures' @@ -9,11 +10,11 @@ describe 'asar package', -> describe 'fs.readFileSync', -> it 'reads a normal file', -> file1 = path.join fixtures, 'asar', 'a.asar', 'file1' - assert.equal fs.readFileSync(file1).toString(), 'file1\n' + assert.equal fs.readFileSync(file1).toString().trim(), 'file1' file2 = path.join fixtures, 'asar', 'a.asar', 'file2' - assert.equal fs.readFileSync(file2).toString(), 'file2\n' + assert.equal fs.readFileSync(file2).toString().trim(), 'file2' file3 = path.join fixtures, 'asar', 'a.asar', 'file3' - assert.equal fs.readFileSync(file3).toString(), 'file3\n' + assert.equal fs.readFileSync(file3).toString().trim(), 'file3' it 'reads from a empty file', -> file = path.join fixtures, 'asar', 'empty.asar', 'file1' @@ -23,13 +24,13 @@ describe 'asar package', -> it 'reads a linked file', -> p = path.join fixtures, 'asar', 'a.asar', 'link1' - assert.equal fs.readFileSync(p).toString(), 'file1\n' + assert.equal fs.readFileSync(p).toString().trim(), 'file1' it 'reads a file from linked directory', -> p = path.join fixtures, 'asar', 'a.asar', 'link2', 'file1' - assert.equal fs.readFileSync(p).toString(), 'file1\n' + assert.equal fs.readFileSync(p).toString().trim(), 'file1' p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' - assert.equal fs.readFileSync(p).toString(), 'file1\n' + assert.equal fs.readFileSync(p).toString().trim(), 'file1' it 'throws ENOENT error when can not find file', -> p = path.join fixtures, 'asar', 'a.asar', 'not-exist' @@ -44,12 +45,16 @@ describe 'asar package', -> assert /ENOENT/.test e async = true + it 'reads a normal file with unpacked files', -> + p = path.join fixtures, 'asar', 'unpack.asar', 'a.txt' + assert.equal fs.readFileSync(p).toString().trim(), 'a' + describe 'fs.readFile', -> it 'reads a normal file', (done) -> p = path.join fixtures, 'asar', 'a.asar', 'file1' fs.readFile p, (err, content) -> assert.equal err, null - assert.equal String(content), 'file1\n' + assert.equal String(content).trim(), 'file1' done() it 'reads from a empty file', (done) -> @@ -63,14 +68,14 @@ describe 'asar package', -> p = path.join fixtures, 'asar', 'a.asar', 'link1' fs.readFile p, (err, content) -> assert.equal err, null - assert.equal String(content), 'file1\n' + assert.equal String(content).trim(), 'file1' done() it 'reads a file from linked directory', (done) -> p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' fs.readFile p, (err, content) -> assert.equal err, null - assert.equal String(content), 'file1\n' + assert.equal String(content).trim(), 'file1' done() it 'throws ENOENT error when can not find file', (done) -> @@ -80,6 +85,11 @@ describe 'asar package', -> done() describe 'fs.lstatSync', -> + it 'handles path with trailing slash correctly', -> + p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' + fs.lstatSync p + fs.lstatSync p + '/' + it 'returns information of root', -> p = path.join fixtures, 'asar', 'a.asar' stats = fs.lstatSync p @@ -131,6 +141,10 @@ describe 'asar package', -> assert.throws throws, /ENOENT/ describe 'fs.lstat', -> + it 'handles path with trailing slash correctly', (done) -> + p = path.join fixtures, 'asar', 'a.asar', 'link2', 'link2', 'file1' + fs.lstat p + '/', done + it 'returns information of root', (done) -> p = path.join fixtures, 'asar', 'a.asar' stats = fs.lstat p, (err, stats) -> @@ -328,7 +342,7 @@ describe 'asar package', -> fd = fs.openSync p, 'r' buffer = new Buffer(6) fs.readSync fd, buffer, 0, 6, 0 - assert.equal String(buffer), 'file1\n' + assert.equal String(buffer).trim(), 'file1' fs.closeSync fd it 'throws ENOENT error when can not find file', -> @@ -344,7 +358,7 @@ describe 'asar package', -> buffer = new Buffer(6) fs.read fd, buffer, 0, 6, 0, (err) -> assert.equal err, null - assert.equal String(buffer), 'file1\n' + assert.equal String(buffer).trim(), 'file1' fs.close fd, done it 'throws ENOENT error when can not find file', (done) -> @@ -371,6 +385,21 @@ describe 'asar package', -> done() child.send file + describe 'internalModuleReadFile', -> + internalModuleReadFile = process.binding('fs').internalModuleReadFile + + it 'read a normal file', -> + 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' + assert.equal internalModuleReadFile(file3).toString().trim(), 'file3' + + it 'reads a normal file with unpacked files', -> + p = path.join fixtures, 'asar', 'unpack.asar', 'a.txt' + assert.equal internalModuleReadFile(p).toString().trim(), 'a' + describe 'asar protocol', -> url = require 'url' remote = require 'remote' @@ -380,25 +409,25 @@ describe 'asar package', -> it 'can request a file in package', (done) -> p = path.resolve fixtures, 'asar', 'a.asar', 'file1' $.get "file://#{p}", (data) -> - assert.equal data, 'file1\n' + assert.equal data.trim(), 'file1' done() it 'can request a file in package with unpacked files', (done) -> p = path.resolve fixtures, 'asar', 'unpack.asar', 'a.txt' $.get "file://#{p}", (data) -> - assert.equal data, 'a\n' + assert.equal data.trim(), 'a' done() it 'can request a linked file in package', (done) -> p = path.resolve fixtures, 'asar', 'a.asar', 'link2', 'link1' $.get "file://#{p}", (data) -> - assert.equal data, 'file1\n' + assert.equal data.trim(), 'file1' done() it 'can request a file in filesystem', (done) -> p = path.resolve fixtures, 'asar', 'file' $.get "file://#{p}", (data) -> - assert.equal data, 'file\n' + assert.equal data.trim(), 'file' done() it 'gets 404 when file is not found', (done) -> @@ -443,12 +472,19 @@ describe 'asar package', -> stats = originalFs.statSync file assert stats.isFile() + it 'is available in forked scripts', (done) -> + child = child_process.fork path.join(fixtures, 'module', 'original-fs.js') + child.on 'message', (msg) -> + assert.equal msg, 'object' + done() + child.send 'message' + describe 'graceful-fs module', -> gfs = require 'graceful-fs' it 'recognize asar archvies', -> p = path.join fixtures, 'asar', 'a.asar', 'link1' - assert.equal gfs.readFileSync(p).toString(), 'file1\n' + assert.equal gfs.readFileSync(p).toString().trim(), 'file1' it 'does not touch global fs object', -> assert.notEqual fs.readdir, gfs.readdir diff --git a/spec/chromium-spec.coffee b/spec/chromium-spec.coffee index d39183576b40..2c42cd9a4cf0 100644 --- a/spec/chromium-spec.coffee +++ b/spec/chromium-spec.coffee @@ -2,11 +2,13 @@ assert = require 'assert' http = require 'http' https = require 'https' path = require 'path' +ws = require 'ws' +remote = require 'remote' describe 'chromium feature', -> fixtures = path.resolve __dirname, 'fixtures' - describe 'heap snapshot', -> + xdescribe 'heap snapshot', -> it 'does not crash', -> process.atomBinding('v8_util').takeHeapSnapshot() @@ -34,13 +36,59 @@ describe 'chromium feature', -> describe 'window.open', -> it 'returns a BrowserWindowProxy object', -> - b = window.open 'about:blank', 'test', 'show=no' + b = window.open 'about:blank', '', 'show=no' + assert.equal b.closed, false assert.equal b.constructor.name, 'BrowserWindowProxy' b.close() + it 'accepts "node-integration" as feature', (done) -> + listener = (event) -> + window.removeEventListener 'message', listener + b.close() + assert.equal event.data, 'undefined' + done() + window.addEventListener 'message', listener + b = window.open "file://#{fixtures}/pages/window-opener-node.html", '', 'node-integration=no,show=no' + + describe 'window.opener', -> + @timeout 10000 + + ipc = remote.require 'ipc' + url = "file://#{fixtures}/pages/window-opener.html" + w = null + + afterEach -> + w?.destroy() + ipc.removeAllListeners 'opener' + + it 'is null for main window', (done) -> + ipc.on 'opener', (event, opener) -> + assert.equal opener, null + done() + BrowserWindow = remote.require 'browser-window' + w = new BrowserWindow(show: false) + w.loadUrl url + + it 'is not null for window opened by window.open', (done) -> + ipc.on 'opener', (event, opener) -> + b.close() + done(if opener isnt null then undefined else opener) + b = window.open url, '', 'show=no' + + describe 'window.opener.postMessage', -> + it 'sets source and origin correctly', (done) -> + listener = (event) -> + window.removeEventListener 'message', listener + b.close() + assert.equal event.source.guestId, b.guestId + 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', -> it 'does not crash', -> - RUint8Array = require('remote').getGlobal 'Uint8Array' + RUint8Array = remote.getGlobal 'Uint8Array' new RUint8Array describe 'webgl', -> @@ -82,3 +130,60 @@ describe 'chromium feature', -> iframe.onload = -> assert.equal iframe.contentWindow.test, 'undefined undefined undefined' done() + + describe 'storage', -> + it 'requesting persitent quota works', (done) -> + navigator.webkitPersistentStorage.requestQuota 1024 * 1024, (grantedBytes) -> + assert.equal grantedBytes, 1048576 + done() + + describe 'websockets', -> + wss = null + server = null + WebSocketServer = ws.Server + + afterEach -> + wss.close() + server.close() + + it 'has user agent', (done) -> + server = http.createServer() + server.listen 0, '127.0.0.1', -> + port = server.address().port + wss = new WebSocketServer(server: server) + wss.on 'error', done + wss.on 'connection', (ws) -> + if ws.upgradeReq.headers['user-agent'] + done() + else + done('user agent is empty') + websocket = new WebSocket("ws://127.0.0.1:#{port}") + + describe 'Promise', -> + it 'resolves correctly in Node.js calls', (done) -> + document.registerElement('x-element', { + prototype: Object.create(HTMLElement.prototype, { + createdCallback: { value: -> } + }) + }) + + setImmediate -> + called = false + Promise.resolve().then -> + done(if called then undefined else new Error('wrong sequnce')) + document.createElement 'x-element' + called = true + + it 'resolves correctly in Electron calls', (done) -> + document.registerElement('y-element', { + prototype: Object.create(HTMLElement.prototype, { + createdCallback: { value: -> } + }) + }) + + remote.getGlobal('setImmediate') -> + called = false + Promise.resolve().then -> + done(if called then undefined else new Error('wrong sequnce')) + document.createElement 'y-element' + called = true diff --git a/spec/fixtures/api/blank.html b/spec/fixtures/api/blank.html new file mode 100644 index 000000000000..accfb421446d --- /dev/null +++ b/spec/fixtures/api/blank.html @@ -0,0 +1,5 @@ + + + diff --git a/spec/fixtures/api/localstorage.html b/spec/fixtures/api/localstorage.html new file mode 100644 index 000000000000..8110a0b4be6a --- /dev/null +++ b/spec/fixtures/api/localstorage.html @@ -0,0 +1,11 @@ + + + + + diff --git a/spec/fixtures/api/preload.html b/spec/fixtures/api/preload.html index 22dee23444fa..ece481f92413 100644 --- a/spec/fixtures/api/preload.html +++ b/spec/fixtures/api/preload.html @@ -3,7 +3,7 @@ diff --git a/spec/fixtures/module/original-fs.js b/spec/fixtures/module/original-fs.js new file mode 100644 index 000000000000..90b6abcf9b78 --- /dev/null +++ b/spec/fixtures/module/original-fs.js @@ -0,0 +1,3 @@ +process.on('message', function (msg) { + process.send(typeof require('original-fs')); +}); diff --git a/spec/fixtures/module/preload-node-off.js b/spec/fixtures/module/preload-node-off.js new file mode 100644 index 000000000000..9020f4513a10 --- /dev/null +++ b/spec/fixtures/module/preload-node-off.js @@ -0,0 +1,7 @@ +setImmediate(function() { + try { + console.log([typeof process, typeof setImmediate, typeof global].join(' ')); + } catch (e) { + console.log(e.message); + } +}); diff --git a/spec/fixtures/module/promise.js b/spec/fixtures/module/promise.js new file mode 100644 index 000000000000..2e52ed374400 --- /dev/null +++ b/spec/fixtures/module/promise.js @@ -0,0 +1,5 @@ +exports.twicePromise = function (promise) { + return promise.then(function (value) { + return value * 2; + }); +} diff --git a/spec/fixtures/module/send-later.js b/spec/fixtures/module/send-later.js new file mode 100644 index 000000000000..fce96b84b787 --- /dev/null +++ b/spec/fixtures/module/send-later.js @@ -0,0 +1,4 @@ +var ipc = require('ipc'); +window.onload = function() { + ipc.send('answer', typeof window.process); +} diff --git a/spec/fixtures/module/set-immediate.js b/spec/fixtures/module/set-immediate.js new file mode 100644 index 000000000000..e7d44a75d1fe --- /dev/null +++ b/spec/fixtures/module/set-immediate.js @@ -0,0 +1,11 @@ +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); + }); +}); diff --git a/spec/fixtures/pages/basic-auth.html b/spec/fixtures/pages/basic-auth.html index 81d5bd209cee..aa95546a9c11 100644 --- a/spec/fixtures/pages/basic-auth.html +++ b/spec/fixtures/pages/basic-auth.html @@ -1,16 +1,21 @@ - + diff --git a/spec/fixtures/pages/c.html b/spec/fixtures/pages/c.html index f0e33fde5ea1..bc55104ec535 100644 --- a/spec/fixtures/pages/c.html +++ b/spec/fixtures/pages/c.html @@ -1,7 +1,7 @@ diff --git a/spec/fixtures/pages/close.html b/spec/fixtures/pages/close.html new file mode 100644 index 000000000000..9696eac36b54 --- /dev/null +++ b/spec/fixtures/pages/close.html @@ -0,0 +1,9 @@ + + + + + + + diff --git a/spec/fixtures/pages/dom-ready.html b/spec/fixtures/pages/dom-ready.html new file mode 100644 index 000000000000..541852f9ab0c --- /dev/null +++ b/spec/fixtures/pages/dom-ready.html @@ -0,0 +1,9 @@ + + + + + + diff --git a/spec/fixtures/pages/f.html b/spec/fixtures/pages/f.html deleted file mode 100644 index e2003a3ab0d8..000000000000 --- a/spec/fixtures/pages/f.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/spec/fixtures/pages/fullscreen.html b/spec/fixtures/pages/fullscreen.html new file mode 100644 index 000000000000..4e5648e0194d --- /dev/null +++ b/spec/fixtures/pages/fullscreen.html @@ -0,0 +1 @@ + diff --git a/spec/fixtures/pages/history.html b/spec/fixtures/pages/history.html new file mode 100644 index 000000000000..b5029d638926 --- /dev/null +++ b/spec/fixtures/pages/history.html @@ -0,0 +1,8 @@ + + + + + diff --git a/spec/fixtures/pages/onkeyup.html b/spec/fixtures/pages/onkeyup.html new file mode 100644 index 000000000000..99e6c3e98382 --- /dev/null +++ b/spec/fixtures/pages/onkeyup.html @@ -0,0 +1,9 @@ + + + + + diff --git a/spec/fixtures/pages/onmouseup.html b/spec/fixtures/pages/onmouseup.html new file mode 100644 index 000000000000..1fd38bc7211f --- /dev/null +++ b/spec/fixtures/pages/onmouseup.html @@ -0,0 +1,9 @@ + + + + + diff --git a/spec/fixtures/pages/partition/one.html b/spec/fixtures/pages/partition/one.html new file mode 100644 index 000000000000..ad22c341df95 --- /dev/null +++ b/spec/fixtures/pages/partition/one.html @@ -0,0 +1,4 @@ + diff --git a/spec/fixtures/pages/post.html b/spec/fixtures/pages/post.html new file mode 100644 index 000000000000..06f6f365492d --- /dev/null +++ b/spec/fixtures/pages/post.html @@ -0,0 +1,10 @@ + + +
+

+
+ + + diff --git a/spec/fixtures/pages/useragent.html b/spec/fixtures/pages/useragent.html new file mode 100644 index 000000000000..4e19f5b8276a --- /dev/null +++ b/spec/fixtures/pages/useragent.html @@ -0,0 +1,7 @@ + + + + + diff --git a/spec/fixtures/pages/will-navigate.html b/spec/fixtures/pages/will-navigate.html index bd2ebdc1cb43..8d0c9b779e2d 100644 --- a/spec/fixtures/pages/will-navigate.html +++ b/spec/fixtures/pages/will-navigate.html @@ -1,7 +1,7 @@ diff --git a/spec/fixtures/pages/window-opener-node.html b/spec/fixtures/pages/window-opener-node.html new file mode 100644 index 000000000000..118603c82d3b --- /dev/null +++ b/spec/fixtures/pages/window-opener-node.html @@ -0,0 +1,7 @@ + + + + + diff --git a/spec/fixtures/pages/window-opener-postMessage.html b/spec/fixtures/pages/window-opener-postMessage.html new file mode 100644 index 000000000000..5d0f8ede6d37 --- /dev/null +++ b/spec/fixtures/pages/window-opener-postMessage.html @@ -0,0 +1,7 @@ + + + + + diff --git a/spec/fixtures/pages/window-opener.html b/spec/fixtures/pages/window-opener.html new file mode 100644 index 000000000000..0b5ecd556c9b --- /dev/null +++ b/spec/fixtures/pages/window-opener.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/spec/modules-spec.coffee b/spec/modules-spec.coffee index 545d55e7d324..e7bdac36b3e3 100644 --- a/spec/modules-spec.coffee +++ b/spec/modules-spec.coffee @@ -7,16 +7,20 @@ describe 'third-party module', -> fixtures = path.join __dirname, 'fixtures' temp.track() - describe 'runas', -> - it 'can be required in renderer', -> - require 'runas' + # If the test is executed with the debug build on Windows, we will skip it + # because native modules don't work with the debug build (see issue #2558). + if process.platform isnt 'win32' or + process.execPath.toLowerCase().indexOf('\\out\\d\\') is -1 + describe 'runas', -> + it 'can be required in renderer', -> + require 'runas' - it 'can be required in node binary', (done) -> - runas = path.join fixtures, 'module', 'runas.js' - child = require('child_process').fork runas - child.on 'message', (msg) -> - assert.equal msg, 'ok' - done() + it 'can be required in node binary', (done) -> + runas = path.join fixtures, 'module', 'runas.js' + child = require('child_process').fork runas + child.on 'message', (msg) -> + assert.equal msg, 'ok' + done() describe 'q', -> Q = require 'q' diff --git a/spec/node-spec.coffee b/spec/node-spec.coffee index 006565407915..c8d569e01ada 100644 --- a/spec/node-spec.coffee +++ b/spec/node-spec.coffee @@ -56,6 +56,13 @@ describe 'node feature', -> done() child.send 'message' + it 'has setImmediate working in script', (done) -> + child = child_process.fork path.join(fixtures, 'module', 'set-immediate.js') + child.on 'message', (msg) -> + assert.equal msg, 'ok' + done() + child.send 'message' + describe 'contexts', -> describe 'setTimeout in fs callback', -> it 'does not crash', (done) -> @@ -126,3 +133,22 @@ describe 'node feature', -> b = new Buffer(p.innerText) assert.equal b.toString(), '闲云潭影日悠悠,物换星移几度秋' assert.equal Buffer.byteLength(p.innerText), 45 + + it 'correctly parses external one-byte UTF8 string', -> + p = document.createElement 'p' + p.innerText = 'Jøhänñéß' + b = new Buffer(p.innerText) + assert.equal b.toString(), 'Jøhänñéß' + assert.equal Buffer.byteLength(p.innerText), 13 + + describe 'process.stdout', -> + it 'should not throw exception', -> + process.stdout + + # Not reliable on some machines + xit 'should have isTTY defined', -> + assert.equal typeof(process.stdout.isTTY), 'boolean' + + describe 'vm.createContext', -> + it 'should not crash', -> + require('vm').runInNewContext('') diff --git a/spec/package.json b/spec/package.json index 6e9489e601d8..a3b0d590effa 100644 --- a/spec/package.json +++ b/spec/package.json @@ -9,8 +9,9 @@ "graceful-fs": "3.0.5", "mocha": "2.1.0", "q": "0.9.7", - "runas": "2.x", + "runas": "3.x", "temp": "0.8.1", - "walkdir": "0.0.7" + "walkdir": "0.0.7", + "ws": "0.7.2" } } diff --git a/spec/static/main.js b/spec/static/main.js index df9f19a630e2..38ba7cc089de 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -2,23 +2,28 @@ var app = require('app'); var ipc = require('ipc'); var dialog = require('dialog'); var BrowserWindow = require('browser-window'); -var Menu = require('menu'); 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'); +// 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; + ipc.on('message', function(event, arg) { event.sender.send('message', arg); }); ipc.on('console.log', function(event, args) { - console.log.apply(console, args); + console.error.apply(console, args); }); ipc.on('console.error', function(event, args) { - console.log.apply(console, args); + console.error.apply(console, args); }); ipc.on('process.exit', function(event, code) { @@ -46,95 +51,8 @@ app.on('window-all-closed', function() { }); app.on('ready', function() { - var template = [ - { - label: 'Atom', - submenu: [ - { - label: 'Quit', - accelerator: 'CommandOrControl+Q', - click: function(item, window) { app.quit(); } - }, - ], - }, - { - label: 'Edit', - submenu: [ - { - label: 'Undo', - accelerator: 'CommandOrControl+Z', - selector: 'undo:', - }, - { - label: 'Redo', - accelerator: 'CommandOrControl+Shift+Z', - selector: 'redo:', - }, - { - type: 'separator', - }, - { - label: 'Cut', - accelerator: 'CommandOrControl+X', - selector: 'cut:', - }, - { - label: 'Copy', - accelerator: 'CommandOrControl+C', - selector: 'copy:', - }, - { - label: 'Paste', - accelerator: 'CommandOrControl+V', - selector: 'paste:', - }, - { - label: 'Select All', - accelerator: 'CommandOrControl+A', - selector: 'selectAll:', - }, - ] - }, - { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'CommandOrControl+R', - click: function(item, window) { window.restart(); } - }, - { - label: 'Enter Fullscreen', - click: function(item, window) { window.setFullScreen(true); } - }, - { - label: 'Toggle DevTools', - accelerator: 'Alt+CommandOrControl+I', - click: function(item, window) { window.toggleDevTools(); } - }, - ] - }, - { - label: 'Window', - submenu: [ - { - label: 'Open', - accelerator: 'CommandOrControl+O', - }, - { - label: 'Close', - accelerator: 'CommandOrControl+W', - click: function(item, window) { window.close(); } - }, - ] - }, - ]; - - var menu = Menu.buildFromTemplate(template); - app.setApplicationMenu(menu); - // Test if using protocol module would crash. - require('protocol').registerProtocol('test-if-crashes', function() {}); + require('protocol').registerStringProtocol('test-if-crashes', function() {}); window = new BrowserWindow({ title: 'Electron Tests', diff --git a/spec/webview-spec.coffee b/spec/webview-spec.coffee index 54bc2e78817f..b310b7b129f8 100644 --- a/spec/webview-spec.coffee +++ b/spec/webview-spec.coffee @@ -1,16 +1,17 @@ assert = require 'assert' path = require 'path' +http = require 'http' describe ' tag', -> + @timeout 10000 + fixtures = path.join __dirname, 'fixtures' webview = null - beforeEach -> webview = new WebView - afterEach -> - document.body.removeChild webview + document.body.removeChild(webview) if document.body.contains(webview) describe 'src attribute', -> it 'specifies the page to load', (done) -> @@ -34,7 +35,7 @@ describe ' tag', -> describe 'nodeintegration attribute', -> it 'inserts no node symbols when not set', (done) -> webview.addEventListener 'console-message', (e) -> - assert.equal e.message, 'undefined undefined undefined' + assert.equal e.message, 'undefined undefined undefined undefined' done() webview.src = "file://#{fixtures}/pages/c.html" document.body.appendChild webview @@ -47,19 +48,31 @@ describe ' tag', -> webview.src = "file://#{fixtures}/pages/d.html" document.body.appendChild webview - it 'loads native modules when navigation happens', (done) -> - listener = (e) -> - webview.removeEventListener 'did-finish-load', listener - listener2 = (e) -> - assert.equal e.message, 'function' - done() - webview.addEventListener 'console-message', listener2 - webview.src = "file://#{fixtures}/pages/native-module.html" - webview.addEventListener 'did-finish-load', listener + it 'loads node symbols after POST navigation when set', (done) -> + webview.addEventListener 'console-message', (e) -> + assert.equal e.message, 'function object object' + done() webview.setAttribute 'nodeintegration', 'on' - webview.src = "file://#{fixtures}/pages/native-module.html" + webview.src = "file://#{fixtures}/pages/post.html" document.body.appendChild webview + # If the test is executed with the debug build on Windows, we will skip it + # because native modules don't work with the debug build (see issue #2558). + if process.platform isnt 'win32' or + process.execPath.toLowerCase().indexOf('\\out\\d\\') is -1 + it 'loads native modules when navigation happens', (done) -> + listener = (e) -> + webview.removeEventListener 'did-finish-load', listener + listener2 = (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', -> it 'loads the script before other scripts in window', (done) -> listener = (e) -> @@ -71,6 +84,14 @@ describe ' tag', -> 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', (done) -> + webview.addEventListener 'console-message', (e) -> + assert.equal e.message, 'object function 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', (done) -> message = 'boom!' listener = (e) -> @@ -99,6 +120,18 @@ describe ' tag', -> webview.src = "file://#{fixtures}/pages/referrer.html" document.body.appendChild webview + describe 'useragent attribute', -> + it 'sets the user agent', (done) -> + referrer = 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko' + listener = (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', -> it 'does not disable web security when not set', (done) -> src = " @@ -129,10 +162,49 @@ describe ' tag', -> webview.src = "data:text/html;base64,#{encoded}" document.body.appendChild webview + describe 'partition attribute', -> + it 'inserts no node symbols when not set', (done) -> + webview.addEventListener 'console-message', (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', (done) -> + webview.addEventListener 'console-message', (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', (done) -> + listener = (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', (done) -> + listener = (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 'new-window event', -> it 'emits when window.open is called', (done) -> webview.addEventListener 'new-window', (e) -> - assert.equal e.url, 'http://host' + assert.equal e.url, 'http://host/' assert.equal e.frameName, 'host' done() webview.src = "file://#{fixtures}/pages/window-open.html" @@ -179,6 +251,14 @@ describe ' tag', -> webview.src = "file://#{fixtures}/pages/a.html" document.body.appendChild webview + describe 'close event', -> + it 'should fire when interior page calls window.close', (done) -> + webview.addEventListener 'close', -> + done() + + webview.src = "file://#{fixtures}/pages/close.html" + document.body.appendChild webview + describe '.reload()', -> it 'should emit beforeunload handler', (done) -> listener = (e) -> @@ -193,3 +273,89 @@ describe ' tag', -> webview.setAttribute 'nodeintegration', 'on' webview.src = "file://#{fixtures}/pages/beforeunload-false.html" document.body.appendChild webview + + describe '.clearHistory()', -> + it 'should clear the navigation history', (done) -> + listener = (e) -> + assert.equal e.channel, 'history' + assert.equal e.args[0], 2 + assert webview.canGoBack() + webview.clearHistory() + assert not 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', -> + auth = require 'basic-auth' + + it 'should authenticate with correct credentials', (done) -> + message = 'Authenticated' + server = http.createServer (req, res) -> + credentials = auth(req) + if credentials.name == 'test' and credentials.pass == 'test' + res.end(message) + else + res.end('failed') + server.close() + server.listen 0, '127.0.0.1', -> + {port} = server.address() + webview.addEventListener 'ipc-message', (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', -> + it 'emits when document is loaded', (done) -> + server = http.createServer (req) -> + # Never respond, so the page never finished loading. + server.listen 0, '127.0.0.1', -> + {port} = server.address() + webview.addEventListener 'dom-ready', -> + done() + webview.src = "file://#{fixtures}/pages/dom-ready.html?port=#{port}" + document.body.appendChild webview + + describe 'executeJavaScript', -> + return unless process.env.TRAVIS is 'true' + + it 'should support user gesture', (done) -> + listener = (e) -> + webview.removeEventListener 'enter-html-full-screen', listener + done() + listener2 = (e) -> + jsScript = 'document.getElementsByTagName("video")[0].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 + + describe 'sendInputEvent', -> + it 'can send keyboard event', (done) -> + webview.addEventListener 'ipc-message', (e) -> + assert.equal e.channel, 'keyup' + assert.deepEqual e.args, [67, true, false] + done() + webview.addEventListener 'dom-ready', -> + webview.sendInputEvent type: 'keyup', keyCode: 'c', modifiers: ['shift'] + webview.src = "file://#{fixtures}/pages/onkeyup.html" + webview.setAttribute 'nodeintegration', 'on' + document.body.appendChild webview + + it 'can send mouse event', (done) -> + webview.addEventListener 'ipc-message', (e) -> + assert.equal e.channel, 'mouseup' + assert.deepEqual e.args, [10, 20, false, true] + done() + webview.addEventListener 'dom-ready', -> + webview.sendInputEvent type: 'mouseup', modifiers: ['ctrl'], x: 10, y: 20 + webview.src = "file://#{fixtures}/pages/onmouseup.html" + webview.setAttribute 'nodeintegration', 'on' + document.body.appendChild webview diff --git a/toolchain.gypi b/toolchain.gypi new file mode 100644 index 000000000000..6977847106f7 --- /dev/null +++ b/toolchain.gypi @@ -0,0 +1,234 @@ +{ + 'variables': { + # Clang stuff. + 'make_clang_dir%': 'vendor/llvm-build/Release+Asserts', + # Set this to true when building with Clang. + 'clang%': 1, + + # Path to sysroot dir. + 'sysroot%': '', + + 'variables': { + # Set ARM architecture version. + 'arm_version%': 7, + + # Set NEON compilation flags. + 'arm_neon%': 1, + }, + + # Copy conditionally-set variables out one scope. + 'arm_version%': '<(arm_version)', + 'arm_neon%': '<(arm_neon)', + + # Variables to control Link-Time Optimization (LTO). + 'use_lto%': 0, + 'use_lto_o2%': 0, + + 'conditions': [ + # Do not use Clang on Windows. + ['OS=="win"', { + 'clang%': 0, + }], # OS=="win" + + # Define the abosulte version of <(DEPTH). + ['OS!="win"', { + 'source_root': '/dev/null 2>&1 ; then + for inc_file in $root$included_files; do + process_ld_so_conf "$root" "$inc_file" + done + fi + else + if ls $(pwd)/$included_files >/dev/null 2>&1 ; then + for inc_file in $(pwd)/$included_files; do + process_ld_so_conf "$root" "$inc_file" + done + fi + fi + continue + fi + + echo "$ENTRY" | grep -qs ^/ + if [ $? -eq 0 ]; then + process_entry "$root" "$ENTRY" + fi + done + + # popd is a bashism + cd "$saved_pwd" +} + +# Main + +if [ $# -ne 1 ]; then + echo Usage $0 /abspath/to/sysroot + exit 1 +fi + +echo $1 | grep -qs ' ' +if [ $? -eq 0 ]; then + log_error_and_exit $1 contains whitespace. +fi + +LD_SO_CONF="$1/etc/ld.so.conf" +LD_SO_CONF_D="$1/etc/ld.so.conf.d" + +if [ -e "$LD_SO_CONF" ]; then + process_ld_so_conf "$1" "$LD_SO_CONF" | xargs echo +elif [ -e "$LD_SO_CONF_D" ]; then + find "$LD_SO_CONF_D" -maxdepth 1 -name '*.conf' -print -quit > /dev/null + if [ $? -eq 0 ]; then + for entry in $LD_SO_CONF_D/*.conf; do + process_ld_so_conf "$1" "$entry" + done | xargs echo + fi +fi diff --git a/vendor/boto b/vendor/boto new file mode 160000 index 000000000000..f7574aa6cc2c --- /dev/null +++ b/vendor/boto @@ -0,0 +1 @@ +Subproject commit f7574aa6cc2c819430c1f05e9a1a1a666ef8169b diff --git a/vendor/breakpad b/vendor/breakpad index 4427c1170387..4ee7e1a703d0 160000 --- a/vendor/breakpad +++ b/vendor/breakpad @@ -1 +1 @@ -Subproject commit 4427c1170387afe46eb3fad259436f1f9f5efa86 +Subproject commit 4ee7e1a703d066861b7bf6fce28526f8ed07dcd6 diff --git a/vendor/brightray b/vendor/brightray index a929d7582927..25f3a9d0a5b7 160000 --- a/vendor/brightray +++ b/vendor/brightray @@ -1 +1 @@ -Subproject commit a929d75829270def615e342c79ea17634e8c5989 +Subproject commit 25f3a9d0a5b73ec170a65f4e2e4c9ad91e23fc8c diff --git a/vendor/crashpad b/vendor/crashpad new file mode 160000 index 000000000000..e6a0d433b0ee --- /dev/null +++ b/vendor/crashpad @@ -0,0 +1 @@ +Subproject commit e6a0d433b0ee399eecce2bef671794771052ffdb diff --git a/vendor/native_mate b/vendor/native_mate index 047a8de9342a..b7387da0854b 160000 --- a/vendor/native_mate +++ b/vendor/native_mate @@ -1 +1 @@ -Subproject commit 047a8de9342a3217a8d8316f77b9276e8eb7a649 +Subproject commit b7387da0854b20d376fdae0d93a01f83d080668d diff --git a/vendor/node b/vendor/node index e5aaa1ad33b3..aa9c7a2316ba 160000 --- a/vendor/node +++ b/vendor/node @@ -1 +1 @@ -Subproject commit e5aaa1ad33b3479dbb29df797beca5873e7490dc +Subproject commit aa9c7a2316ba7762f1d04d091585695be3e6be22 diff --git a/vendor/requests b/vendor/requests new file mode 160000 index 000000000000..e4d59bedfd3c --- /dev/null +++ b/vendor/requests @@ -0,0 +1 @@ +Subproject commit e4d59bedfd3c7f4f254f4f5d036587bcd8152458